Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_AntdVue/scripts/generate-llms-txt.mjs
T
2026-10-25 18:52:36 +08:00

120 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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. 了解项目"],
["quickstart", "2. 快速入门"],
["guide", "3. 模块开发实战"],
["architecture", "4. 架构设计"],
["core", "5. 核心功能"],
["modules", "6. 业务模块"],
["tools", "7. 文件处理"],
["api-spec", "8. 接口规范与对接"],
["frontend", "9. 前端开发"],
["generator", "10. 代码生成器"],
["deploy", "11. 运维部署"],
["standards", "12. 规范标准"],
["appendix", "附录"],
["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) {
const m = md.match(/^# (?!#)(.+)$/m);
return m ? m[1].trim() : "";
}
/** 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 label =
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 = [
"# RXThinkCMF Laravel13+AntDesign 开发文档",
"",
`> ${SITE_DESCRIPTION}。支持五种数据库一键切换、RBAC 按钮级权限、多租户数据隔离、声明式 CRUD 基座与代码生成器。本文档共 ${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} 个章节)`,
);