env-twin
Version:
Synchronize your .env.example and .env files effortlessly. env-twin ensures your environment variable templates and actual configurations stay in sync by copying missing keys, highlighting differences, and keeping your project's environment setup consiste
150 lines (147 loc) • 4.91 kB
JavaScript
import fs from 'fs';
import path from 'path';
const CLI_FLAGS = {
SOURCE: ['--source', '--src'],
DEST: ['--dest', '--destination', '--d', '--out', '--target'],
HELP: ['--help', '-h'],
VERSION: ['--version', '-v'],
};
function parseArgs() {
const args = process.argv.slice(2);
const params = {
help: false,
version: false,
};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
const nextArg = args[i + 1];
switch (true) {
case CLI_FLAGS.SOURCE.includes(arg):
if (!nextArg || nextArg.startsWith('-')) {
throw new Error(`Missing value for ${arg} argument`);
}
params.source = nextArg;
i++;
break;
case CLI_FLAGS.DEST.includes(arg):
if (!nextArg || nextArg.startsWith('-')) {
throw new Error(`Missing value for ${arg} argument`);
}
params.dest = nextArg;
i++;
break;
case CLI_FLAGS.HELP.includes(arg):
params.help = true;
break;
case CLI_FLAGS.VERSION.includes(arg):
params.version = true;
break;
default:
if (arg.startsWith('-')) {
throw new Error(`Unknown option '${arg}'`);
}
}
}
return params;
}
// Print usage information
function printUsage() {
console.log(`
Usage: env-twin [options]
Options:
--source, --src Source .env file path (default: .env)
--dest, --destination Destination .env.example file path (default: .env.example)
--help, -h Display this help message
--version, -v Display version information
Example:
env-twin --src .env.development --destination .env.dev.example
`);
}
// Get package version from package.json
function getVersion() {
try {
const packagePath = path.resolve(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
return packageJson.version || 'unknown';
}
catch (error) {
return '1.0.0';
}
}
try {
const params = parseArgs();
// Handle --help flag
if (params.help) {
printUsage();
process.exit(0);
}
// Handle --version flag
if (params.version) {
console.log(`env-twin version ${getVersion()}`);
process.exit(0);
}
// Show usage if no arguments provided and no default files exist
if (Object.keys(params).length === 2 &&
!params.source &&
!params.dest &&
!fs.existsSync('.env')) {
printUsage();
process.exit(0);
}
const envPath = path.resolve(process.cwd(), params.source || '.env');
const examplePath = path.resolve(process.cwd(), params.dest || '.env.example');
// Check if source file exists
if (!fs.existsSync(envPath)) {
console.error(`Error: Source file '${envPath}' not found!`);
console.error(`Use --src or --source to specify a different source file.`);
printUsage();
process.exit(1);
}
// Read source file
let envContent;
try {
envContent = fs.readFileSync(envPath, 'utf-8');
}
catch (error) {
console.error(`Error: Failed to read '${envPath}'`);
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
// Process content
const exampleContent = envContent
.split('\n')
.map((line) => {
if (line.trim() === '' || line.trim().startsWith('#')) {
return line;
}
const [key] = line.split('=');
if (!key)
return line;
// Convert key to lowercase and replace underscores
const inputValue = `input_${key.toLowerCase().replace(/[^a-z0-9]+/g, '_')}`;
return `${key}="${inputValue}"`;
})
.join('\n');
// Create directory if it doesn't exist
const destDir = path.dirname(examplePath);
try {
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}
// Write to destination file
fs.writeFileSync(examplePath, exampleContent);
console.log(`Success: Generated '${path.basename(examplePath)}' from '${path.basename(envPath)}'`);
console.log('Note: All environment variable values have been removed for security.');
}
catch (error) {
console.error(`Error: Failed to write to '${examplePath}'`);
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
}
catch (error) {
console.error('An unexpected error occurred:');
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}