The config loader lets you specify environment values using JSON/YAML or JS/TS config files, then overlay them deterministically with privacy and source precedence.
Behavior: In the shipped CLI (plugin-first host) and the generator path, the loader/overlay pipeline is always active and is a no-op when no config files are present.
When enabled, the loader discovers up to three configs in the following order:
Notes:
JSON/YAML (data only, always-on; no-op when no files are present):
env, defaultEnv, defaultEnvKey, paths, pathsDelimiter, pathsDelimiterPattern, dotenvToken, privateToken.vars, varsAssignor, varsAssignorPattern, varsDelimiter, varsDelimiterPattern, outputPath.loadProcess, dynamicPath.excludeAll, excludeDynamic, excludeEnv, excludeGlobal, excludePrivate, excludePublic.shell, capture.log, debug, trace, strict.redact, redactPatterns.warnEntropy, entropyThreshold, entropyMinLength, entropyWhitelist.JS/TS (data + dynamic):
(vars: Record<string, string | undefined>, env?: string) => string | undefined.TS support:
Config privacy derives from the filename suffix:
The loader overlays config-provided values onto the “base” file-derived dotenv values using these axes (higher wins):
The overlay flow:
When no explicit -e flag is provided, get-dotenv resolves the target environment in three tiers:
-e flag — highest priority, used as-is.defaultEnvKey lookup — a global-only pre-pass loads dotenv files (excluding env-scoped files) and looks up the variable named by defaultEnvKey (default: DEFAULT_ENV). If found, its value becomes the resolved environment.defaultEnv — the fallback from config if neither of the above produces a value.Example: if .env.local contains DEFAULT_ENV=bali and your config sets defaultEnv: 'dev', the resolved environment is bali (tier 2 wins over tier 3).
By the time plugins run, bag.env is always the fully resolved environment — plugins never need to handle defaulting themselves.
There are three distinct expansion stages:
--env, --vars, --output-path, --dotenv-token, --private-token, --dynamic-path, --paths) are dotenv-expanded once against process.env via an option parser; they do not see { ...ctx.dotenv }.afterResolve, the host deep-interpolates each plugin’s config slice (string leaves only) once against { ...ctx.dotenv, ...process.env } (process.env wins).Notes:
getDotenv() (programmatic API) expands outputPath against the composed variables (it can reference other dotenv keys). In the CLI, argv-provided values (like --output-path) are expanded against process.env at parse time, but values coming from rootOptionDefaults are not automatically expanded.You can validate the final composed environment via config:
Behavior:
Examples:
# getdotenv.config.yaml
requiredKeys:
- APP_SETTING
- ENV_SETTING
// getdotenv.config.ts
import { z } from 'zod';
export default {
schema: z.object({
APP_SETTING: z.string().min(1),
ENV_SETTING: z.string().optional(),
}),
};
Plugin configuration is discovered under plugins.<mount-path> in the same packaged/project public/local files. The host merges slices by precedence (packaged → project/public → project/local) and deep‑interpolates string leaves once against { ...ctx.dotenv, ...process.env } (process.env wins for plugin slices). Plugins read their own validated slice via the instance‑bound helper plugin.readConfig(cli).
Location and shape:
{
"plugins": {
"batch": {
"rootPath": "./packages"
},
"aws/dynamodb": {
"create": { "version": "v1" }
},
"my-plugin": {
"scripts": {
"build": { "cmd": "npm run build", "shell": "/bin/bash" }
},
"shell": false
}
}
}
Precedence and timing:
For compile‑time DX, overlayEnv preserves the key set:
import { overlayEnv } from '@karmaniverous/get-dotenv/env/overlay';
const base = { A: 'a' };
const out = overlayEnv({
base,
env: 'dev',
configs: {},
programmaticVars: { B: 'b' },
});
// out is typed as { A?: string } & { B?: string } at compile time
Presentation‑only diagnostics help audit values without altering runtime behavior:
You can define a scripts table in config. Script strings are resolved by the cmd and batch commands. If you want a named script to inherit the root shell behavior, use the string form; the object form { cmd, shell } uses shell as the effective shell for that entry (and when omitted, the script currently runs with shell-off rather than inheriting the root shell):
{
"scripts": {
"bash-only": { "cmd": "echo $SHELL && echo OK", "shell": "/bin/bash" },
"plain": { "cmd": "node -v", "shell": false }
}
}
Then:
getdotenv cmd bash-only
getdotenv cmd plain
For help‑time visibility of root flags (e.g., hiding --capture or an entire family like --shell/--shell-off), set rootOptionVisibility in JSON/YAML/JS/TS config. Precedence matches root defaults: createCli < packaged/public < project/public < project/local.