Files
slm-design/generate.ts
S.Gromov 07542330b5
All checks were successful
CI/CD Pipeline / build (push) Successful in 16s
CI/CD Pipeline / version (push) Successful in 4s
CI/CD Pipeline / docker (push) Successful in 42s
CI/CD Pipeline / deploy (push) Successful in 6s
fix: исправить ссылки llms на markdown-файлы
- обновлены ссылки llms.txt на доступные .md-файлы
- добавлено копирование markdown-файлов в публичную статику
- исключена docs/public из сканирования VitePress и индекса Git
- добавлена пометка для локальной копии ARCHITECTURE.md
2026-05-02 06:53:35 +03:00

218 lines
7.1 KiB
TypeScript
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.

import path from "path";
import fs from "fs";
import os from "os";
import { execFileSync } from "child_process";
const SRC_DIR = "./docs";
const PUBLIC_DIR = "./public";
const DOCS_PUBLIC_DIR = path.join(SRC_DIR, "public");
const DOC_ROUTE_PREFIX = "/docs";
const PUBLIC_ARCHITECTURE_FILE = "ARCHITECTURE.md";
const PUBLIC_ARCHITECTURE_NOTICE = `> Локальная копия канонической спецификации SLM Design.
> Источник: https://slm-design.gromlab.ru/ARCHITECTURE.md
> Не редактировать вручную в этом проекте.`;
interface SidebarItem {
text: string;
link: string;
}
interface SidebarGroup {
text: string;
items: SidebarItem[];
}
function parseSidebar(): SidebarGroup[] {
const configPath = path.join(".vitepress", "config.ts");
const raw = fs.readFileSync(configPath, "utf8");
const start = raw.indexOf("const sidebar = [");
const end = raw.indexOf("];", start) + 2;
const section = raw.substring(start, end);
const groups: SidebarGroup[] = [];
const groupParts = section.split(/\n\s*\}\s*,?\s*\n/).filter(Boolean);
for (const part of groupParts) {
const textMatch = part.match(/text:\s*'([^']*)'/);
if (!textMatch) continue;
const items: SidebarItem[] = [];
const itemRe = /\{\s*text:\s*'([^']*)'\s*,\s*link:\s*'([^']*)'\s*\}/g;
let im: RegExpExecArray | null;
while ((im = itemRe.exec(part)) !== null) {
items.push({ text: im[1], link: im[2] });
}
if (items.length > 0) groups.push({ text: textMatch[1], items });
}
return groups;
}
const SIDEBAR = parseSidebar();
function linkToFileRel(link: string): string {
const rel = link.replace(/^\//, "");
if (rel === "" || rel.endsWith("/")) return `${rel}index.md`;
return `${rel}.md`;
}
function fileRelToRoute(file: string): string {
const route = file.endsWith("/index.md")
? file.replace(/index\.md$/, "")
: file.replace(/\.md$/, "");
return `${DOC_ROUTE_PREFIX}/${route}`;
}
function fileRelToMdUrl(file: string): string {
return `${DOC_ROUTE_PREFIX}/${file}`;
}
function getAllFiles(): string[] {
return SIDEBAR.flatMap((g) => g.items.map((item) => linkToFileRel(item.link)));
}
const stripFrontmatter = (content: string) =>
content.replace(/^---[\s\S]*?---\n*/m, "");
const stripRulesLink = (content: string) =>
content.replace(/<!-- rules-link -->[\s\S]*?<!-- \/rules-link -->\n*/g, "");
const shiftHeadings = (content: string) => {
const lines = content.split("\n");
let inCodeBlock = false;
return lines
.map((line) => {
if (line.startsWith("```")) inCodeBlock = !inCodeBlock;
if (inCodeBlock) return line;
if (/^#{1,5}\s/.test(line)) return "#" + line;
return line;
})
.join("\n");
};
const buildArchitectureMarkdown = (routePrefix: string) => {
const files = getAllFiles();
const parts: string[] = [];
for (const file of files) {
const filePath = path.join(SRC_DIR, file);
if (!fs.existsSync(filePath)) continue;
const raw = fs.readFileSync(filePath, "utf8");
const content = stripRulesLink(stripFrontmatter(raw)).trim();
if (!content) continue;
const route = routePrefix + fileRelToRoute(file).replace(DOC_ROUTE_PREFIX, "");
const processed = file.endsWith("index.md") ? content : shiftHeadings(content);
parts.push(`<!-- ${route} -->\n${processed}`);
}
return parts.join("\n\n");
};
function buildLlms() {
const parts: string[] = [`# SLM Design\n`];
parts.push(`> Scoped Layered Module Design — модульная архитектура фронтенд-приложений\n`);
for (const group of SIDEBAR) {
parts.push(`## ${group.text}`);
for (const item of group.items) {
const fileRel = linkToFileRel(item.link);
const filePath = path.join(SRC_DIR, fileRel);
let desc = "";
if (fs.existsSync(filePath)) {
const raw = fs.readFileSync(filePath, "utf8");
const fm = raw.match(/^---[\s\S]*?---\n*/m);
desc = fm ? fm[0].match(/description:\s*(.+)/)?.[1] || "" : "";
}
const route = fileRelToMdUrl(fileRel);
const line = desc
? `- [${item.text}](${route}): ${desc}`
: `- [${item.text}](${route})`;
parts.push(line);
}
parts.push("");
}
const outPath = path.join(PUBLIC_DIR, "llms.txt");
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
fs.writeFileSync(outPath, parts.join("\n"), "utf8");
console.log(`llms.txt создан: ${outPath}`);
}
function buildLlmsFull() {
const outPath = path.join(PUBLIC_DIR, "llms-full.txt");
fs.writeFileSync(outPath, buildArchitectureMarkdown("/docs"), "utf8");
console.log(`llms-full.txt создан: ${outPath}`);
}
function copyMarkdownFiles() {
fs.rmSync(DOCS_PUBLIC_DIR, { recursive: true, force: true });
let copied = 0;
for (const file of getAllFiles()) {
const src = path.join(SRC_DIR, file);
if (!fs.existsSync(src)) continue;
const dest = path.join(DOCS_PUBLIC_DIR, file);
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.copyFileSync(src, dest);
copied++;
}
console.log(`скопировано ${copied} .md-файлов в ${DOCS_PUBLIC_DIR}`);
}
function buildPublicArchitecture() {
const outPath = path.join(PUBLIC_DIR, PUBLIC_ARCHITECTURE_FILE);
const content = `${PUBLIC_ARCHITECTURE_NOTICE}\n\n${buildArchitectureMarkdown("/docs")}`;
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
fs.writeFileSync(outPath, content, "utf8");
console.log(`${PUBLIC_ARCHITECTURE_FILE} создан: ${outPath}`);
}
function buildZip() {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "slm-"));
const tmpDir = path.join(tmpRoot, "slm-design");
fs.mkdirSync(tmpDir, { recursive: true });
const files = getAllFiles();
for (const file of files) {
const src = path.join(SRC_DIR, file);
if (!fs.existsSync(src)) continue;
let content = fs.readFileSync(src, "utf8");
content = stripRulesLink(stripFrontmatter(content)).trim();
const destName = path.basename(file);
fs.writeFileSync(path.join(tmpDir, destName), content, "utf8");
}
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8"));
const version = `v${pkg.version}`;
fs.writeFileSync(path.join(tmpDir, "VERSION"), `${version}\n${new Date().toISOString()}\n`, "utf8");
const outPath = path.resolve(PUBLIC_DIR, "slm-design.zip");
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
if (fs.existsSync(outPath)) fs.unlinkSync(outPath);
execFileSync("zip", ["-rq", outPath, "slm-design"], { cwd: tmpRoot });
fs.rmSync(tmpRoot, { recursive: true });
console.log(`slm-design.zip создан: ${outPath}`);
}
function buildReadme() {
const indexPath = path.join(SRC_DIR, "architecture/index.md");
if (!fs.existsSync(indexPath)) {
console.log("Пропуск README: index.md не найден");
return;
}
let content = stripFrontmatter(fs.readFileSync(indexPath, "utf8"));
content = content.replace(/<!-- rules-link -->[\s\S]*?<!-- \/rules-link -->\n*/g, "");
fs.writeFileSync("./README.md", content, "utf8");
console.log("README.md создан");
}
buildLlms();
buildLlmsFull();
copyMarkdownFiles();
buildPublicArchitecture();
buildZip();
buildReadme();