Incremental migration from cosmiconfig
Swap cosmiconfig for seniority in one import, then move to discover() and resolve() when you want precedence and provenance — and what changes at each step.
1. Change the import
- import { cosmiconfig } from 'cosmiconfig';
+ import { cosmiconfig } from 'seniority';The explorer is cosmiconfig's (search, load, clearCaches), graded 186 of 243 by
cosmiconfig's own suite. The gap is YAML. If your config can be YAML, pass the parser:
import yaml from 'js-yaml';
import { cosmiconfig } from 'seniority';
const explorer = cosmiconfig('mytool', { loaders: { '.yaml': (_path, text) => yaml.load(text), '.yml': (_path, text) => yaml.load(text) } });For lilconfig the swap is seniority/lilconfig, graded 77 of 77, and npx burgee migrate
makes it for you.
2. Resolve instead of merging by hand
A cosmiconfig caller usually merges the file with flags and the environment itself, in an order
written once per program. discover plus resolve replaces that code, and keeps the record:
import { writeFileSync } from 'node:fs';
import { cosmiconfig, discover, explain, resolve } from 'seniority';
writeFileSync('mytool.config.json', '{ "region": "eu-2", "retries": 3 }\n');
const env = { MYTOOL_RETRIES: '5' };
const flags = { region: 'ap-1' };
// Before: find the file, then merge by hand.
const found = await cosmiconfig('mytool', { searchPlaces: ['mytool.config.json'] }).search();
const before = { ...found.config, ...(env.MYTOOL_RETRIES && { retries: Number(env.MYTOOL_RETRIES) }), ...flags };
console.log('before:', before);
// After: one order, and the answer to "why".
const specs = { region: { type: 'string' }, retries: { type: 'number' } };
const result = resolve(specs, { flags, env, envPrefix: 'MYTOOL', config: await discover({ name: 'mytool', cwd: '.', env }) });
console.log('after: ', result.values);
process.stdout.write(explain('retries', result));before: { region: 'ap-1', retries: 5 }
after: { region: 'ap-1', retries: '5' }
retries = "5" from env MYTOOL_RETRIES
candidates: flag --retries (unset), config file mytool.config.json:1 3, default (unset)Note the one difference in the output: the environment value is the string "5". resolve
converts only booleans from the environment. Declare the shape and run validate (see
Validation) to catch a number that arrives as a string, or convert
it where you read it.
What changes when you move:
- The order is fixed: flag, env, config,
package.json, default. - The environment reaches declared options only, under the prefix.
discoverlooks in fewer places than cosmiconfig's default search places: an explicit file,<NAME>_CONFIG,<name>.config.*in the working directory, and the user config directory. It walks upward only when asked.
Testing configuration
Test precedence, environment handling and validation without touching process.env or the disk, because resolve() takes every layer as an argument.
seniority
Every export of seniority, with its signature and doc comment: explain, ConfigError, envBoolean, envName, ORDER, RANK and 34 more, plus 31 types.