UNPKG

envsyncer

Version:

Sync your root .env files into monorepo subfolders with ease

129 lines (104 loc) • 4.06 kB
import fs from 'fs'; import path from 'path'; import chalk from 'chalk'; const ENV_FILES = ['.env', '.env.local', '.env.development', '.env.production']; function parseEnvVars(content: string): Set<string> { const envVars = new Set<string>(); const lines = content.split('\n'); for (const line of lines) { const trimmed = line.trim(); // Skip comments and empty lines if (trimmed && !trimmed.startsWith('#')) { const equalIndex = trimmed.indexOf('='); if (equalIndex > 0) { const varName = trimmed.substring(0, equalIndex); envVars.add(varName); } } } return envVars; } function extractLocalEnvVars(localContent: string, rootEnvVars: Set<string>): string[] { const lines = localContent.split('\n'); const localEnvLines: string[] = []; for (const line of lines) { const trimmed = line.trim(); // Keep comments and empty lines that are not at the start if (!trimmed || trimmed.startsWith('#')) { // Only keep local comments if they're not environment variable comments if (localEnvLines.length > 0) { localEnvLines.push(line); } continue; } const equalIndex = trimmed.indexOf('='); if (equalIndex > 0) { const varName = trimmed.substring(0, equalIndex); // Only keep local env vars that are not in root if (!rootEnvVars.has(varName)) { localEnvLines.push(line); } } } // Remove trailing empty lines while (localEnvLines.length > 0 && !localEnvLines[localEnvLines.length - 1].trim()) { localEnvLines.pop(); } return localEnvLines; } export async function syncEnvToTargets(targetDirs: string[]) { const rootEnvFiles = ENV_FILES.filter((file) => fs.existsSync(path.join(process.cwd(), file)) ); if (rootEnvFiles.length === 0) { console.log(chalk.red('āŒ No .env files found in root directory.')); console.log( chalk.gray('Looked for:', ENV_FILES.join(', ')) ); return; } console.log(chalk.blue('\nšŸ“„ Found env files:'), rootEnvFiles.join(', ')); for (const envFile of rootEnvFiles) { const rootEnvPath = path.join(process.cwd(), envFile); const rootEnvContent = fs.readFileSync(rootEnvPath, 'utf-8'); const rootEnvVars = parseEnvVars(rootEnvContent); for (const dir of targetDirs) { const targetPath = path.join(process.cwd(), dir, envFile); try { let localEnvLines: string[] = []; // Backup local env vars if the file exists if (fs.existsSync(targetPath)) { const localEnvContent = fs.readFileSync(targetPath, 'utf-8'); localEnvLines = extractLocalEnvVars(localEnvContent, rootEnvVars); } // Start with root env content (preserves all formatting and comments) let finalContent = rootEnvContent; // Add local env vars if any exist if (localEnvLines.length > 0) { // Ensure root content ends with a newline if (!finalContent.endsWith('\n')) { finalContent += '\n'; } // Add separator comment finalContent += '\n# Local environment variables\n'; finalContent += localEnvLines.join('\n'); // Ensure final content ends with a newline if (!finalContent.endsWith('\n')) { finalContent += '\n'; } } fs.writeFileSync(targetPath, finalContent); const localCount = localEnvLines.filter(line => line.trim() && !line.trim().startsWith('#')).length; const message = localCount > 0 ? `āœ… Synced ${envFile} to ${chalk.gray(`${dir}/${envFile}`)} ${chalk.cyan(`(preserved ${localCount} local vars)`)}` : `āœ… Synced ${envFile} to ${chalk.gray(`${dir}/${envFile}`)}`; console.log(chalk.green(message)); } catch (error) { console.error( chalk.red(`āŒ Failed to sync ${envFile} to ${dir}:`), error ); } } } }