chore(config): replace dotenv with Node's native env file loader - #750
Conversation
Node 26 loads env files natively, so the `-r dotenv/config` preload in the start, start:production, and test:watch scripts is redundant. Swap it for `--env-file-if-exists=.env` and drop the dependency. `--env-file-if-exists` rather than `--env-file` because the file is absent in both places that matter: `.env` is listed in .dockerignore, so the production image never has one, and it is gitignored, so CI never checks one out. Plain `--env-file` exits 9 on a missing file; the if-exists form exits 0 with a one-line notice on stderr, matching dotenv's silent no-op. Override semantics are unchanged - a variable already present in the environment still wins over the file, which is what dotenv did by default and what start:dev relies on when it sets NODE_ENV before calling start. Side benefit: dotenv v17+ prints an `injected env (N) from .env` banner to stdout, the same stream pino writes structured JSON to. The native loader is silent, so startup output stays machine-parseable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iD3V7Zyugg3atXVsogTBf
|
Reviewed: a small, correct change with nothing blocking. Looks ready to merge. Checked
Optional: the production image never has a |
|
Thanks — agreed on the verification, and I re-ran the CI check independently: On the optional suggestion, the premise holds. One correction to the rationale for keeping it, though. The comment frames it as mattering "if a So the case for dropping is a little stronger than stated. The case against is one the comment doesn't raise: it would leave three sibling scripts where two carry the flag and one doesn't, for a reason Proposing we merge as-is. The asymmetry costs more in future confusion than the log line costs in noise. If the log line does turn out to be a nuisance, the clean version isn't just deleting the flag — it's deleting it and adding a comment in the Dockerfile beside the 🤖 Reviewed with Claude Code · https://claude.ai/code/session_018iD3V7Zyugg3atXVsogTBf |
What
Node 26 loads env files natively, so the
-r dotenv/configpreload is redundant. This swaps it for--env-file-if-exists=.envin the three scripts that used it and drops the dependency.dotenv was only ever wired in through those preload flags — there is no
import 'dotenv'ordotenv.config()anywhere in the source, and nothing else in the tree depends on it. The.envfile itself is 7 lines of plainKEY=valuewith one comment: no${VAR}expansion, no quoting, no multiline values, nothing the native parser lacks.Why
--env-file-if-existsand not--env-fileThe file is absent in both places that matter —
.envis listed in.dockerignore, so the production image never has one, and it is gitignored, so CI never checks one out. Plain--env-fileexits9on a missing file. The if-exists form exits0with a one-line notice on stderr, which matches dotenv's silent no-op closely enough to be a drop-in.Behavior verified on Node 26.8.1
.envpresentPORT,DOMAIN,PROTOCOLfrom the file.envmissingstart:productionflag set--permissiondoes not block the readThat second row is what
start:devdepends on when it setsNODE_ENV=developmentbefore delegating tostart.Side benefit
dotenv v17+ prints an
◇ injected env (N) from .envbanner to stdout — the same stream pino writes structured JSON to. The native loader is silent, so startup output stays machine-parseable.Trade-off
CI and the container now print
.env not found. Continuing without it.to stderr on every run, where dotenv was silent. Cosmetic, and there is no flag to suppress it.Testing
pnpm test— 62 files, 992 passed, 1 skipped. Also run by the pre-push hook.🤖 Generated with Claude Code
https://claude.ai/code/session_018iD3V7Zyugg3atXVsogTBf