UNPKG

envx-cli

Version:

Environment file encryption and management tool

278 lines 13 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createEncryptCommand = 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 createEncryptCommand = () => { const command = new commander_1.Command('encrypt'); command .description('Encrypt environment files') .option('-e, --environment <env>', 'Environment name (e.g., development, staging, production)') .option('-p, --passphrase <passphrase>', 'Passphrase for encryption') .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') .action(async (options) => { try { await executeEncrypt(options); } catch (error) { exec_1.CliUtils.error(`Encryption failed: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } }); return command; }; exports.createEncryptCommand = createEncryptCommand; async function executeEncrypt(rawOptions) { exec_1.CliUtils.header('Environment File Encryption'); 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 encryption 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 encrypt:'); } } 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 encrypted ${totalSuccess} file(s) across all environments`); } if (totalErrors > 0) { exec_1.CliUtils.error(`Total: Failed to encrypt ${totalErrors} file(s) across all environments`); } if (totalSuccess > 0) { console.log(); exec_1.CliUtils.info('Next steps:'); console.log(chalk_1.default.gray('• Add *.gpg files to your version control')); console.log(chalk_1.default.gray('• Consider adding .env.* files to .gitignore')); console.log(chalk_1.default.gray('• Use "envx decrypt" to decrypt files when needed')); } 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 encryption passphrase for ${exec_1.CliUtils.formatEnvironment(environment)}:` : 'Enter encryption passphrase:'; passphrase = await interactive_1.InteractiveUtils.promptPassphrase(promptMessage); } } if (!isPartOfAll) { (0, schemas_1.validateEncryptOptions)({ 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 unencryptedFiles = envFiles.filter(file => !file.encrypted && file.exists); if (unencryptedFiles.length === 0) { exec_1.CliUtils.warning(`No unencrypted .env.${environment} files found to encrypt.`); return { successCount: 0, errorCount: 0 }; } if (!isPartOfAll) { exec_1.CliUtils.info(`Found ${unencryptedFiles.length} file(s) to encrypt:`); unencryptedFiles.forEach(file => { console.log(` • ${exec_1.CliUtils.formatPath(file.path, cwd)}`); }); } else { exec_1.CliUtils.info(`Found ${unencryptedFiles.length} file(s) to encrypt`); } let filesToProcess = unencryptedFiles; if (rawOptions.interactive && unencryptedFiles.length > 1 && !isPartOfAll) { const selectedPaths = await interactive_1.InteractiveUtils.selectFiles(unencryptedFiles.map(f => file_1.FileUtils.getRelativePath(f.path, cwd)), 'Select files to encrypt:'); filesToProcess = unencryptedFiles.filter(file => selectedPaths.includes(file_1.FileUtils.getRelativePath(file.path, cwd))); } if (filesToProcess.length === 0) { exec_1.CliUtils.info('No files selected for encryption.'); return { successCount: 0, errorCount: 0 }; } if (!rawOptions.interactive && !isPartOfAll) { const confirm = await interactive_1.InteractiveUtils.confirmOperation(`Encrypt ${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('Encrypting Files'); } let successCount = 0; let errorCount = 0; for (const envFile of filesToProcess) { const relativePath = file_1.FileUtils.getRelativePath(envFile.path, cwd); console.log(); exec_1.CliUtils.info(`Processing: ${chalk_1.default.cyan(relativePath)}`); try { const encryptedPath = file_1.FileUtils.getEncryptedPath(envFile.path); const encryptedExists = await file_1.FileUtils.fileExists(encryptedPath); if (encryptedExists) { const tempDecryptedPath = `${envFile.path}.temp.${Date.now()}`; try { const decryptResult = exec_1.ExecUtils.decryptFile(encryptedPath, tempDecryptedPath, passphrase); if (decryptResult.success) { const filesIdentical = await file_1.FileUtils.filesAreIdentical(envFile.path, tempDecryptedPath); if (await file_1.FileUtils.fileExists(tempDecryptedPath)) { exec_1.ExecUtils.removeFile(tempDecryptedPath); } if (filesIdentical) { exec_1.CliUtils.success('File already encrypted with same content - skipping'); successCount++; continue; } else { exec_1.CliUtils.warning('File has changes - updating encrypted version'); } } else { exec_1.CliUtils.warning('Could not decrypt existing file - creating new encrypted version'); } } catch { if (await file_1.FileUtils.fileExists(tempDecryptedPath)) { exec_1.ExecUtils.removeFile(tempDecryptedPath); } exec_1.CliUtils.warning('Error comparing with existing encrypted file - proceeding with encryption'); } } const encryptResult = exec_1.ExecUtils.encryptFile(envFile.path, passphrase); if (encryptResult.success) { exec_1.CliUtils.success(`Encrypted: ${chalk_1.default.cyan(relativePath)}`); exec_1.CliUtils.info(`Created: ${chalk_1.default.cyan(`${relativePath}.gpg`)}`); successCount++; } else { exec_1.CliUtils.error(`Failed to encrypt: ${encryptResult.message}`); if (encryptResult.errors) { encryptResult.errors.forEach(error => { console.log(chalk_1.default.red(` • ${error}`)); }); } errorCount++; } } catch (error) { exec_1.CliUtils.error(`Error processing ${relativePath}: ${error instanceof Error ? error.message : String(error)}`); errorCount++; } } if (!isPartOfAll) { console.log(); exec_1.CliUtils.subheader('Encryption Summary'); if (successCount > 0) { exec_1.CliUtils.success(`Successfully encrypted ${successCount} file(s)`); } if (errorCount > 0) { exec_1.CliUtils.error(`Failed to encrypt ${errorCount} file(s)`); } if (successCount > 0) { console.log(); exec_1.CliUtils.info('Next steps:'); console.log(chalk_1.default.gray('• Add *.gpg files to your version control')); console.log(chalk_1.default.gray('• Consider adding .env.* files to .gitignore')); console.log(chalk_1.default.gray('• Use "envx decrypt" to decrypt files when needed')); } if (errorCount > 0) { process.exit(1); } } return { successCount, errorCount }; } //# sourceMappingURL=encrypt.js.map