Files
site-portfolio/scripts/sync-gitverse.mjs
T

163 lines
5.5 KiB
JavaScript

/**
* Копирует содержимое папки public в папку gitverse.
* Файлы `.png` не копируются (на GitVerse достаточно WebP и прочих ассетов).
*
* По умолчанию:
* node scripts/sync-gitverse.mjs
* (копирует projectRoot/public -> projectRoot/gitverse)
*
* Можно передать свои пути:
* node scripts/sync-gitverse.mjs <sourceDir> <targetDir>
*/
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);
});