seniority
Recipes

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:

migrated.mjs
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));
node migrated.mjs
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.
  • discover looks 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.

On this page