env-file.ts44 lines · main
1/**
2 * Merge briven-owned keys into a .env.local style file. Preserves
3 * user-set keys and comments; only updates keys whose name appears in
4 * the `updates` record. Returns the new full content.
5 */
6export function mergeEnvFile(existing: string, updates: Record<string, string>): string {
7 const updateKeys = new Set(Object.keys(updates));
8 // Normalize CRLF (Windows-edited .env.local) to LF so we don't leave
9 // stray \r on preserved lines and emit mixed line endings on rewrite.
10 const normalized = existing.replace(/\r\n/g, '\n');
11 const lines = normalized.split('\n');
12 const out: string[] = [];
13 const seen = new Set<string>();
14
15 for (const line of lines) {
16 const match = line.match(/^([A-Z][A-Z0-9_]*)=/);
17 if (match && updateKeys.has(match[1]!)) {
18 const key = match[1]!;
19 out.push(`${key}=${quote(updates[key]!)}`);
20 seen.add(key);
21 } else {
22 out.push(line);
23 }
24 }
25
26 // Strip the synthetic trailing empty line from split('\n') so we
27 // don't accumulate blank lines across merges.
28 if (out.length > 0 && out[out.length - 1] === '') out.pop();
29
30 for (const key of updateKeys) {
31 if (!seen.has(key)) {
32 out.push(`${key}=${quote(updates[key]!)}`);
33 }
34 }
35
36 return out.join('\n') + '\n';
37}
38
39function quote(value: string): string {
40 // Always quote — keeps the file readable + avoids parsing edge cases
41 // when values contain spaces or shell-special chars.
42 const escaped = value.replace(/"/g, '\\"');
43 return `"${escaped}"`;
44}