Packages / ops-env
Loading
What load() reads in production and on your machine, which value wins, and how errors look.
| situation | what load() does |
|---|---|
ENV_SECRET is set (a deployed service) | reads /run/secrets/$ENV_SECRET. A missing file, a directory, or a file that does not parse throws, and nothing from it is set |
ENV_SECRET is unset or empty (your machine, tests) | reads .env in the working directory if there is one. A missing file is fine; a broken one prints a warning and sets nothing |
SECRETS_DIR moves the directory the secret is read from, for a setup that mounts secrets
somewhere other than /run/secrets. Parent directories are never searched for .env.
What wins
A variable already in the environment wins, even when it is empty. Within the file, the
first of a repeated name wins. That is how a plain container variable, such as the
DATABASE_URL of a SQLite database the service mounts, overrides a group key of the same
name.
A failed load stops the process
Under --import or --preload, a thrown error stops Node or Bun before your app starts.
That is the point: a service that boots with half its configuration silently absent is
worse than one that does not boot.
EnvFileError: ENV_SECRET names 'web-env' but /run/secrets/web-env is not there: the deploy did not render this service's env secret
Errors name the file and the line, never the line's content, because that content is a secret and the error lands in the boot log.
For tests
import { loadFrom } from '@syncronet/ops-env'
const env: Record<string, string | undefined> = {}
loadFrom('web-env', '/tmp/fixtures', env, process.cwd())
loadFrom(secretName, secretsDir, env, cwd) does what load() does into an object you
pass. parse(text, source) returns the [name, value] pairs of a file and throws
EnvParseError on a broken one.