docs: docs-site initial
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { copyFile, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const siteRoot = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
|
||||
const repoRoot = path.resolve(siteRoot, '..');
|
||||
const sourceDir = path.join(repoRoot, 'docs');
|
||||
const outputDir = path.join(siteRoot, 'src', 'content', 'docs', 'reference');
|
||||
|
||||
const descriptions = {
|
||||
'admin.md': 'Возможности админ-панели, управление пользователями, настройками, тарифами и поддержкой.',
|
||||
'architecture.md': 'Краткая архитектура backend, frontend, worker и инфраструктурных сервисов.',
|
||||
'configuration.md': 'Минимальный .env, bootstrap-секреты и настройка через Web App админку.',
|
||||
'deployment.md': 'Docker Compose, reverse proxy, TLS, образы, обновления и резервные копии.',
|
||||
'env-vars.md': 'Полный справочник переменных окружения Remnawave Minishop.',
|
||||
'migration-to-minishop.md': 'Перенос данных со старого remnawave-tg-shop на split-архитектуру Minishop.',
|
||||
'support.md': 'Пользовательские тикеты, админский inbox, уведомления и лимиты поддержки.',
|
||||
'tariffs.md': 'Каталог тарифов, period/traffic-модели, premium-сквады и HWID-устройства.',
|
||||
'webapp.md': 'Telegram Mini App, авторизация, публичные инструкции и проксирование.',
|
||||
'webapp-themes.md': 'Кастомные темы, CSS-токены, ассеты и пайплайн создания темы.',
|
||||
};
|
||||
|
||||
const imageExtensions = new Set(['.avif', '.gif', '.jpeg', '.jpg', '.png', '.svg', '.webp']);
|
||||
|
||||
function yamlString(value) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function slugFor(fileName) {
|
||||
return fileName.replace(/\.md$/i, '');
|
||||
}
|
||||
|
||||
function extractTitle(fileName, content) {
|
||||
const match = content.match(/^#\s+(.+?)\s*$/m);
|
||||
return match?.[1] ?? slugFor(fileName);
|
||||
}
|
||||
|
||||
function stripFirstHeading(content) {
|
||||
return content.replace(/^#\s+.+?\s*\r?\n+/, '');
|
||||
}
|
||||
|
||||
function rewriteMarkdownLinks(markdown) {
|
||||
return markdown.replace(/\]\((?!https?:\/\/|mailto:|tel:|\/|#)([^)\s]+\.md)(#[^)]+)?\)/g, (match, target, hash = '') => {
|
||||
const slug = slugFor(path.posix.basename(target));
|
||||
return `](/reference/${slug}/${hash})`;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeCodeFences(markdown) {
|
||||
return markdown
|
||||
.replace(/^```env\s*$/gim, '```ini')
|
||||
.replace(/^```caddyfile\s*$/gim, '```txt');
|
||||
}
|
||||
|
||||
function frontmatter({ title, description, fileName }) {
|
||||
const editUrl = `https://gitlab.com/3252a8/remnawave-minshop/-/edit/main/docs/${encodeURIComponent(fileName)}`;
|
||||
return [
|
||||
'---',
|
||||
`title: ${yamlString(title)}`,
|
||||
`description: ${yamlString(description)}`,
|
||||
`editUrl: ${yamlString(editUrl)}`,
|
||||
'---',
|
||||
'',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
async function syncMarkdown(entries) {
|
||||
for (const entry of entries.filter((item) => item.name.endsWith('.md'))) {
|
||||
const sourcePath = path.join(sourceDir, entry.name);
|
||||
const content = await readFile(sourcePath, 'utf8');
|
||||
const title = extractTitle(entry.name, content);
|
||||
const body = normalizeCodeFences(rewriteMarkdownLinks(stripFirstHeading(content).trimStart()));
|
||||
const output = frontmatter({
|
||||
title,
|
||||
description: descriptions[entry.name] ?? title,
|
||||
fileName: entry.name,
|
||||
});
|
||||
|
||||
await writeFile(path.join(outputDir, entry.name), `${output}${body}\n`, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
async function syncImages(entries) {
|
||||
for (const entry of entries.filter((item) => imageExtensions.has(path.extname(item.name).toLowerCase()))) {
|
||||
await copyFile(path.join(sourceDir, entry.name), path.join(outputDir, entry.name));
|
||||
}
|
||||
}
|
||||
|
||||
await rm(outputDir, { recursive: true, force: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const entries = await readdir(sourceDir, { withFileTypes: true });
|
||||
await syncMarkdown(entries);
|
||||
await syncImages(entries);
|
||||
|
||||
console.log(`Synced documentation from ${path.relative(repoRoot, sourceDir)} to ${path.relative(repoRoot, outputDir)}`);
|
||||
Reference in New Issue
Block a user