UNPKG

git-veil

Version:

A CLI tool for synchronizing development activities to a personal GitHub repository discreetly and confidentially.

299 lines (298 loc) 14.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.pushCommits = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const child_process_1 = require("child_process"); const qrcode_1 = __importDefault(require("qrcode")); const fileUtils_1 = require("../utils/fileUtils"); // Main function for pushing anonymized commits function pushCommits(options) { var _a; return __awaiter(this, void 0, void 0, function* () { // Search for the GitVeil project root (to always use the correct config) let projectRoot = path.dirname(((_a = require.main) === null || _a === void 0 ? void 0 : _a.filename) || process.argv[1]); while (!fs.existsSync(path.join(projectRoot, "gitveil.config.json"))) { const parent = path.dirname(projectRoot); if (parent === projectRoot) break; projectRoot = parent; } const configPath = path.join(projectRoot, "gitveil.config.json"); let targetRepoPath = ""; let userName = "GitVeil"; let userEmail = "gitveil@example.com"; // Read the module config (always at the root) if (fs.existsSync(configPath)) { const config = JSON.parse(fs.readFileSync(configPath, "utf-8")); targetRepoPath = config.targetRepoPath; userName = config.name || userName; userEmail = config.email || userEmail; } else { console.error("gitveil.config.json not found."); console.log("Please run `gitveil config --init` to create the configuration file."); return; } console.log(); console.log(`> Mirror repo path: ${targetRepoPath}`); // Check for the presence of README.md (serves as commit file) const readmePath = path.join(targetRepoPath, "README.md"); let initialCounter = 0; if (fs.existsSync(readmePath)) { try { const readmeContent = fs.readFileSync(readmePath, "utf-8"); const counterMatch = readmeContent.match(/Counter: (\d+)/); if (counterMatch) initialCounter = parseInt(counterMatch[1], 10); } catch (e) { console.warn("Unable to read the initial Counter value in README.md"); } } else { // Create an empty README.md if needed fs.writeFileSync(readmePath, ""); console.log("📄 README.md created in targetRepoPath."); } // Memory-optimized reading of JSON files (avoids parsing multiple times) const logsDir = path.join(projectRoot, "records-folder"); if (!fs.existsSync(logsDir)) { console.error(`❌ Logs directory not found: ${logsDir}`); return; } // Only read .json files const files = fs.readdirSync(logsDir).filter((f) => f.endsWith(".json")); const allDates = new Set(); for (const file of files) { try { const content = fs.readFileSync(path.join(logsDir, file), "utf-8"); const json = JSON.parse(content); if (Array.isArray(json)) { json.forEach((entry) => allDates.add(entry)); } } catch (e) { console.warn(`⚠️ Error parsing file ${file}`); } } // Initialize git repository only if needed const gitDir = path.join(targetRepoPath, ".git"); if (!fs.existsSync(gitDir)) { // If the repository doesn't exist, initialize it and configure the user console.log(`🆕 Initializing git repo in ${targetRepoPath}`); (0, child_process_1.execSync)(`git -C "${targetRepoPath}" init`); (0, child_process_1.execSync)(`git -C "${targetRepoPath}" config user.name "${userName}"`); (0, child_process_1.execSync)(`git -C "${targetRepoPath}" config user.email "${userEmail}"`); if (fs.existsSync(readmePath)) { (0, child_process_1.execSync)(`git -C "${targetRepoPath}" add README.md`); const status = (0, child_process_1.execSync)(`git -C "${targetRepoPath}" status --porcelain`).toString(); if (status.trim()) { (0, child_process_1.execSync)(`git -C "${targetRepoPath}" commit -m "Initial commit"`); console.log("✅ Initial commit created."); } } } else { // If the repository exists, check user config let currentName = ""; try { currentName = (0, child_process_1.execSync)(`git -C "${targetRepoPath}" config user.name`) .toString() .trim(); } catch (_b) { } let currentEmail = ""; try { currentEmail = (0, child_process_1.execSync)(`git -C "${targetRepoPath}" config user.email`) .toString() .trim(); } catch (_c) { } if (currentName !== userName) (0, child_process_1.execSync)(`git -C "${targetRepoPath}" config user.name "${userName}"`); if (currentEmail !== userEmail) (0, child_process_1.execSync)(`git -C "${targetRepoPath}" config user.email "${userEmail}"`); } // Check if the repo has no commits and push an initial commit if needed let hasCommit = false; try { const logResult = (0, child_process_1.execSync)(`git -C "${targetRepoPath}" log --oneline main`, { stdio: "pipe", }).toString(); if (logResult.trim()) hasCommit = true; } catch (e) { } if (!hasCommit) { if (!fs.existsSync(readmePath)) { fs.writeFileSync(readmePath, "# GitVeil\n"); } (0, child_process_1.execSync)(`git -C "${targetRepoPath}" add README.md`); (0, child_process_1.execSync)(`git -C "${targetRepoPath}" commit -m "Initial commit"`); (0, child_process_1.execSync)(`git -C "${targetRepoPath}" push origin main --quiet`); (0, child_process_1.execSync)(`git -C "${targetRepoPath}" config core.autocrlf input`); console.log("> Initial commit created and pushed on main."); } // Get dates already present in git history (avoid duplicates) let existingCommitDates = []; try { const gitLogCmd = `git -C "${targetRepoPath}" log --pretty=format:%ad --date=iso8601-strict`; const stdout = (0, child_process_1.execSync)(gitLogCmd).toString(); existingCommitDates = stdout.split("\n").filter(Boolean); } catch (e) { } // The counter must reflect the number of existing commits to avoid increment errors let counter = existingCommitDates.length; // Filter dates to commit (only new ones) let newCommits = 0; const toCommit = Array.from(allDates) .sort() .filter((date) => !existingCommitDates.includes(String(date).trim().replace(/\r|\n/g, ""))); const totalToCommit = toCommit.length; if (allDates.size > 0) { console.log(`📦 ${totalToCommit} commit(s) to create (not yet in history) out of ${allDates.size} extracted`); console.log(); // Display a QR code to GitHub (small format) const qrAscii = yield qrcode_1.default.toString("https://coff.ee/nicolastardieu", { type: "terminal", small: true, }); console.log("If GitVeil has been valuable to you, please consider supporting its continued development"); console.log("Thank you for trusting GitVeil. Support the project here: https://coff.ee/nicolastardieu"); console.log(); console.log(qrAscii); console.log(); } if (totalToCommit === 0) { // Nothing to do, clean up and exit console.log(); console.log("Everything is up to date, nothing to push."); (0, fileUtils_1.clearRecordsFolder)(logsDir); return; } const gitCmdBase = `git -C "${targetRepoPath}"`; let oldUserName = ""; let oldUserEmail = ""; try { oldUserName = (0, child_process_1.execSync)(`${gitCmdBase} config user.name`).toString().trim(); } catch (_d) { } try { oldUserEmail = (0, child_process_1.execSync)(`${gitCmdBase} config user.email`) .toString() .trim(); } catch (_e) { } // Synchronous loop for commits const lastCommitIndex = toCommit.length - 1; const commitCmds = []; for (const [idx, dateRaw] of toCommit.entries()) { const date = String(dateRaw).trim().replace(/\r|\n/g, ""); if (existingCommitDates.includes(date)) { continue; } counter++; newCommits++; const commitEnv = Object.assign(Object.assign({}, process.env), { GIT_COMMITTER_NAME: userName, GIT_COMMITTER_EMAIL: userEmail }); if (idx !== lastCommitIndex) { commitCmds.push({ cmd: `${gitCmdBase} commit --allow-empty --author="${userName} <${userEmail}>" -m "Commit #${counter} for ${date}" --date="${date}"`, env: commitEnv, isLast: false, date, idx, }); } else { // Last commit: modify README.md const content = [`Counter: ${counter}`, ""].join("\n"); fs.writeFileSync(readmePath, content); commitCmds.push({ cmd: `${gitCmdBase} add -A`, env: commitEnv, isLast: false, date, idx, }); commitCmds.push({ cmd: `${gitCmdBase} status --porcelain`, env: commitEnv, isLast: true, date, idx, }); } } for (const commit of commitCmds) { if (commit.isLast) { const stdout = (0, child_process_1.execSync)(commit.cmd, { env: commit.env }).toString(); if (!stdout.trim()) continue; const lastCommitCmd = `${gitCmdBase} commit --author="${userName} <${userEmail}>" -m "Commit #${counter} for ${commit.date}" --date="${commit.date}"`; (0, child_process_1.execSync)(lastCommitCmd, { env: commit.env }); } else { (0, child_process_1.execSync)(commit.cmd, { env: commit.env }); } process.stdout.write(`\r> Commit ${commit.idx + 1}/${totalToCommit} (${Math.round(((commit.idx + 1) / totalToCommit) * 100)}%)`); } // Restore original local git config try { if (oldUserName) (0, child_process_1.execSync)(`${gitCmdBase} config user.name "${oldUserName}"`); if (oldUserEmail) (0, child_process_1.execSync)(`${gitCmdBase} config user.email "${oldUserEmail}"`); } catch (_f) { } process.stdout.write("\n"); // Automatic push to remote repository (--quiet option to speed up) try { console.log(); console.log("> Pushing commits to remote repository... 🚀"); (0, child_process_1.execSync)(`${gitCmdBase} push origin main --quiet`); console.log("> Push commits to remote completed ✅"); // Clean up temporary files after push (0, fileUtils_1.clearRecordsFolder)(logsDir); } catch (err) { console.error(`❌ Git push failed: ${err.message}`); } }); } exports.pushCommits = pushCommits;