Source plugins
seniority/plugin: add a configuration source — a vault, a CI variable set, a remote config — at a rank between two built-ins, named in --explain, and checked by npx seniority check before it ships.
A plugin adds a source; it cannot reorder the five built-ins. It registers under the
family's plugin key, sources, and its rank slots it between two of them:
import { explain, RANK, resolve } from 'seniority';
import { register, sources } from 'seniority/plugin';
register({
name: 'acme-vault',
sources: {
vault: {
rank: RANK.env + 1, // after the environment, before the config file
read: (rt) => ({ location: 'acme://vault/ci', values: { region: rt.env.CI_REGION } }),
},
},
});
const env = { CI_REGION: 'ap-1' };
const specs = { region: { type: 'string', default: 'us-1' } };
const result = resolve(specs, { flags: {}, env, config: { path: 'app.config.json', data: { region: 'eu-2' } }, sources: sources({ env, cwd: '.' }) });
process.stdout.write(explain('region', result));region = "ap-1" from vault acme://vault/ci
candidates: flag --region (unset), config file app.config.json "eu-2", default "us-1"--explain names the plugin's source by the name and location it declared, even though
seniority has never heard of it.
| field | meaning |
|---|---|
rank | an integer strictly between RANK.flag (0) and RANK.default (40): a source may not beat what the user typed, nor sink below the declared default |
read(runtime) | returns { location?, values }, or nothing to contribute nothing |
values | the data itself, instead of read: a source may be pure data |
location | what --explain prints; the source's name when absent |
A source has either read or values, never both, because one source gives one answer. At an
equal rank, the plugin registered later wins. sources(runtime) reads every registered source
and sorts them by rank; pass the result to resolve as sources.
Checking a plugin before it ships
npx seniority check <file> validates a plugin module's default export against the family
schema. It exits 0 on success, 1 with a code and a fix, or 2 on a usage error:
export default {
name: 'acme-vault',
sources: {
vault: { rank: -5, values: { region: 'ap-1' } },
},
};E_PLUGIN_SCHEMA: plugin "acme-vault": source "vault" has rank -5
fix: a rank is an integer strictly between 0 (flag) and 40 (default) — a source may not beat what the user typed, nor sink below the declared defaultWhat is tested
plugin.test.ts:- a plugin's source is the provenance of the value, and names itself in
--explain; - it appears as a losing candidate when a built-in outranks it;
- a source may be data;
- the rank slots it between the built-ins, and cannot outrank the flag or sink below the default;
- the family's refusals.
- a plugin's source is the provenance of the value, and names itself in
Validation
validate() returns every violation at once, each naming the rule, the value and the source that set it — file and line, flag or variable — and check() throws them as one CONFIG error.
Why seniority
seniority against cosmiconfig, lilconfig, dotenv and rc, one capability per row, every cell linked to the test, grade or source that proves it — including YAML, which it deliberately leaves to a loader you pass.