- добавлен отдельный VitePress-сайт для NextJS Style Guide - удалены дубли SLM-канонов из style-guide - обновлены ссылки, сборочные скрипты, CI, Docker и README - разблокирована карточка NextJS Style Guide на главной
147 lines
4.2 KiB
TypeScript
147 lines
4.2 KiB
TypeScript
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import { pathToFileURL } from 'node:url';
|
||
|
||
type Page = {
|
||
source: string;
|
||
target: string;
|
||
};
|
||
|
||
type RouteRewrite = {
|
||
from: string;
|
||
to: string;
|
||
};
|
||
|
||
type DocsConfig = {
|
||
mounts: Page[];
|
||
routeRewrites?: RouteRewrite[];
|
||
};
|
||
|
||
const MD_LINK_RE = /\]\((?!#|[a-z][a-z0-9+.-]*:|\/\/)([^)\s]+)(#[^)]*)?\)/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)]),
|
||
);
|
||
const routeRewrites = [...(config.routeRewrites ?? [])].sort((a, b) => b.from.length - a.from.length);
|
||
|
||
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 applyRouteRewrites(route: string) {
|
||
for (const rewrite of routeRewrites) {
|
||
if (route === rewrite.from || route.startsWith(`${rewrite.from}/`) || route.startsWith(`${rewrite.from}#`)) {
|
||
return `${rewrite.to}${route.slice(rewrite.from.length)}`;
|
||
}
|
||
}
|
||
|
||
return undefined;
|
||
}
|
||
|
||
function formatDocsRoute(route: string) {
|
||
const rewritten = applyRouteRewrites(route);
|
||
if (rewritten) return rewritten;
|
||
|
||
if (route === '/docs') return '/';
|
||
if (route.startsWith('/docs/')) return route.slice('/docs'.length);
|
||
|
||
return undefined;
|
||
}
|
||
|
||
function formatRelativeRoute(hrefPath: string, sourceDir: string) {
|
||
const sourcePath = normalizePath(path.posix.normalize(path.posix.join(sourceDir, hrefPath)));
|
||
|
||
if (sourcePath.startsWith('style-guide/')) {
|
||
const route = `/docs/${sourcePath.slice('style-guide/'.length)}`;
|
||
|
||
return applyRouteRewrites(route);
|
||
}
|
||
|
||
return undefined;
|
||
}
|
||
|
||
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 queryPart = query ? `?${query}` : '';
|
||
|
||
if (hrefPath.startsWith('/')) {
|
||
const route = formatDocsRoute(hrefPath);
|
||
|
||
if (route) return `](${route}${queryPart}${hash})`;
|
||
|
||
const rewritten = applyRouteRewrites(hrefPath);
|
||
|
||
if (rewritten) return `](${rewritten}${queryPart}${hash})`;
|
||
|
||
return match;
|
||
}
|
||
|
||
if (!hrefPath.endsWith('.md')) {
|
||
const route = formatRelativeRoute(hrefPath, sourceDir);
|
||
|
||
if (route) return `](${route}${queryPart}${hash})`;
|
||
|
||
return match;
|
||
}
|
||
|
||
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}${queryPart}`);
|
||
|
||
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} страниц`);
|