envx-cli
Version:
Environment file encryption and management tool
278 lines • 13 kB
JavaScript
;
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