envx-cli
Version:
Environment file encryption and management tool
302 lines • 14.2 kB
JavaScript
;
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 &&
!rawOptions.overwrite) {
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