UNPKG

envx-cli

Version:

Environment file encryption and management tool

299 lines 14.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createDecryptCommand = void 0; const chalk_1 = __importDefault(require("chalk")); const commander_1 = require("commander"); const schemas_1 = require("../schemas"); const exec_1 = require("../utils/exec"); const file_1 = require("../utils/file"); const interactive_1 = require("../utils/interactive"); const createDecryptCommand = () => { const command = new commander_1.Command('decrypt'); command .description('Decrypt environment files') .option('-e, --environment <env>', 'Environment name (e.g., development, staging, production)') .option('-p, --passphrase <passphrase>', 'Passphrase for decryption') .option('-s, --secret <secret>', 'Secret key from environment variable') .option('-c, --cwd <path>', 'Working directory path') .option('-i, --interactive', 'Interactive mode for selecting files') .option('-a, --all', 'Process all available environments') .option('--overwrite', 'Overwrite existing decrypted files without confirmation') .action(async (options) => { try { await executeDecrypt(options); } catch (error) { exec_1.CliUtils.error(`Decryption failed: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } }); return command; }; exports.createDecryptCommand = createDecryptCommand; async function executeDecrypt(rawOptions) { exec_1.CliUtils.header('Environment File Decryption'); if (rawOptions.all) { if (rawOptions.environment) { throw new Error('Cannot use --all with --environment flag'); } if (rawOptions.interactive) { throw new Error('Interactive mode is not compatible with --all flag'); } } const cwd = rawOptions.cwd || exec_1.ExecUtils.getCurrentDir(); if (!exec_1.ExecUtils.isGpgAvailable()) { exec_1.CliUtils.error('GPG is not available. Please install GPG to use decryption features.'); interactive_1.InteractiveUtils.displayPrerequisites(); process.exit(1); } const availableEnvironments = await file_1.FileUtils.findAllEnvironments(cwd); if (availableEnvironments.length === 0) { exec_1.CliUtils.warning('No environment files found in the current directory.'); exec_1.CliUtils.info('Use the "create" command to create environment files first.'); return; } if (rawOptions.all) { await processAllEnvironments(rawOptions, availableEnvironments, cwd); return; } let environment = rawOptions.environment || ''; if (!environment) { if (availableEnvironments.length === 1) { environment = availableEnvironments[0]; exec_1.CliUtils.info(`Using environment: ${exec_1.CliUtils.formatEnvironment(environment)}`); } else { environment = await interactive_1.InteractiveUtils.selectEnvironment(availableEnvironments, 'Select environment to decrypt:'); } } if (!availableEnvironments.includes(environment)) { throw new Error(`Environment '${environment}' not found. Available: ${availableEnvironments.join(', ')}`); } await processSingleEnvironment(rawOptions, environment, cwd); } async function processAllEnvironments(rawOptions, environments, cwd) { exec_1.CliUtils.info(`Processing ${environments.length} environment(s): ${environments.map(env => exec_1.CliUtils.formatEnvironment(env)).join(', ')}`); let totalSuccess = 0; let totalErrors = 0; const results = []; for (const environment of environments) { console.log(); exec_1.CliUtils.subheader(`Processing Environment: ${exec_1.CliUtils.formatEnvironment(environment)}`); try { const result = await processSingleEnvironment(rawOptions, environment, cwd, true); results.push({ environment, success: result.successCount, errors: result.errorCount, }); totalSuccess += result.successCount; totalErrors += result.errorCount; } catch (error) { exec_1.CliUtils.error(`Failed to process environment '${environment}': ${error instanceof Error ? error.message : String(error)}`); results.push({ environment, success: 0, errors: 1 }); totalErrors++; } } console.log(); exec_1.CliUtils.header('Overall Summary'); results.forEach(result => { if (result.success > 0 || result.errors > 0) { console.log(`${exec_1.CliUtils.formatEnvironment(result.environment)}: ${chalk_1.default.green(`${result.success} success`)} | ${chalk_1.default.red(`${result.errors} errors`)}`); } }); console.log(); if (totalSuccess > 0) { exec_1.CliUtils.success(`Total: Successfully decrypted ${totalSuccess} file(s) across all environments`); } if (totalErrors > 0) { exec_1.CliUtils.error(`Total: Failed to decrypt ${totalErrors} file(s) across all environments`); } if (totalSuccess > 0) { console.log(); exec_1.CliUtils.info('Next steps:'); console.log(chalk_1.default.gray('• Review the decrypted environment files')); console.log(chalk_1.default.gray('• Remember: decrypted files contain sensitive data')); console.log(chalk_1.default.gray('• Consider re-encrypting files when done: envx encrypt')); console.log(chalk_1.default.gray('• Never commit decrypted .env files to version control')); } if (totalSuccess > 0) { console.log(); exec_1.CliUtils.warning('Security Notice:'); console.log(chalk_1.default.yellow('Decrypted files contain sensitive information.')); console.log(chalk_1.default.yellow('Ensure they are not accidentally committed to version control.')); } if (totalErrors > 0) { process.exit(1); } } async function processSingleEnvironment(rawOptions, environment, cwd, isPartOfAll = false) { let passphrase = rawOptions.passphrase || ''; if (!passphrase || passphrase.trim() === '') { const envrcConfig = await file_1.FileUtils.readEnvrc(cwd); const secretVar = file_1.FileUtils.generateSecretVariableName(environment); if (rawOptions.secret && envrcConfig[rawOptions.secret]) { passphrase = envrcConfig[rawOptions.secret]; exec_1.CliUtils.info(`Using secret from .envrc: ${chalk_1.default.cyan(rawOptions.secret)}`); } else if (envrcConfig[secretVar]) { passphrase = envrcConfig[secretVar]; exec_1.CliUtils.info(`Using secret from .envrc: ${chalk_1.default.cyan(secretVar)}`); } else { const promptMessage = isPartOfAll ? `Enter decryption passphrase for ${exec_1.CliUtils.formatEnvironment(environment)}:` : 'Enter decryption passphrase:'; passphrase = await interactive_1.InteractiveUtils.promptPassphrase(promptMessage); } } if (!isPartOfAll) { (0, schemas_1.validateDecryptOptions)({ environment, passphrase, cwd, secret: rawOptions.secret, }); } if (!isPartOfAll) { exec_1.CliUtils.info('Testing GPG operation...'); } const gpgTest = exec_1.ExecUtils.testGpgOperation(passphrase); if (!gpgTest.success) { throw new Error(`GPG test failed: ${gpgTest.message}`); } if (!isPartOfAll) { exec_1.CliUtils.success('GPG test passed'); } const envFiles = await file_1.FileUtils.findEnvFiles(environment, cwd); const encryptedFiles = envFiles.filter(file => file.encrypted && file.exists); if (encryptedFiles.length === 0) { exec_1.CliUtils.warning(`No encrypted .env.${environment}.gpg files found to decrypt.`); if (!isPartOfAll) { exec_1.CliUtils.info('Use the "encrypt" command to encrypt environment files first.'); } return { successCount: 0, errorCount: 0 }; } if (!isPartOfAll) { exec_1.CliUtils.info(`Found ${encryptedFiles.length} encrypted file(s) to decrypt:`); encryptedFiles.forEach(file => { const encryptedPath = file_1.FileUtils.getEncryptedPath(file.path); console.log(` • ${exec_1.CliUtils.formatPath(encryptedPath, cwd)}`); }); } else { exec_1.CliUtils.info(`Found ${encryptedFiles.length} encrypted file(s) to decrypt`); } let filesToProcess = encryptedFiles; if (rawOptions.interactive && encryptedFiles.length > 1 && !isPartOfAll) { const selectedPaths = await interactive_1.InteractiveUtils.selectFiles(encryptedFiles.map(f => file_1.FileUtils.getRelativePath(file_1.FileUtils.getEncryptedPath(f.path), cwd)), 'Select files to decrypt:'); filesToProcess = encryptedFiles.filter(file => { const encryptedPath = file_1.FileUtils.getEncryptedPath(file.path); return selectedPaths.includes(file_1.FileUtils.getRelativePath(encryptedPath, cwd)); }); } if (filesToProcess.length === 0) { exec_1.CliUtils.info('No files selected for decryption.'); return { successCount: 0, errorCount: 0 }; } const conflictFiles = []; for (const envFile of filesToProcess) { if (await file_1.FileUtils.fileExists(envFile.path)) { conflictFiles.push(envFile.path); } } if (conflictFiles.length > 0 && !rawOptions.overwrite && !isPartOfAll) { exec_1.CliUtils.warning(`The following files already exist and will be overwritten:`); conflictFiles.forEach(file => { console.log(` • ${exec_1.CliUtils.formatPath(file, cwd)}`); }); const confirm = await interactive_1.InteractiveUtils.confirmOperation('Continue and overwrite existing files?', false); if (!confirm) { exec_1.CliUtils.info('Operation cancelled.'); return { successCount: 0, errorCount: 0 }; } } if (!rawOptions.interactive && conflictFiles.length === 0 && !isPartOfAll) { const confirm = await interactive_1.InteractiveUtils.confirmOperation(`Decrypt ${filesToProcess.length} file(s) for ${exec_1.CliUtils.formatEnvironment(environment)}?`); if (!confirm) { exec_1.CliUtils.info('Operation cancelled.'); return { successCount: 0, errorCount: 0 }; } } if (!isPartOfAll) { exec_1.CliUtils.subheader('Decrypting Files'); } let successCount = 0; let errorCount = 0; for (const envFile of filesToProcess) { const encryptedPath = file_1.FileUtils.getEncryptedPath(envFile.path); const relativePath = file_1.FileUtils.getRelativePath(envFile.path, cwd); const relativeEncryptedPath = file_1.FileUtils.getRelativePath(encryptedPath, cwd); console.log(); exec_1.CliUtils.info(`Processing: ${chalk_1.default.cyan(relativeEncryptedPath)}`); try { let backupPath = null; if (await file_1.FileUtils.fileExists(envFile.path)) { backupPath = await file_1.FileUtils.createBackup(envFile.path); exec_1.CliUtils.info(`Created backup: ${chalk_1.default.gray(file_1.FileUtils.getRelativePath(backupPath, cwd))}`); } const decryptResult = exec_1.ExecUtils.decryptFile(encryptedPath, envFile.path, passphrase); if (decryptResult.success) { exec_1.CliUtils.success(`Decrypted: ${chalk_1.default.cyan(relativePath)}`); if (backupPath) { await file_1.FileUtils.removeBackup(backupPath); } successCount++; } else { if (backupPath) { exec_1.ExecUtils.moveFile(backupPath, envFile.path); exec_1.CliUtils.info('Restored original file from backup'); } exec_1.CliUtils.error(`Failed to decrypt: ${decryptResult.message}`); if (decryptResult.errors) { decryptResult.errors.forEach(error => { console.log(chalk_1.default.red(` • ${error}`)); }); } errorCount++; } } catch (error) { exec_1.CliUtils.error(`Error processing ${relativeEncryptedPath}: ${error instanceof Error ? error.message : String(error)}`); errorCount++; } } if (!isPartOfAll) { console.log(); exec_1.CliUtils.subheader('Decryption Summary'); if (successCount > 0) { exec_1.CliUtils.success(`Successfully decrypted ${successCount} file(s)`); } if (errorCount > 0) { exec_1.CliUtils.error(`Failed to decrypt ${errorCount} file(s)`); } if (successCount > 0) { console.log(); exec_1.CliUtils.info('Next steps:'); console.log(chalk_1.default.gray('• Review the decrypted environment files')); console.log(chalk_1.default.gray('• Remember: decrypted files contain sensitive data')); console.log(chalk_1.default.gray('• Consider re-encrypting files when done: envx encrypt')); console.log(chalk_1.default.gray('• Never commit decrypted .env files to version control')); } if (successCount > 0) { console.log(); exec_1.CliUtils.warning('Security Notice:'); console.log(chalk_1.default.yellow('Decrypted files contain sensitive information.')); console.log(chalk_1.default.yellow('Ensure they are not accidentally committed to version control.')); } if (errorCount > 0) { process.exit(1); } } return { successCount, errorCount }; } //# sourceMappingURL=decrypt.js.map