UNPKG

@accounter/server

Version:

The test suite is split into three Vitest projects for efficiency and isolation:

98 lines (87 loc) 2.73 kB
import { readFile, writeFile, rename, unlink } from 'fs/promises'; /** * Write or update an environment variable in a .env file * * Uses atomic write pattern (temp file + rename) to prevent corruption * if process crashes during write. * * ⚠️ **SECURITY WARNING**: This function stores values in PLAINTEXT. * Never use for production secrets. Use environment variables, * secret managers (AWS Secrets Manager, Vault), or encrypted files. * * **Thread Safety**: Not safe for concurrent writes to the same file. * Multiple processes writing simultaneously may cause race conditions. * * **Format Assumptions**: Assumes KEY=VALUE format without quotes or inline comments. * * @param filePath - Path to the .env file * @param key - Environment variable key (uppercase recommended) * @param value - Environment variable value (stored as-is) * * @example * ```typescript * // Create new variable * await writeEnvVar('.env', 'DATABASE_URL', 'postgresql://localhost/mydb'); * * // Update existing variable * await writeEnvVar('.env', 'DATABASE_URL', 'postgresql://localhost/newdb'); * * // Result is idempotent * await writeEnvVar('.env', 'API_KEY', 'abc123'); * await writeEnvVar('.env', 'API_KEY', 'abc123'); // No change * ``` */ export async function writeEnvVar( filePath: string, key: string, value: string, ): Promise<void> { let content = ''; try { content = await readFile(filePath, 'utf-8'); } catch (error) { // File doesn't exist, will create it if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { throw error; } } const lines = content.split('\n'); const keyPattern = new RegExp(`^${escapeRegExp(key)}=`); let found = false; // Remove trailing empty string from split (if content ended with \n) if (lines.length > 0 && lines[lines.length - 1] === '') { lines.pop(); } const updatedLines = lines.map(line => { if (keyPattern.test(line)) { found = true; return `${key}=${value}`; } return line; }); if (!found) { updatedLines.push(`${key}=${value}`); } // Join with newlines and add single trailing newline const newContent = updatedLines.join('\n') + '\n'; // Atomic write: temp file + rename const tempPath = `${filePath}.tmp.${process.pid}`; try { await writeFile(tempPath, newContent, 'utf-8'); await rename(tempPath, filePath); } catch (error) { // Clean up temp file on error try { await unlink(tempPath); } catch { // Ignore cleanup errors } throw error; } } /** * Escape special regex characters in a string */ function escapeRegExp(str: string): string { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }