UNPKG

dotenvx-interactive-cli

Version:

An interactive CLI tool for managing .env files with dotenvx - encrypt, decrypt, and manage environment variables with ease.

249 lines (246 loc) 6.96 kB
#!/usr/bin/env node import chalk from 'chalk'; import inquirer from 'inquirer'; import { promises } from 'fs'; import { spawnSync, spawn } from 'child_process'; import { glob } from 'glob'; async function executeCommand(cmd, ...args) { return new Promise((resolve) => { const child = spawn(cmd, args, { shell: true }); let stdout = ""; let stderr = ""; child.stdout?.on("data", (data) => { process.stdout.write(data); stdout += data.toString(); }); child.stderr?.on("data", (data) => { process.stderr.write(data); stderr += data.toString(); }); child.on("close", (code) => { resolve({ stdout, stderr, exitCode: code ?? 0 }); }); }); } async function checkDotenvxInstallation() { try { const cmd = process.platform === "win32" ? "where" : "which"; const result = spawnSync(cmd, ["dotenvx"], { encoding: "utf8" }); return result.status === 0; } catch { return false; } } async function checkEnvKeysFile() { try { await promises.access(".env.keys"); return true; } catch { return false; } } async function findEnvFiles() { const files = await glob(".env*", { ignore: [".env.keys", ".env.keys.json", "*.vault"], nodir: true }); return files.filter( (file) => !file.endsWith(".keys") && !file.endsWith(".vault") ); } async function selectFiles(files, action) { if (files.length === 0) { console.log( chalk.yellow("No .env files found in the current directory.") ); return []; } const { selectedFiles } = await inquirer.prompt({ type: "checkbox", name: "selectedFiles", message: `Select .env files to ${action}: `, // instructions: chalk.dim("(Press Space to select, Enter to confirm)"), instructions: `${chalk.dim("(Press ")} ${chalk.blue( "Space" )} ${chalk.dim("to select, ")}${chalk.blue("Enter")} ${chalk.dim( "to confirm, " )}${chalk.blue("Ctrl/Cmd + C")} ${chalk.dim("to exit)")} `, choices: [ { name: `All files ${chalk.dim("\u2190 Select all .env files")}`, value: "ALL", short: "All files" }, new inquirer.Separator(), ...files.map((file) => ({ name: `${file} ${chalk.dim("\u2190 Press Space to select")}`, value: file, short: file })) ], pageSize: 10, loop: false, prefix: chalk.blue("?"), validate: (answer) => { if (answer.length === 0) { return "Please select at least one file to proceed"; } return true; } }); if (selectedFiles.includes("ALL")) { return files; } return selectedFiles; } async function main() { console.log(chalk.bold("dotenvx-interactive-cli")); if (process.argv.includes("--help") || process.argv.includes("-h")) { console.log(` ${chalk.bold("dotenvx-interactive-cli")} Usage: dotenvx-interactive [options] Options: --help, -h Show this help message `); process.exit(0); } const [isDotenvxInstalled, isEnvKeysFilePresent] = await Promise.all([ checkDotenvxInstallation(), checkEnvKeysFile() ]); if (!isDotenvxInstalled) { console.log(chalk.red("\u274C dotenvx is not installed on your system.")); console.log( chalk.yellow( "Please install it using: npm install -g @dotenvx/dotenvx" ) ); throw new Error("dotenvx not installed"); } if (!isEnvKeysFilePresent) { console.log( chalk.yellow( "No .env.keys file found. Please create one to proceed." ) ); throw new Error(".env.keys file not found"); } console.log(chalk.green("\u{1F44C} dotenvx is installed")); console.log(); const answers = await inquirer.prompt([ { type: "list", name: "action", message: "What would you like to do?", choices: [ { name: "Encrypt .env files", value: "encrypt" }, { name: "Decrypt .env files", value: "decrypt" }, { name: "Install precommit hook", value: "precommit" }, { name: "Exit", value: "exit" } ] } ]); console.log(); switch (answers.action) { case "encrypt": try { const files = await findEnvFiles(); const selectedFiles = await selectFiles(files, "encrypt"); if (selectedFiles.length > 0) { await executeCommand( "dotenvx", "encrypt", "-f", ...selectedFiles ); console.log(chalk.green("\u2713 Files encrypted successfully")); } else { console.log( chalk.yellow("No files selected for encryption") ); } } catch (error) { console.error(chalk.red("Error encrypting files:"), error); throw error; } break; case "decrypt": try { const files = await findEnvFiles(); const selectedFiles = await selectFiles(files, "decrypt"); if (selectedFiles.length > 0) { const execResult = await executeCommand( "dotenvx", "decrypt", "-f", ...selectedFiles ); if (execResult.exitCode === 0) { console.log( chalk.green("\u2713 Files decrypted successfully") ); } else { console.error( chalk.red("\u274C Failed to decrypt files:"), execResult.stderr ); } } else { console.log( chalk.yellow("No files selected for decryption") ); } } catch (error) { console.error(chalk.red("Error decrypting files:"), error); throw error; } break; case "precommit": try { await executeCommand( "dotenvx", "ext", "precommit", "--install" ); console.log( chalk.green("\u{1F44D} Precommit hook installed successfully") ); } catch (error) { console.error( chalk.red("Error installing precommit hook:"), error ); throw error; } break; case "exit": throw new Error("User exited"); } } process.on("SIGINT", () => { console.log("\n\u{1F44B} Exiting gracefully. Until next time!"); process.exit(0); }); process.on("uncaughtException", (error) => { if (error instanceof Error && error.name === "ExitPromptError" || typeof error === "object" && error && error.isTtyError) { console.log("\u{1F44B} Prompt exited. Until next time!"); process.exit(0); } else { console.error(chalk.red("Unexpected error:"), error); process.exit(1); } }); main().catch((err) => { if (err instanceof Error && err.name === "ExitPromptError" || typeof err === "object" && err && err.isTtyError) { console.log("\u{1F44B} Prompt exited. Until next time!"); process.exit(0); } console.error(chalk.red("\u274C An error occurred:"), err); process.exit(1); });