签入版本
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 生成 public/llms.txt(llmstxt.org 标准)——AI 爬虫的站点摘要索引。
|
||||
*
|
||||
* 数据源:zh 目录下全部 Markdown 的 H1 + :::tip 概述首句(与 meta
|
||||
* description 同源,复用 .vitepress/config/extract.mjs)。按侧边栏章节分组。
|
||||
*
|
||||
* 用法:node scripts/generate-llms-txt.mjs
|
||||
* 已挂入 package.json 的 docs:build 前置步骤。
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
SITE_URL,
|
||||
SITE_DESCRIPTION,
|
||||
extractDescription,
|
||||
} from "../.vitepress/config/extract.mjs";
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const ZH = path.join(ROOT, "zh");
|
||||
|
||||
/** 顶层目录 → 侧边栏章节名(与 .vitepress/config/zh.ts 保持一致) */
|
||||
const SECTIONS = [
|
||||
["", "1. 了解项目"],
|
||||
["volume1", "2. 快速入门"],
|
||||
["volume2", "3. 模块开发实战"],
|
||||
["volume3", "4. 架构设计"],
|
||||
["volume4", "5. 开发指南"],
|
||||
["volume5", "6. 代码生成器"],
|
||||
["volume6", "7. 运维部署"],
|
||||
["volume7", "8. 规范标准"],
|
||||
["other", "附录"],
|
||||
];
|
||||
|
||||
function walk(dir, out = []) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) walk(p, out);
|
||||
else if (entry.name.endsWith(".md")) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 提取首个 H1 文本 */
|
||||
function h1Of(md) {
|
||||
// 围栏感知:跳过 ``` 代码块内的 # 注释,取真正的页面 H1
|
||||
let inFence = false;
|
||||
for (const line of md.split("\n")) {
|
||||
if (line.startsWith("```")) { inFence = !inFence; continue; }
|
||||
if (!inFence && line.startsWith("# ") && !line.startsWith("## ")) {
|
||||
return line.slice(2).trim();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/** H1 编号排序键:"5.10 x" → [5,10];无编号排最后 */
|
||||
function sortKey(h1) {
|
||||
const m = h1.match(/^(\d+(?:\.\d+)*)/);
|
||||
if (m) return m[1].split(".").map(Number);
|
||||
const letter = h1.match(/^([A-Z])\./);
|
||||
if (letter) return [1000 + letter[1].charCodeAt(0)];
|
||||
return [9999];
|
||||
}
|
||||
|
||||
function cmpKey(a, b) {
|
||||
const len = Math.max(a.length, b.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const x = a[i] ?? 0;
|
||||
const y = b[i] ?? 0;
|
||||
if (x !== y) return x - y;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const files = walk(ZH);
|
||||
const groups = new Map(SECTIONS.map(([dir, label]) => [label, []]));
|
||||
|
||||
for (const file of files) {
|
||||
const rel = path.relative(ZH, file).replace(/\\/g, "/");
|
||||
const topDir = rel.includes("/") ? rel.split("/")[0] : "";
|
||||
const base = rel.split("/").pop();
|
||||
const label = (topDir === "" && base === "faq.md") ? "附录"
|
||||
: SECTIONS.find(([dir]) => dir === topDir)?.[1] ?? "附录";
|
||||
|
||||
const md = fs.readFileSync(file, "utf8");
|
||||
const h1 = h1Of(md);
|
||||
if (!h1) continue;
|
||||
const desc = extractDescription(md);
|
||||
const url = `${SITE_URL}/zh/${rel.replace(/\.md$/, ".html")}`;
|
||||
groups.get(label).push({ h1, desc, url, key: sortKey(h1) });
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"# DjangoAdmin Django+AntDesign 开发文档",
|
||||
"",
|
||||
`> ${SITE_DESCRIPTION}。内置 RBAC 权限体系、代码生成器等开箱即用功能,支持五种数据库一键切换。本文档共 ${files.length} 篇,按章节组织。`,
|
||||
"",
|
||||
];
|
||||
|
||||
// 按章节顺序输出,合并重复 label(appendix/other 同为「附录」,只输出一次)
|
||||
const printed = new Set();
|
||||
for (const [, label] of SECTIONS) {
|
||||
if (printed.has(label)) continue;
|
||||
const items = groups.get(label);
|
||||
if (!items?.length) continue;
|
||||
printed.add(label);
|
||||
|
||||
lines.push(`## ${label}`, "");
|
||||
items.sort((a, b) => cmpKey(a.key, b.key));
|
||||
for (const { h1, desc, url } of items) {
|
||||
lines.push(desc ? `- [${h1}](${url}): ${desc}` : `- [${h1}](${url})`);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
const out = path.join(ROOT, "public", "llms.txt");
|
||||
fs.writeFileSync(out, lines.join("\n"), "utf8");
|
||||
console.log(
|
||||
`llms.txt: ${files.length} 页 → ${path.relative(ROOT, out)} (${printed.size} 个章节)`,
|
||||
);
|
||||
Reference in New Issue
Block a user