seniority
Coming from…

Coming from dotenv

A dotenv alternative with a drop-in path: import dotenv from seniority/dotenv, graded 106 / 141 by dotenv's own test suite (the declined .env.vault is most of the gap) — then the same package resolves every value across flags, env, config files and defaults, with provenance as text, --json data or an agent event.

seniority is a dotenv alternative that also covers what cosmiconfig, rc and find-up do beside it: one resolution for flags, environment, config files, a package.json field and defaults, in a fixed order, with no dependencies.

Migrate from dotenv in one import

- import dotenv from 'dotenv';
+ import dotenv from 'seniority/dotenv';

seniority/dotenv carries dotenv 17's config, parse and populate, as named exports and on a default export shaped like dotenv's module.exports. parse is dotenv's line grammar, character for character. config() behaves as dotenv's does when told nothing: it reads ./.env from the working directory into process.env, and it takes dotenv's options — path (one file or several, a URL, a leading ~), processEnv, encoding, override and debug — returning { parsed } or { parsed, error } rather than throwing for a missing file.

There is no dotenv/config preload entry. Where you had import 'dotenv/config' or node -r dotenv/config, call it once at your entry point:

import { config } from 'seniority/dotenv';

config();

Not built, on purpose: decrypt, DOTENV_KEY and the .env.vault format, which dotenv deprecated in favour of dotenvx, and config's quiet banner and tips.

Is seniority compatible with dotenv?

Not fully, and the number says so. dotenv's own suite, vendored at 17.4.2 and unmodified apart from the import specifier, runs against seniority/dotenv beside a control that runs it against real dotenv:

passingrate
seniority/dotenv106 / 14175.2%
dotenv itself (control)141 / 141100.0%

From Compatibility, which npm run compat:page generates from the oracle's last run; that page is the authority. The suite runs under node-tap, whose plan counts assertions rather than test cases, so 141 is assertions. Of the 35 not passing, the oracle's row accounts for each: 27 are the vault and decrypt path and 2 are dotenvx tips, both declined above, and 6 load dotenv's private lib/* modules by path, which the harness maps to the package root. Below 100%, seniority is never described as dotenv-compatible; this is a grade.

The same page grades seniority (cosmiconfig's own suite), seniority/lilconfig and seniority/rc.

What you gain over dotenv

dotenv puts a file into the environment. seniority resolves the value, and keeps the receipt:

  • Provenance on every value. resolve() returns each option's winning source and every candidate it beat. explain('region', result) prints region = "eu-2" from config file ./app.config.json with the unset flag and env candidates beneath it.
  • The same record for agents. explanationJson() is that record as --json data, and explanationEvent() is it as an agent event — generated by the code that picked the value, so neither can drift from the text.
  • An order nobody can rearrange. flag > env > config file > package.json field > default, exported as ORDER. populate already keeps a key the environment holds unless you pass override; resolve states that rule once for every source, and a plugin can add a source between them but can never beat the flag the user typed.
  • resolve is pure. No filesystem, no process.env: the environment is passed in, so it is testable without a process. Only the drop-in façades reach for process.env by default, because their incumbents do.

When to switch from dotenv

  • "Why is this set to that?" is a question your users or agents ask, and the answer depends on whether a flag, the environment or a config file won.
  • You load .env and then hand-write the order in which a flag, an env var and a config file override each other.
  • You do not use .env.vault or DOTENV_KEY.

The API, the discovery rules and the other drop-in paths are on seniority.

On this page