Files
docs/scripts/docs/prepare.ts
S.Gromov 86ab6bc8fd feat: добавить хаб документаций
- добавлен React/Vite-лендинг с карточками документаций
- добавлена генерация корневого llms.txt из конфига документов
- добавлена сборка SLM Design через VitePress
- добавлены Dockerfile, Caddyfile и Gitea CI/CD
- настроены контекстные Link headers для llms.txt
2026-05-13 10:12:31 +03:00

86 lines
2.6 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 fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
type Page = {
source: string;
target: string;
};
type DocsConfig = {
mounts: Page[];
};
const MD_LINK_RE = /\]\((?!#|[a-z][a-z0-9+.-]*:)([^)\s]+\.md)(#[^)]*)?\)/gi;
const siteName = process.argv[2];
if (!siteName) {
throw new Error('Укажите имя сайта: tsx scripts/docs/prepare.ts slm-design');
}
const rootDir = process.cwd();
const canonsDir = path.join(rootDir, 'canons');
const siteDir = path.join(rootDir, 'docs', siteName);
const contentDir = path.join(siteDir, 'content');
const configPath = path.join(siteDir, 'docs.config.ts');
if (!fs.existsSync(configPath)) {
throw new Error(`Не найден конфиг сайта: ${configPath}`);
}
const config = (await import(pathToFileURL(configPath).href)) as DocsConfig;
const targetBySource = new Map(
config.mounts.map((page) => [normalizePath(page.source), normalizePath(page.target)]),
);
function normalizePath(value: string) {
return value.split(path.sep).join('/').replace(/^\.\//, '');
}
function formatRelativeMarkdownPath(fromTarget: string, toTarget: string) {
const relative = path
.relative(path.dirname(fromTarget), toTarget)
.split(path.sep)
.join('/');
return relative.startsWith('.') ? relative : `./${relative}`;
}
function transformMarkdownLinks(content: string, page: Page) {
const sourceDir = path.posix.dirname(normalizePath(page.source));
return content.replace(MD_LINK_RE, (match, href: string, hash = '') => {
const [hrefPath, query = ''] = href.split('?');
const sourcePath = normalizePath(path.posix.normalize(path.posix.join(sourceDir, hrefPath)));
const target = targetBySource.get(sourcePath);
if (!target) return match;
const nextHref = formatRelativeMarkdownPath(page.target, `${target}${query ? `?${query}` : ''}`);
return `](${nextHref}${hash})`;
});
}
fs.rmSync(contentDir, { recursive: true, force: true });
fs.mkdirSync(contentDir, { recursive: true });
for (const page of config.mounts) {
const sourcePath = path.join(canonsDir, page.source);
const targetPath = path.join(contentDir, page.target);
if (!fs.existsSync(sourcePath)) {
throw new Error(`Не найден канон: ${sourcePath}`);
}
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
const content = transformMarkdownLinks(fs.readFileSync(sourcePath, 'utf8'), page);
fs.writeFileSync(targetPath, content, 'utf8');
console.log(`${page.target} -> canons/${page.source}`);
}
console.log(`Подготовлен VitePress content для ${siteName}: ${config.mounts.length} страниц`);