seniority
Guides

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:

vault.mjs
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));
node vault.mjs
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.

fieldmeaning
rankan 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
valuesthe data itself, instead of read: a source may be pure data
locationwhat --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:

badplugin.mjs
export default {
  name: 'acme-vault',
  sources: {
    vault: { rank: -5, values: { region: 'ap-1' } },
  },
};
npx seniority check badplugin.mjs
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 default

What 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.

On this page