/** * Копирует содержимое папки public в папку gitverse. * Файлы `.png` не копируются (на GitVerse достаточно WebP и прочих ассетов). * * По умолчанию: * node scripts/sync-gitverse.mjs * (копирует projectRoot/public -> projectRoot/gitverse) * * Можно передать свои пути: * node scripts/sync-gitverse.mjs */ import path from "node:path"; import os from "node:os"; import fsp from "node:fs/promises"; import { execFile, spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const projectRoot = path.resolve(__dirname, ".."); const PRESERVE_IN_TARGET = new Set([".git", ".gitverse"]); function shouldSkipGitverseFile(fileName) { return path.extname(fileName).toLowerCase() === ".png"; } const DEFAULT_SSH_KEY_PATH = path.join(os.homedir(), ".ssh", "id_ed25519_work"); const GITVERSE_SITE_URL = "https://mamadaevv.gitverse.site/me/"; /** Системный браузер по умолчанию (Windows / macOS / Linux). */ function openUrlInDefaultBrowser(url) { if (typeof url !== "string" || !/^https?:\/\//i.test(url)) return; const opts = { detached: true, stdio: "ignore" }; let child; if (process.platform === "win32") { child = spawn("cmd", ["/c", "start", "", url], opts); } else if (process.platform === "darwin") { child = spawn("open", [url], opts); } else { child = spawn("xdg-open", [url], opts); } child?.unref(); } function resolveDir(input, fallback) { const raw = input ?? fallback; return path.isAbsolute(raw) ? raw : path.resolve(projectRoot, raw); } async function ensureDirExists(dirPath, label) { try { const st = await fsp.stat(dirPath); if (!st.isDirectory()) { throw new Error(`${label} не является папкой: ${dirPath}`); } } catch (error) { if (error && error.code === "ENOENT") { throw new Error(`${label} не найдена: ${dirPath}`); } throw error; } } async function cleanTargetDir(targetDir) { await fsp.mkdir(targetDir, { recursive: true }); const entries = await fsp.readdir(targetDir, { withFileTypes: true }); for (const entry of entries) { if (PRESERVE_IN_TARGET.has(entry.name)) continue; const fullPath = path.join(targetDir, entry.name); await fsp.rm(fullPath, { recursive: true, force: true }); } } async function copyDirFiltered(sourceDir, targetDir) { await fsp.mkdir(targetDir, { recursive: true }); const entries = await fsp.readdir(sourceDir, { withFileTypes: true }); for (const entry of entries) { const srcPath = path.join(sourceDir, entry.name); const dstPath = path.join(targetDir, entry.name); if (entry.isDirectory()) { await copyDirFiltered(srcPath, dstPath); continue; } if (!entry.isFile() || shouldSkipGitverseFile(entry.name)) continue; await fsp.copyFile(srcPath, dstPath); } } function runGit(args, cwd) { return new Promise((resolve, reject) => { const gitEnv = { ...process.env, GIT_SSH_COMMAND: `ssh -i "${DEFAULT_SSH_KEY_PATH}" -o IdentitiesOnly=yes`, }; execFile("git", args, { cwd, encoding: "utf8", env: gitEnv }, (error, stdout, stderr) => { if (error) { reject(new Error((stderr || error.message || "").trim())); return; } resolve((stdout || "").trim()); }); }); } function makeCommitMessage() { const now = new Date(); const y = now.getFullYear(); const m = String(now.getMonth() + 1).padStart(2, "0"); const d = String(now.getDate()).padStart(2, "0"); const hh = String(now.getHours()).padStart(2, "0"); const mm = String(now.getMinutes()).padStart(2, "0"); return `sync public ${y}-${m}-${d} ${hh}:${mm}`; } async function commitAndPush(targetDir) { const gitDir = path.join(targetDir, ".git"); await ensureDirExists(gitDir, "Git репозиторий (.git)"); try { await fsp.access(DEFAULT_SSH_KEY_PATH); } catch { throw new Error(`SSH-ключ не найден: ${DEFAULT_SSH_KEY_PATH}`); } await runGit(["add", "."], targetDir); const changed = await runGit(["status", "--porcelain"], targetDir); if (!changed) { console.log("Изменений для коммита нет."); return; } const message = makeCommitMessage(); await runGit(["commit", "-m", message], targetDir); await runGit(["push"], targetDir); console.log(`Коммит и пуш выполнены: ${message}`); openUrlInDefaultBrowser(GITVERSE_SITE_URL); console.log(`Открыт в браузере: ${GITVERSE_SITE_URL}`); } async function main() { const sourceDir = resolveDir(process.argv[2], "public"); const targetDir = resolveDir(process.argv[3], "gitverse"); await ensureDirExists(sourceDir, "Исходная папка"); if (sourceDir === targetDir) { throw new Error("Исходная и целевая папки совпадают."); } await cleanTargetDir(targetDir); await copyDirFiltered(sourceDir, targetDir); console.log( `Синхронизировано: ${path.relative(projectRoot, sourceDir)} -> ${path.relative(projectRoot, targetDir)} (сохранены .git и .gitverse)`, ); await commitAndPush(targetDir); } main().catch((error) => { console.error("Ошибка копирования:", error.message || error); process.exit(1); });