seniority
Coming from…

Coming from cosmiconfig

A cosmiconfig alternative with zero dependencies: import { cosmiconfig } from seniority, graded 186 / 243 by cosmiconfig's own suite (YAML is the gap) — with provenance, so every resolved value can say where it came from, as text, --json data or an agent event.

seniority is a cosmiconfig alternative that also covers what dotenv, 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 cosmiconfig in one import

- import { cosmiconfig } from 'cosmiconfig';
+ import { cosmiconfig } from 'seniority';

The root export carries cosmiconfig's surface: cosmiconfig, cosmiconfigSync, Explorer, ExplorerSync, defaultLoaders, defaultLoadersSync, getDefaultSearchPlaces, globalConfigSearchPlaces and metaSearchPlaces — all three search strategies, both caches, $import, and the meta-config merge.

YAML is the one gap. seniority bundles no format parser, so its loadYaml reads the subset of YAML that is also JSON and refuses the rest by name. Pass loaders: { '.yaml': yaml.load } and you have cosmiconfig's behaviour, with the parser as your dependency rather than everyone's.

Is seniority compatible with cosmiconfig?

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

passingrate
seniority186 / 24376.5%
cosmiconfig itself (control)240 / 24398.8%

From Compatibility, which npm run compat:page generates from the oracle's last run; that page is the authority, and it names every case that leaves the gate. Of the 57 cases not passing, seniority's README and that page account for each: 54 are the YAML refusal above (the largest block is import.test.ts, whose fixtures are all .yml), one is the harness reaching for a file path the vendored copy does not have, and two are the XDG global-directory cases, which register only on Linux and are counted against seniority on every platform. Below 100%, seniority is never described as cosmiconfig-compatible; this is a grade.

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

What you gain over cosmiconfig

cosmiconfig finds a file. 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. A plugin can add a source between them and can never beat the flag the user typed — so the agent reading your --schema and the person reading your --help can reason about it from the outside.
  • resolve is pure. No filesystem, no process.env: the environment is passed in, so it is testable without a process.

When to switch from cosmiconfig

  • "Why is this set to that?" is a question your users or agents ask, and nobody can answer it without reading the source.
  • You want config, env and flags in one precedence rather than three hand-written ones.
  • You do not need YAML, or you are content to pass the parser in.

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

On this page