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 PUBLIC_ARCHITECTURE_FILE = "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 getAllFiles(): string[] { return SIDEBAR.flatMap((g) => g.items.map((item) => { const rel = item.link.replace(/^\//, "") + ".md"; const indexPath = rel.replace(/\.md$/, "/index.md"); const filePath = path.join(SRC_DIR, indexPath); return fs.existsSync(filePath) ? indexPath : rel; }) ); } const stripFrontmatter = (content: string) => content.replace(/^---[\s\S]*?---\n*/m, ""); const stripRulesLink = (content: string) => content.replace(/[\s\S]*?\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 + "/" + file.replace(/\.md$/, ""); const processed = file.endsWith("index.md") ? content : shiftHeadings(content); parts.push(`\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 rel = item.link.replace(/^\//, "") + ".md"; const indexPath = rel.replace(/\.md$/, "/index.md"); const fileRel = fs.existsSync(path.join(SRC_DIR, indexPath)) ? indexPath : rel; 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 = "/docs" + item.link; 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 buildPublicArchitecture() { const outPath = path.join(PUBLIC_DIR, PUBLIC_ARCHITECTURE_FILE); fs.mkdirSync(PUBLIC_DIR, { recursive: true }); fs.writeFileSync(outPath, buildArchitectureMarkdown("/docs"), "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(/[\s\S]*?\n*/g, ""); fs.writeFileSync("./README.md", content, "utf8"); console.log("README.md создан"); } buildLlms(); buildLlmsFull(); buildPublicArchitecture(); buildZip(); buildReadme();