UNPKG

envx-cli

Version:

Environment file encryption and management tool

365 lines 14.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InteractiveUtils = void 0; const chalk_1 = __importDefault(require("chalk")); const inquirer_1 = __importDefault(require("inquirer")); const path_1 = __importDefault(require("path")); const exec_1 = require("./exec"); const file_1 = require("./file"); class InteractiveUtils { static async selectEnvironment(environments, message = 'Select environment:') { if (environments.length === 0) { throw new Error('No environments found'); } if (environments.length === 1) { return environments[0]; } const { environment } = await inquirer_1.default.prompt([ { type: 'list', name: 'environment', message, choices: environments, }, ]); return environment; } static async selectMultipleEnvironments(environments, message = 'Select environments:') { if (environments.length === 0) { throw new Error('No environments found'); } const { selectedEnvironments } = await inquirer_1.default.prompt({ type: 'checkbox', name: 'selectedEnvironments', message, choices: environments, validate: (input) => { if (input.length === 0) { return 'Please select at least one environment'; } return true; }, }); return selectedEnvironments; } static async promptPassphrase(message = 'Enter passphrase:') { const { passphrase } = await inquirer_1.default.prompt([ { type: 'password', name: 'passphrase', message, mask: '*', validate: (input) => { if (!input.trim()) { return 'Passphrase cannot be empty'; } if (input.length < 4) { return 'Passphrase must be at least 4 characters long'; } return true; }, }, ]); return passphrase; } static async confirmPassphrase(originalPassphrase) { const { confirmPassphrase } = await inquirer_1.default.prompt([ { type: 'password', name: 'confirmPassphrase', message: 'Confirm passphrase:', mask: '*', validate: (input) => { if (input !== originalPassphrase) { return 'Passphrases do not match'; } return true; }, }, ]); return confirmPassphrase === originalPassphrase; } static async promptNewPassphrase() { const passphrase = await this.promptPassphrase('Enter new passphrase:'); await this.confirmPassphrase(passphrase); return passphrase; } static async promptEnvironmentName(message = 'Enter environment name:') { const { environment } = await inquirer_1.default.prompt([ { type: 'input', name: 'environment', message, validate: (input) => { if (!input.trim()) { return 'Environment name cannot be empty'; } if (!file_1.FileUtils.isValidEnvironmentName(input.trim())) { return 'Environment name can only contain letters, numbers, hyphens, and underscores'; } return true; }, filter: (input) => input.trim().toLowerCase(), }, ]); return environment; } static async promptSecret(stageName) { const { secretOption } = await inquirer_1.default.prompt([ { type: 'list', name: 'secretOption', message: `How would you like to set the secret for ${chalk_1.default.magenta(stageName)}?`, choices: [ { name: 'Generate random secret', value: 'generate' }, { name: 'Enter custom secret', value: 'custom' }, ], }, ]); if (secretOption === 'generate') { const { secretLength } = await inquirer_1.default.prompt([ { type: 'list', name: 'secretLength', message: 'Select secret length:', choices: [ { name: '32 characters (recommended)', value: 32 }, { name: '64 characters (high security)', value: 64 }, { name: '16 characters (basic)', value: 16 }, ], default: 32, }, ]); return file_1.FileUtils.generateRandomSecret(secretLength); } else { const { customSecret } = await inquirer_1.default.prompt([ { type: 'password', name: 'customSecret', message: `Enter secret for ${stageName}:`, mask: '*', validate: (input) => { if (!input.trim()) { return 'Secret cannot be empty'; } if (input.length < 8) { return 'Secret must be at least 8 characters long'; } return true; }, }, ]); return customSecret; } } static async setupEnvrc(cwd, existingEnvironments = []) { exec_1.CliUtils.header('Interactive .envrc Setup'); console.log(chalk_1.default.gray('This will help you set up secrets for your environments.')); console.log(chalk_1.default.gray('The secrets will be stored in a .envrc file.')); console.log(); const envrcPath = path_1.default.join(cwd, '.envrc'); const envrcExists = await file_1.FileUtils.fileExists(envrcPath); if (envrcExists) { exec_1.CliUtils.warning('.envrc file already exists'); const { overwrite } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'overwrite', message: 'Do you want to overwrite the existing .envrc file?', default: false, }, ]); if (!overwrite) { exec_1.CliUtils.info('Keeping existing .envrc file'); return await file_1.FileUtils.readEnvrc(cwd); } } let environments = []; if (existingEnvironments.length > 0) { exec_1.CliUtils.info(`Found existing environments: ${existingEnvironments.map(env => chalk_1.default.magenta(env)).join(', ')}`); const { useExisting } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'useExisting', message: 'Do you want to set up secrets for these existing environments?', default: true, }, ]); if (useExisting) { const selectedEnvs = await this.selectMultipleEnvironments(existingEnvironments, 'Select environments to set up secrets for:'); environments = environments.concat(selectedEnvs); } } const { addMore } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'addMore', message: 'Do you want to add secrets for additional environments?', default: environments.length === 0, }, ]); if (addMore) { let addingMore = true; while (addingMore) { const newEnv = await this.promptEnvironmentName('Enter new environment name:'); if (!environments.includes(newEnv)) { environments.push(newEnv); exec_1.CliUtils.success(`Added ${chalk_1.default.magenta(newEnv)} to the list`); } else { exec_1.CliUtils.warning(`Environment ${chalk_1.default.magenta(newEnv)} already exists in the list`); } const { continueAdding } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'continueAdding', message: 'Do you want to add another environment?', default: false, }, ]); addingMore = continueAdding; } } if (environments.length === 0) { throw new Error('No environments selected for .envrc setup'); } const config = {}; const secrets = []; exec_1.CliUtils.subheader('Setting up secrets'); for (const env of environments.sort()) { console.log(); exec_1.CliUtils.info(`Setting up secret for ${chalk_1.default.magenta(env)}`); const secret = await this.promptSecret(env); const variableName = file_1.FileUtils.generateSecretVariableName(env); config[variableName] = secret; secrets.push({ stage: env, secret, variableName, }); exec_1.CliUtils.success(`Secret configured for ${chalk_1.default.magenta(env)} as ${chalk_1.default.cyan(variableName)}`); } console.log(); exec_1.CliUtils.subheader('Summary'); const tableRows = secrets.map(({ stage, variableName }) => [ chalk_1.default.magenta(stage), chalk_1.default.cyan(variableName), chalk_1.default.green('✓ Set'), ]); exec_1.CliUtils.printTable(['Environment', 'Variable Name', 'Status'], tableRows); const { confirmCreate } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'confirmCreate', message: 'Create .envrc file with these settings?', default: true, }, ]); if (!confirmCreate) { throw new Error('Setup cancelled by user'); } return config; } static async selectFiles(files, message = 'Select files:') { if (files.length === 0) { throw new Error('No files available for selection'); } if (files.length === 1) { const { confirm } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'confirm', message: `Process file ${chalk_1.default.cyan(files[0])}?`, default: true, }, ]); return confirm ? files : []; } const { selectedFiles } = await inquirer_1.default.prompt({ type: 'checkbox', name: 'selectedFiles', message, choices: files.map(file => ({ name: file, value: file, })), validate: (input) => { if (input.length === 0) { return 'Please select at least one file'; } return true; }, }); return selectedFiles; } static async confirmOperation(message, defaultValue = true) { const { confirm } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'confirm', message, default: defaultValue, }, ]); return confirm; } static async selectOperationMode() { const { operation } = await inquirer_1.default.prompt([ { type: 'list', name: 'operation', message: 'What would you like to do?', choices: [ { name: 'Encrypt environment files', value: 'encrypt' }, { name: 'Decrypt environment files', value: 'decrypt' }, { name: 'Create new environment file', value: 'create' }, { name: 'Interactive setup (.envrc)', value: 'interactive' }, ], }, ]); return operation; } static async withProgress(promise, message = 'Processing...') { const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; let i = 0; const interval = setInterval(() => { process.stdout.write(`\r${spinner[i]} ${message}`); i = (i + 1) % spinner.length; }, 100); try { const result = await promise; clearInterval(interval); process.stdout.write(`\r✓ ${message}\n`); return result; } catch (error) { clearInterval(interval); process.stdout.write(`\r✗ ${message}\n`); throw error; } } static displayWelcome() { console.log(); console.log(chalk_1.default.bold.cyan('🔐 Welcome to EnvX')); console.log(chalk_1.default.gray('Environment file encryption and management tool')); console.log(); } static displayPrerequisites() { exec_1.CliUtils.header('Prerequisites'); console.log('Before using EnvX, make sure you have:'); console.log(chalk_1.default.yellow('• GPG installed and configured')); console.log(chalk_1.default.yellow('• Appropriate file permissions in your project directory')); console.log(); console.log('To install GPG:'); console.log(chalk_1.default.cyan('• macOS: brew install gnupg')); console.log(chalk_1.default.cyan('• Ubuntu/Debian: sudo apt-get install gnupg')); console.log(chalk_1.default.cyan('• Windows: Download from https://gnupg.org/download/')); console.log(); } } exports.InteractiveUtils = InteractiveUtils; //# sourceMappingURL=interactive.js.map