import { spawn } from 'node:child_process'; import { access, 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 frontendRoot = path.join(repoRoot, 'frontend'); const runtimeDir = path.join(siteRoot, 'public', 'demo', 'runtime'); const templatesDir = path.join(repoRoot, 'backend', 'bot', 'app', 'web', 'templates'); const themesDir = path.join(repoRoot, 'backend', 'bot', 'app', 'web', 'themes'); const localesDir = path.join(repoRoot, 'locales'); const runtimeBase = '/demo/runtime'; const installGuidesConfigUrl = 'https://raw.githubusercontent.com/legiz-ru/my-remnawave/main/sub-page/subpage-config/multiapp.json'; const isWindows = process.platform === 'win32'; const npmExecPath = process.env.npm_execpath || ''; function run(command, args, options = {}) { return new Promise((resolve, reject) => { const child = spawn(command, args, { cwd: repoRoot, stdio: 'inherit', shell: false, ...options, }); child.on('error', reject); child.on('exit', (code) => { if (code === 0) { resolve(); return; } reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`)); }); }); } function runNpm(args) { if (npmExecPath) { return run(process.execPath, [npmExecPath, ...args]); } return run(isWindows ? 'npm.cmd' : 'npm', args, { shell: isWindows }); } async function pathExists(targetPath) { try { await access(targetPath); return true; } catch (_error) { return false; } } async function ensureFrontendDependencies() { const viteBin = isWindows ? path.join(frontendRoot, 'node_modules', '.bin', 'vite.cmd') : path.join(frontendRoot, 'node_modules', '.bin', 'vite'); if (await pathExists(viteBin)) return; await runNpm(['--prefix', frontendRoot, 'ci']); } async function copyDirectory(sourceDir, targetDir, transform = null) { await mkdir(targetDir, { recursive: true }); const entries = await readdir(sourceDir, { withFileTypes: true }); for (const entry of entries) { const sourcePath = path.join(sourceDir, entry.name); const targetPath = path.join(targetDir, entry.name); if (entry.isDirectory()) { await copyDirectory(sourcePath, targetPath, transform); continue; } if (!entry.isFile()) continue; if (transform) { const handled = await transform(sourcePath, targetPath); if (handled) continue; } await mkdir(path.dirname(targetPath), { recursive: true }); await copyFile(sourcePath, targetPath); } } async function copyThemeFile(sourcePath, targetPath) { if (path.extname(sourcePath).toLowerCase() !== '.css') return false; const css = await readFile(sourcePath, 'utf8'); const rewritten = css.replace(/\/webapp-theme-assets\//g, `${runtimeBase}/themes/`); await mkdir(path.dirname(targetPath), { recursive: true }); await writeFile(targetPath, rewritten, 'utf8'); return true; } async function copyRuntimeAsset(name) { await copyFile(path.join(templatesDir, name), path.join(runtimeDir, name)); } function jsonScriptPayload(value) { return JSON.stringify(value).replace(/ Remnawave Minishop Demo
`; } await ensureFrontendDependencies(); await runNpm(['--prefix', frontendRoot, 'run', 'build:docs-demo']); await rm(runtimeDir, { recursive: true, force: true }); await mkdir(runtimeDir, { recursive: true }); const html = await appHtml(); await Promise.all([ copyRuntimeAsset('subscription_webapp_docs_demo.js'), copyRuntimeAsset('subscription_webapp_docs_demo.css'), copyRuntimeAsset('subscription_webapp_admin.js'), copyRuntimeAsset('subscription_webapp_admin.css'), copyDirectory(path.join(templatesDir, 'default-brand'), path.join(runtimeDir, 'default-brand')), copyDirectory(themesDir, path.join(runtimeDir, 'themes'), copyThemeFile), writeFile(path.join(runtimeDir, 'app.html'), html, 'utf8'), installGuidesConfigPayload().then((payload) => writeFile(path.join(runtimeDir, 'subscription-guides-config.json'), payload, 'utf8'), ), ]); console.log(`Built static docs demo runtime at ${path.relative(repoRoot, runtimeDir)}`);