UNPKG

git-fresh

Version:

Quickly reset your Git working directory to a clean state without re-cloning. Stashes, wipes, restores, and pops.

325 lines • 12.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.gitFresh = gitFresh; const chalk_1 = __importDefault(require("chalk")); const child_process_1 = require("child_process"); const fs_1 = require("fs"); const glob_1 = require("glob"); const inquirer_1 = __importDefault(require("inquirer")); const ora_1 = __importDefault(require("ora")); const path_1 = require("path"); /** * Checks if the current directory is a Git repository */ function isGitRepository() { return (0, fs_1.existsSync)('.git'); } /** * Executes a git command and returns the output */ function executeGitCommand(command) { try { return (0, child_process_1.execSync)(`git ${command}`, { encoding: 'utf-8', stdio: 'pipe', windowsHide: true, // Hide console window on Windows maxBuffer: 1024 * 1024 // 1MB buffer for large outputs }); } catch (error) { throw new Error(`Git command failed: ${command}`); } } /** * Checks if there are any changes to stash */ function hasChangesToStash() { try { const status = executeGitCommand('status --porcelain'); return status.trim().length > 0; } catch { return false; } } /** * Finds environment files matching various patterns */ async function findEnvFiles() { const patterns = [ '.env', '.env.*', '*.env', '.*.env', '**/.env', '**/.env.*', '**/.*env', '**/*.env' ]; const envFiles = new Set(); for (const pattern of patterns) { try { const matches = await (0, glob_1.glob)(pattern, { dot: true, ignore: ['node_modules/**', '.git/**'] }); matches.forEach(file => envFiles.add(file)); } catch (error) { // Ignore glob errors for individual patterns } } return Array.from(envFiles).sort(); } /** * Prompts user to select which env files to ignore */ async function selectEnvFilesToIgnore(envFiles) { if (envFiles.length === 0) { return []; } console.log(chalk_1.default.yellow('\nšŸ”’ Environment files detected:')); envFiles.forEach((file) => { console.log(chalk_1.default.gray(` ${file}`)); }); const { action } = await inquirer_1.default.prompt([ { type: 'list', name: 'action', message: 'What would you like to do with these environment files?', choices: [ { name: 'Ignore all (recommended)', value: 'all' }, { name: 'Select individually', value: 'select' }, { name: 'Ignore none (remove all)', value: 'none' } ], default: 'all' } ]); if (action === 'all') { return envFiles; } else if (action === 'none') { return []; } else { const { selectedFiles } = await inquirer_1.default.prompt([ { type: 'checkbox', name: 'selectedFiles', message: 'Select files to ignore (keep safe):', choices: envFiles.map(file => ({ name: file, value: file, checked: true })), } ]); return selectedFiles; } } /** * Removes all files and directories except .git and ignored files */ function removeAllExceptGitAndIgnored(ignoredFiles = []) { const items = (0, fs_1.readdirSync)('.'); // Normalize all ignored file paths to use consistent separators across platforms const normalizedIgnoredFiles = ignoredFiles.map(file => { // Use posix.normalize to ensure forward slashes for consistent comparison return path_1.posix.normalize(file.replace(/\\/g, '/')); }); const ignoredSet = new Set(normalizedIgnoredFiles); // Function to normalize path to use forward slashes for consistent comparison function normalizePath(path) { // Convert to forward slashes and normalize return path_1.posix.normalize(path.replace(/\\/g, '/')); } // Function to join paths in a cross-platform way but normalize for comparison function joinAndNormalize(...paths) { return normalizePath((0, path_1.join)(...paths)); } // Function to check if a path should be preserved function shouldPreservePath(itemPath) { const normalizedItemPath = normalizePath(itemPath); // Always preserve .git if (normalizedItemPath === '.git') { return true; } // Check exact match with ignored files if (ignoredSet.has(normalizedItemPath)) { return true; } // Check if any ignored file is inside this directory for (const ignoredFile of normalizedIgnoredFiles) { if (ignoredFile.startsWith(normalizedItemPath + '/')) { return true; } } return false; } // Function to recursively process directories function processDirectory(dirPath) { try { const dirItems = (0, fs_1.readdirSync)(dirPath); for (const item of dirItems) { // Use join for proper path construction, then normalize for comparison const itemPath = dirPath === '.' ? item : (0, path_1.join)(dirPath, item); const normalizedItemPath = normalizePath(itemPath); if (shouldPreservePath(itemPath)) { continue; // Skip this item as it should be preserved } try { const stats = (0, fs_1.statSync)(itemPath); if (stats.isDirectory()) { // Check if this directory contains any files we need to preserve let hasPreservedContent = false; for (const ignoredFile of normalizedIgnoredFiles) { if (ignoredFile.startsWith(normalizedItemPath + '/')) { hasPreservedContent = true; break; } } if (hasPreservedContent) { // Process the directory contents recursively processDirectory(itemPath); } else { // Remove the entire directory (0, fs_1.rmSync)(itemPath, { recursive: true, force: true }); } } else { // Remove the file (0, fs_1.rmSync)(itemPath, { force: true }); } } catch (error) { console.warn(chalk_1.default.yellow(`Warning: Could not remove ${itemPath}`)); } } } catch (error) { console.warn(chalk_1.default.yellow(`Warning: Could not read directory ${dirPath}`)); } } // Start processing from the root directory processDirectory('.'); } /** * Finds files matching the specified glob pattern */ async function findGlobFiles(pattern) { try { const matches = await (0, glob_1.glob)(pattern, { dot: true, ignore: ['node_modules/**', '.git/**'] }); return matches.sort(); } catch (error) { console.warn(chalk_1.default.yellow(`Warning: Could not process glob pattern "${pattern}"`)); return []; } } /** * Main function that performs the git fresh operation */ async function gitFresh(options = {}) { console.log(chalk_1.default.blue.bold('šŸš€ Git Fresh - Resetting working directory\n')); // Check if we're in a git repository if (!isGitRepository()) { throw new Error('This is not a Git repository. Please run this command in a Git repository.'); } // Platform-specific checks and adjustments const platform = process.platform; const isWindows = platform === 'win32'; const isUnix = platform === 'linux' || platform === 'darwin'; // Log platform info for debugging (only in verbose mode if needed) // console.log(chalk.gray(`Platform: ${platform} | Windows: ${isWindows} | Unix: ${isUnix}`)); let filesToIgnore = []; // Handle glob pattern files if provided if (options.ignoreGlobFiles) { const globFiles = await findGlobFiles(options.ignoreGlobFiles); if (globFiles.length > 0) { filesToIgnore.push(...globFiles); console.log(chalk_1.default.green(`šŸ”’ Protecting ${globFiles.length} file(s) matching pattern "${options.ignoreGlobFiles}":`)); globFiles.forEach(file => { console.log(chalk_1.default.gray(` āœ“ ${file}`)); }); } else { console.log(chalk_1.default.gray(`ℹ No files found matching pattern "${options.ignoreGlobFiles}"`)); } } // Handle environment files if requested if (options.ignoreEnvFiles) { const envFiles = await findEnvFiles(); if (envFiles.length > 0) { if (options.skipConfirmation) { filesToIgnore.push(...envFiles); console.log(chalk_1.default.green(`šŸ”’ Protecting ${envFiles.length} environment file(s) from removal`)); envFiles.forEach(file => { console.log(chalk_1.default.gray(` āœ“ ${file}`)); }); } else { const selectedEnvFiles = await selectEnvFilesToIgnore(envFiles); filesToIgnore.push(...selectedEnvFiles); if (selectedEnvFiles.length > 0) { console.log(chalk_1.default.green(`\nšŸ”’ Will protect ${selectedEnvFiles.length} environment file(s):`)); selectedEnvFiles.forEach(file => { console.log(chalk_1.default.gray(` āœ“ ${file}`)); }); } } } else { console.log(chalk_1.default.gray('ℹ No environment files found')); } } let stashCreated = false; try { // Step 1: Stash changes if any exist if (hasChangesToStash()) { const stashSpinner = (0, ora_1.default)('Stashing current changes...').start(); try { executeGitCommand('stash push -u -m "git-fresh: temporary stash"'); stashSpinner.succeed(chalk_1.default.green('āœ“ Changes stashed successfully')); stashCreated = true; } catch (error) { stashSpinner.fail(chalk_1.default.red('āœ— Failed to stash changes')); throw new Error('Cannot proceed: Failed to stash changes. Your files are safe, but git-fresh cannot continue without successfully stashing changes.'); } } else { console.log(chalk_1.default.gray('ℹ No changes to stash')); } // Step 2: Remove all files except .git and ignored files const removeSpinner = (0, ora_1.default)('Removing files except .git and protected files...').start(); removeAllExceptGitAndIgnored(filesToIgnore); removeSpinner.succeed(chalk_1.default.green('āœ“ Files removed successfully')); // Step 3: Restore all files from git const restoreSpinner = (0, ora_1.default)('Restoring files from Git...').start(); executeGitCommand('restore .'); restoreSpinner.succeed(chalk_1.default.green('āœ“ Files restored successfully')); // Step 4: Pop the stash if we created one if (stashCreated) { const popSpinner = (0, ora_1.default)('Applying stashed changes...').start(); try { executeGitCommand('stash pop'); popSpinner.succeed(chalk_1.default.green('āœ“ Stashed changes applied successfully')); } catch (error) { popSpinner.warn(chalk_1.default.yellow('⚠ Could not apply stashed changes (conflicts may exist)')); console.log(chalk_1.default.yellow('Run "git stash list" to see your stashed changes')); } } console.log(chalk_1.default.green.bold('\nšŸŽ‰ Git working directory reset successfully!')); if (filesToIgnore.length > 0) { console.log(chalk_1.default.cyan(`\nšŸ”’ Protected files were preserved and remain untouched.`)); } } catch (error) { throw new Error(`Failed to reset Git working directory: ${error instanceof Error ? error.message : String(error)}`); } } //# sourceMappingURL=index.js.map