// .env parsing for scripts/dispatch.js. We parse the small subset of the // dotenv format we actually rely on — `KEY=value`, an optional `export` // prefix, and surrounding single/double quotes — rather than taking on the // `dotenv` dependency for one build script. Crucially, nothing here touches // `process.env`: callers decide what to do with the parsed values, which is // what lets dispatch.js read a single key without leaking the whole file into // the child process. import { readFileSync } from 'node:fs' import path from 'node:path' const ENV_LINE = /^\s*(?:export\s+)?([\w.-]+)\s*=\s*(.*?)\s*$/ // Parse the contents of a single env file into a plain key/value object. export function parseEnv(content) { const parsed = {} for (const raw of content.split('\n')) { const m = ENV_LINE.exec(raw) if (!m) continue let value = m[2] if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1) } parsed[m[1]] = value } return parsed } // Read a cascade of env files from `dir`, with later files overriding earlier // ones — matching the precedence vite/Next use // (`.env` < `.env.local` < `.env.` < `.env..local`). A missing file // is skipped, but any other read error (permissions, IO) is surfaced rather // than silently dropping that file's values. Returns the merged key/value // object; never mutates process.env. export function readEnvFiles(dir, files) { const parsed = {} for (const file of files) { let content try { content = readFileSync(path.join(dir, file), 'utf8') } catch (err) { if (err && typeof err === 'object' && err.code === 'ENOENT') continue throw err } Object.assign(parsed, parseEnv(content)) } return parsed }