UNPKG

envx-cli

Version:

Environment file encryption and management tool

266 lines 10.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.FileUtils = void 0; const crypto_1 = __importDefault(require("crypto")); const fast_glob_1 = __importDefault(require("fast-glob")); const fs_extra_1 = __importDefault(require("fs-extra")); const lodash_1 = require("lodash"); const path_1 = __importDefault(require("path")); class FileUtils { static async findEnvFiles(environment, cwd) { const pattern = `**/.env.${environment}`; const encryptedPattern = `**/.env.${environment}.gpg`; const [envFiles, encryptedFiles] = await Promise.all([ (0, fast_glob_1.default)(pattern, { cwd, dot: true }), (0, fast_glob_1.default)(encryptedPattern, { cwd, dot: true }), ]); const results = []; for (const filePath of envFiles) { results.push({ path: path_1.default.join(cwd, filePath), stage: environment, encrypted: false, exists: true, }); } for (const filePath of encryptedFiles) { const decryptedPath = (0, lodash_1.replace)(filePath, '.gpg', ''); results.push({ path: path_1.default.join(cwd, decryptedPath), stage: environment, encrypted: true, exists: true, }); } return results; } static async findAllEnvironments(cwd) { const pattern = '**/.env.*'; const files = await (0, fast_glob_1.default)(pattern, { cwd, dot: true }); const environments = new Set(); for (const file of files) { const basename = path_1.default.basename(file); const match = basename.match(/^\.env\.([^.]+)(\.gpg)?$/); if (match && match[1]) { environments.add(match[1]); } } return Array.from(environments).sort(); } static async fileExists(filePath) { try { const stat = await fs_extra_1.default.stat(filePath); return stat.isFile(); } catch { return false; } } static async createBackup(filePath) { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 8); const backupPath = `${filePath}.backup.${timestamp}.${random}`; await fs_extra_1.default.copy(filePath, backupPath); return backupPath; } static async removeBackup(backupPath) { if (await this.fileExists(backupPath)) { await fs_extra_1.default.remove(backupPath); } } static async getFileHash(filePath) { const fileBuffer = await fs_extra_1.default.readFile(filePath); return crypto_1.default.createHash('md5').update(fileBuffer).digest('hex'); } static async filesAreIdentical(file1, file2) { if (!(await this.fileExists(file1)) || !(await this.fileExists(file2))) { return false; } const [hash1, hash2] = await Promise.all([ this.getFileHash(file1), this.getFileHash(file2), ]); return hash1 === hash2; } static async readEnvrc(cwd) { const envrcPath = path_1.default.join(cwd, '.envrc'); if (!(await this.fileExists(envrcPath))) { return {}; } try { const content = await fs_extra_1.default.readFile(envrcPath, 'utf-8'); const config = {}; const lines = content.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('export ')) { const exportStatement = trimmed.substring(7); const [key, ...valueParts] = exportStatement.split('='); if (key && valueParts.length > 0) { let value = valueParts.join('='); value = value.replace(/^["']|["']$/g, ''); config[key.trim()] = value; } } } return config; } catch (error) { console.warn(`Warning: Could not read .envrc file: ${error}`); return {}; } } static async writeEnvrc(cwd, config) { const envrcPath = path_1.default.join(cwd, '.envrc'); try { const lines = ['# Environment secrets generated by envx', '']; for (const [key, value] of Object.entries(config)) { lines.push(`export ${key}="${value}"`); } lines.push(''); await fs_extra_1.default.writeFile(envrcPath, lines.join('\n'), 'utf-8'); return { success: true, message: 'Successfully wrote .envrc file', filePath: envrcPath, }; } catch (error) { return { success: false, message: `Failed to write .envrc file: ${error}`, filePath: envrcPath, error: error, }; } } static async createEnvTemplate(filePath, template) { try { let content = ''; if (template && (await this.fileExists(template))) { content = await fs_extra_1.default.readFile(template, 'utf-8'); } else { content = [ '# Environment variables', '# Add your environment-specific variables here', '', '# Example:', '# DATABASE_URL=', '# API_KEY=', '# DEBUG=false', '', ].join('\n'); } await fs_extra_1.default.writeFile(filePath, content, 'utf-8'); return { success: true, message: `Successfully created .env file`, filePath, }; } catch (error) { return { success: false, message: `Failed to create .env file: ${error}`, filePath, error: error, }; } } static generateRandomSecret(length = 32) { return crypto_1.default.randomBytes(length).toString('hex'); } static generateSecretVariableName(stage) { return `${stage.toUpperCase()}_SECRET`; } static async ensureDir(dirPath) { await fs_extra_1.default.ensureDir(dirPath); } static getRelativePath(absolutePath, cwd) { return path_1.default.relative(cwd, absolutePath); } static isValidEnvironmentName(name) { return /^[a-zA-Z0-9_-]+$/.test(name); } static getEncryptedPath(filePath) { return `${filePath}.gpg`; } static getDecryptedPath(encryptedPath) { return (0, lodash_1.replace)(encryptedPath, '.gpg', ''); } static isEncryptedFile(filePath) { return filePath.endsWith('.gpg'); } static async getFileStats(filePath) { const stats = await fs_extra_1.default.stat(filePath); return { size: stats.size, mtime: stats.mtime, }; } static async updateGitignore(cwd) { const gitignorePath = path_1.default.join(cwd, '.gitignore'); const envPatterns = ['.env.*', '!.env.example', '!.env.*.gpg']; const secretPatterns = ['.envrc']; try { let existingContent = ''; if (await this.fileExists(gitignorePath)) { existingContent = await fs_extra_1.default.readFile(gitignorePath, 'utf-8'); } const missingEnvPatterns = envPatterns.filter(pattern => !existingContent.includes(pattern)); const missingSecretPatterns = secretPatterns.filter(pattern => !existingContent.includes(pattern)); if (missingEnvPatterns.length === 0 && missingSecretPatterns.length === 0) { return { success: true, message: '.gitignore already contains all EnvX patterns', filePath: gitignorePath, }; } let newContent = existingContent.trim(); const addedSections = []; if (missingEnvPatterns.length > 0) { const envSection = [ '', '# Environment files', ...missingEnvPatterns.map(pattern => pattern === '!.env.*.gpg' ? `${pattern}` : pattern), ]; newContent += (newContent ? '\n' : '') + envSection.join('\n'); addedSections.push('Environment files'); } if (missingSecretPatterns.length > 0) { const secretSection = [ '', '# EnvX secrets', ...missingSecretPatterns.map(pattern => pattern === '.envrc' ? `${pattern}` : pattern), ]; newContent += (newContent ? '\n' : '') + secretSection.join('\n'); addedSections.push('EnvX secrets'); } newContent += '\n'; await fs_extra_1.default.writeFile(gitignorePath, newContent, 'utf-8'); const message = addedSections.length > 0 ? `Successfully updated .gitignore with ${addedSections.join(' and ')} patterns` : 'Successfully updated .gitignore with EnvX patterns'; return { success: true, message, filePath: gitignorePath, }; } catch (error) { return { success: false, message: `Failed to update .gitignore: ${error}`, filePath: gitignorePath, error: error, }; } } } exports.FileUtils = FileUtils; //# sourceMappingURL=file.js.map