env
Environment variables made easy — load, validate, inject.
About
@calvear/env eases environment variable handling for NodeJS apps — like
env-cmd or
dotenv, but with powerful, extensible
features: pluggable providers to load, pull and push variables from
different stores, JSON Schema validation, value interpolation, secret
masking and nested/shared keys.
Features
- Provider plugins — bundled
package-json,app-settings,secretsandlocalproviders, plus your own (NPM package or local script). - Injection — load variables into
process.envand run any command. - JSON Schema — auto-generate and validate variables before injecting.
- Nested & global keys —
GROUP__VARflattening and$-prefixed global keys (the$marker is stripped on injection). - Interpolation — reference other args/vars with
[[ ]]delimiters. - Masking — hide secrets in the debug output by key name, key regex, or value content regex.
- Pretty output — colorized, sorted, masked debug render of the resolved environment.
- Export — write the unified environment to a
.envor JSON file.
Requirements
NodeJS 20 or higher.
> node -v
v20.0.0
ESM only
Since v3 this package is ESM-only ("type": "module"). Consumers must
use ESM import syntax, and custom providers must be ESM modules that export
their provider object.
// ✅ ESM
import { EnvProvider } from '@calvear/env';
// ❌ CommonJS require is not supported
// const { EnvProvider } = require('@calvear/env');
️ Quick start
Install the package:
> npm install @calvear/env
Run the binary directly:
> npx env --help
Usage: env [command] [options..] [: subcmd [:]] [options..]
Commands:
env [options..] [: <subcmd> :]
env pull [options..]
env push [options..]
env schema [options..]
env export [options..]
Or wire it into your npm scripts:
{
"scripts": {
// inject "dev" variables (debug mode) and start the app
"start:dev": "env -e dev -m debug : node dist/main.js",
// inject "prod" variables for the build
"build:prod": "env -e prod -m build : tsc",
// regenerate the validation schema
"env:schema": "env schema -e dev",
},
}
Run it:
> npm run start:dev
⚡ env v3.0.0 · 🌎 dev · 🧩 debug
📦 package-json 6 vars
🗂️ app-settings 5 vars
🔐 secrets 1 secrets
📂 local 1 vars
environment (12 variables)
ENV = dev
NODE_ENV = development
SECRET = *****
VERSION = 3.0.0
...
✓ 12 variables loaded in 142ms
▶ node dist/main.js
My environment loaded is: dev
✓ finished in 168ms
The resolved environment is only rendered at
--log debug. Values are sorted, secrets are masked, and colored by type — strings in gray, numbers in orange, booleanstrue/falsein green/red.
Commands & Options
Interpolation — any option value can reference other arguments using
[[and]]delimiters. Withroot: "config", the value[[root]]/file.jsonresolves toconfig/file.json; withenv: "dev",[[root]]/config.[[env]].jsonresolves toconfig/config.dev.json.
Global options
| Option | Description | Type | Default |
|---|---|---|---|
--help |
Show help | boolean |
|
-e, --env |
Environment to load (i.e. dev, prod) |
string |
|
-m, --modes |
Execution modes (i.e. debug, test) |
string[] |
[] |
--nd, --nestingDelimiter |
Nesting-level delimiter for flatten | string |
__ |
--arrDesc, --arrayDescomposition |
Serialize (false) or break down (true) arrays |
boolean |
false |
-x, --expand |
Interpolate environment variables using itself | boolean |
false |
--ci |
Continuous Integration mode (skips local files) | boolean |
auto |
Workspace options
| Option | Description | Type | Default |
|---|---|---|---|
--root |
Base environment folder path | string |
env |
-c, --configFile |
Config JSON file path | string |
[[root]]/settings/settings.json |
-s, --schemaFile |
Environment schema JSON file path | string |
[[root]]/settings/schema.json |
--pkg, --packageJson |
package.json path |
string |
cwd |
JSON Schema options
| Option | Description | Type | Default |
|---|---|---|---|
-r, --resolve |
Merge new schema or override it | merge/override |
merge |
--null, --nullable |
Whether variables are nullable by default | boolean |
true |
--df, --detectFormat |
Include string formats in the generated schema | boolean |
false |
Logger options
| Option | Description | Type | Default |
|---|---|---|---|
--log, --logLevel |
Log level | silly/trace/debug/info/warn/error |
info |
--mvk, --logMaskValuesOfKeys |
Mask a value when its key matches (exact or regex) | string[] |
[] |
--mrx, --logMaskAnyRegEx |
Mask value content matching a regex (every match) | string[] |
[] |
See Masking secrets for the full masking semantics.
Environment inference from npm scripts
When -e is not provided (neither by CLI nor config file), the CLI infers the
environment from the npm script name (npm_lifecycle_event), taking the
last segment after : — running npm run start:dev infers dev, so the
-e flag becomes redundant in per-env scripts:
{
"scripts": {
"start:dev": "env -m debug : node dist/main.js", // infers "dev"
"start:qa": "env -m debug : node dist/main.js", // infers "qa"
"start:prod": "env -m debug : node dist/main.js", // infers "prod"
},
}
The inferred value is validated against the environments defined in the workspace, discovered dynamically as the union of:
- the
|ENV|and|LOCAL|section keys ofappsettings.json, - per-env provider files in the root folder (
appsettings.<env>.json,appsettings.<env>.local.json,<env>.env.json,<env>.local.env.json).
Validation rules:
- inferred env unknown → the command aborts listing the defined
environments (catches typos like
start:prd); - explicit
-eunknown → only a warning; - no defined environments → inference is skipped silently;
- scripts without a
:suffix (i.e.preview) and direct CLI invocations are unaffected.
Scripts whose suffix is not an environment (i.e. test:mutation,
env:schema) must keep an explicit -e.
[[env]]inside--configFilecannot reference an inferred environment — the config file is loaded before inference runs.
env
Inject environment variables into process.env and execute a command (the
command goes after :).
env -e <env> [options..] [: <subcmd> :] [options..]
> env -e dev -m test unit : npm test
> env -e dev -m debug : npm start : -c [[root]]/[[env]].env.json
> env -e prod -m build optimize : npm run build
| Option | Description | Type | Default |
|---|---|---|---|
--validate, --schemaValidate |
Validate variables against the JSON schema | boolean |
true |
pull
Pull environment variables from the providers' stores (for providers that
implement pull, i.e. custom remote providers).
env pull -e <env> [options..]
| Option | Description | Type | Default |
|---|---|---|---|
-o, --overwrite |
Overwrite local variables | boolean |
false |
push
Push environment variables to the providers' stores (for providers that
implement push).
env push -e <env> [options..]
| Option | Description | Type | Default |
|---|---|---|---|
-f, --force |
Force push for secrets | boolean |
false |
schema
Generate (or update) the validation schema from the providers' output.
> env schema -e dev -m build
export
Export the unified environment to a file.
env export -e <env> -m <modes> [options..]
| Option | Description | Type | Default |
|---|---|---|---|
-u, -p, --uri |
Output file path | string |
.env |
-f, --format |
Output format | dotenv/json |
dotenv |
-q, --quotes |
Wrap values in quotes | boolean |
false |
> env export -e dev -m build -f json --uri [[env]].env.json
Providers
Providers are the core of this library. It ships with four integrated providers, and you can add your own.
package-json
Loads project info from your package.json (version, project, name,
title, description) into ENV, VERSION, PROJECT, NAME, TITLE,
DESCRIPTION.
| Option | Description | Type | Default |
|---|---|---|---|
--vp, --varPrefix |
Prefix for the loaded variables | string |
"" |
# i.e. expose them as REACT_APP_* for CRA
> env -e dev -m build : react-scripts build : --vp REACT_APP_
app-settings
Non-secret loader for appsettings.json, organized by sections:
{
"|DEFAULT|": { "VAR1": "v1_default" },
"|ENV|": {
"dev": {
"C1": "V1",
"GROUP1": { "VAR2": "G1V2", "GROUP2": { "VAR1": "G1G2V1" } },
},
},
"|MODE|": {
"build": { "NODE_ENV": "production" },
"debug": { "NODE_ENV": "development" },
},
"|LOCAL|": { "dev": { "LOCAL_VAR": "only-local" } },
}
It also merges the unitary files appsettings.<env>.json,
appsettings.<mode>.json and appsettings.<env>.local.json.
Precedence (lowest → highest)
- flat root object (only when no
|...|section is present) |DEFAULT||ENV|for the current--env|MODE|for each--modesentry (later modes win)appsettings.<env>.jsonappsettings.<mode>.json(one per mode, in--modesorder)|LOCAL|for the current--envappsettings.<env>.local.json
Local layers (7 and 8) always win, and both are skipped in --ci.
| Option | Description | Type | Default |
|---|---|---|---|
--ef, --envFile |
Non-secret settings file path | string |
[[root]]/appsettings.json |
secrets
Loads secret variables from a per-environment JSON file
([[root]]/[[env]].env.json). Keep this file out of version control.
| Option | Description | Type | Default |
|---|---|---|---|
--sf, --secretsFile |
Secret variables file path | string |
[[root]]/[[env]].env.json |
local
Loads local-only variables (never loaded in --ci). The file is auto-created
if missing.
| Option | Description | Type | Default |
|---|---|---|---|
--lf, --localFile |
Local variables file path | string |
[[root]]/[[env]].local.env.json |
Creating custom providers
Create a provider in two ways:
- Local script — a
.jsfile thatexport defaults your provider. - NPM package — a published module that
export defaults your provider.
Both are wired in the config file via the providers list. A custom
provider can also implement pull/push to fetch and publish variables from a
remote store (i.e. a vault, a secrets manager or an API).
import type { CommandArguments, EnvProvider } from '@calvear/env';
import { logger, readJson, writeJson } from '@calvear/env/utils';
const KEY = 'my-unique-provider-key';
interface MyProviderArguments extends CommandArguments {
anyExtraOption: boolean;
}
const MyProvider: EnvProvider<MyProviderArguments> = {
// unique identifier
key: KEY,
// (optional) add custom options to the CLI via yargs
builder: (builder) => {
builder.options({
anyExtraOption: {
group: KEY,
alias: ['a', 'aeo'],
type: 'boolean',
default: false,
describe: 'Any option description',
},
});
},
// called on load — may be sync or async, and may return a list to merge
load: ({ env }) => {
if (env === 'dev') return { NODE_ENV: 'development' };
return [{ NODE_ENV: 'production' }, { ANY_GROUP: { INNER_VAR: 12 } }];
},
// (optional) called on `env pull`
pull: (argv, config) => {
/* fetch variables into your local cache */
},
// (optional) called on `env push`
push: (argv, config) => {
/* publish/update your variables */
},
};
export default MyProvider;
Config
Any CLI argument can be set in your config file
([[root]]/settings/settings.json by default), but it is mainly used to declare
providers:
{
"logLevel": "silly",
// mask secrets in the debug output (see the Masking section)
"logMaskValuesOfKeys": ["SECRET", "/token/i", "/api_key/i"],
"logMaskAnyRegEx": ["AKIA[0-9A-Z]{16}"],
"providers": [
{ "path": "package-json" },
{ "path": "app-settings" },
{ "path": "secrets" },
{ "path": "local" },
// custom NPM package
{ "path": "@my-scope/my-provider", "type": "module", "config": {} },
// custom local script
{ "path": "scripts/custom-loader.js", "type": "script" },
],
}
Provider order matters — providers are merged in declaration order, so later providers override earlier ones (
package-jsonis the base,localwins).
Nested & global keys
Organize variables in nested objects. They are flattened into process.env
using the nesting delimiter (__ by default):
{
"GROUP1": {
"VAR": "anyValue1",
"GROUP2": { "VAR": "anyValue2" },
},
"VAR3": "anyValue3",
}
process.env.GROUP1__VAR; // "anyValue1"
process.env.GROUP1__GROUP2__VAR; // "anyValue2"
process.env.VAR3; // "anyValue3"
$ global marker
Prefix a key with $ to mark it as global/shared — relevant for the
secrets provider, which uses the marker to scope the secret across the project
rather than per-mode. The marker is stripped on injection at any nesting
depth, while the group prefix is kept:
{
"$TOKEN": "rootValue",
"GROUP1": {
"$SHARED": "groupValue",
"VAR": "anyValue",
},
}
// the `
env
Environment variables made easy — load, validate, inject.
About
__INLINE_CODE_0__ eases environment variable handling for NodeJS apps — like
env-cmd or
dotenv, but with powerful, extensible
features: pluggable providers to __INLINE_CODE_1__, __INLINE_CODE_2__ and __INLINE_CODE_3__ variables from
different stores, JSON Schema validation, value interpolation, secret
masking and nested/shared keys.
Features
- Provider plugins — bundled __INLINE_CODE_4__, __INLINE_CODE_5__, __INLINE_CODE_6__
and __INLINE_CODE_7__ providers, plus your own (NPM package or local script).
- Injection — load variables into __INLINE_CODE_8__ and run any command.
- JSON Schema — auto-generate and validate variables before injecting.
- Nested & global keys — __INLINE_CODE_9__ flattening and __INLINE_CODE_10__-prefixed global
keys (the __INLINE_CODE_11__ marker is stripped on injection).
- Interpolation — reference other args/vars with __INLINE_CODE_12__ delimiters.
- Masking — hide secrets in the debug output by key name, key regex, or
value content regex.
- Pretty output — colorized, sorted, masked debug render of the resolved
environment.
- Export — write the unified environment to a __INLINE_CODE_13__ or JSON file.
Requirements
NodeJS 20 or higher.
> node -v
v20.0.0
ESM only
Since v3 this package is ESM-only (__INLINE_CODE_14__). Consumers must
use ESM __INLINE_CODE_15__ syntax, and custom providers must be ESM modules that __INLINE_CODE_16__
their provider object.
// ✅ ESM
import { EnvProvider } from '@calvear/env';
// ❌ CommonJS require is not supported
// const { EnvProvider } = require('@calvear/env');
️ Quick start
Install the package:
> npm install @calvear/env
Run the binary directly:
> npx env --help
Usage: env [command] [options..] [: subcmd [:]] [options..]
Commands:
env [options..] [: <subcmd> :]
env pull [options..]
env push [options..]
env schema [options..]
env export [options..]
Or wire it into your npm scripts:
{
"scripts": {
// inject "dev" variables (debug mode) and start the app
"start:dev": "env -e dev -m debug : node dist/main.js",
// inject "prod" variables for the build
"build:prod": "env -e prod -m build : tsc",
// regenerate the validation schema
"env:schema": "env schema -e dev",
},
}
Run it:
> npm run start:dev
⚡ env v3.0.0 · 🌎 dev · 🧩 debug
📦 package-json 6 vars
🗂️ app-settings 5 vars
🔐 secrets 1 secrets
📂 local 1 vars
environment (12 variables)
ENV = dev
NODE_ENV = development
SECRET = *****
VERSION = 3.0.0
...
✓ 12 variables loaded in 142ms
▶ node dist/main.js
My environment loaded is: dev
✓ finished in 168ms
The resolved environment is only rendered at __INLINE_CODE_17__. Values are
sorted, secrets are masked, and colored by type — strings in gray,
numbers in orange, booleans __INLINE_CODE_18__/__INLINE_CODE_19__ in green/red.
Commands & Options
Interpolation — any option value can reference other arguments using __INLINE_CODE_20__
and __INLINE_CODE_21__ delimiters. With __INLINE_CODE_22__, the value __INLINE_CODE_23__
resolves to __INLINE_CODE_24__; with __INLINE_CODE_25__,
__INLINE_CODE_26__ resolves to __INLINE_CODE_27__.
Global options
Option
Description
Type
Default
__INLINE_CODE_28__
Show help
__INLINE_CODE_29__
__INLINE_CODE_30__
Environment to load (i.e. __INLINE_CODE_31__, __INLINE_CODE_32__)
__INLINE_CODE_33__
__INLINE_CODE_34__
Execution modes (i.e. __INLINE_CODE_35__, __INLINE_CODE_36__)
__INLINE_CODE_37__
__INLINE_CODE_38__
__INLINE_CODE_39__
Nesting-level delimiter for flatten
__INLINE_CODE_40__
__INLINE_CODE_41__
__INLINE_CODE_42__
Serialize (__INLINE_CODE_43__) or break down (__INLINE_CODE_44__) arrays
__INLINE_CODE_45__
__INLINE_CODE_46__
__INLINE_CODE_47__
Interpolate environment variables using itself
__INLINE_CODE_48__
__INLINE_CODE_49__
__INLINE_CODE_50__
Continuous Integration mode (skips local files)
__INLINE_CODE_51__
auto
Workspace options
Option
Description
Type
Default
__INLINE_CODE_52__
Base environment folder path
__INLINE_CODE_53__
__INLINE_CODE_54__
__INLINE_CODE_55__
Config JSON file path
__INLINE_CODE_56__
__INLINE_CODE_57__
__INLINE_CODE_58__
Environment schema JSON file path
__INLINE_CODE_59__
__INLINE_CODE_60__
__INLINE_CODE_61__
__INLINE_CODE_62__ path
__INLINE_CODE_63__
cwd
JSON Schema options
Option
Description
Type
Default
__INLINE_CODE_64__
Merge new schema or override it
__INLINE_CODE_65__/__INLINE_CODE_66__
__INLINE_CODE_67__
__INLINE_CODE_68__
Whether variables are nullable by default
__INLINE_CODE_69__
__INLINE_CODE_70__
__INLINE_CODE_71__
Include string formats in the generated schema
__INLINE_CODE_72__
__INLINE_CODE_73__
Logger options
Option
Description
Type
Default
__INLINE_CODE_74__
Log level
__INLINE_CODE_75__/__INLINE_CODE_76__/__INLINE_CODE_77__/__INLINE_CODE_78__/__INLINE_CODE_79__/__INLINE_CODE_80__
__INLINE_CODE_81__
__INLINE_CODE_82__
Mask a value when its key matches (exact or regex)
__INLINE_CODE_83__
__INLINE_CODE_84__
__INLINE_CODE_85__
Mask value content matching a regex (every match)
__INLINE_CODE_86__
__INLINE_CODE_87__
See Masking secrets for the full masking semantics.
Environment inference from npm scripts
When __INLINE_CODE_88__ is not provided (neither by CLI nor config file), the CLI infers the
environment from the npm script name (__INLINE_CODE_89__), taking the
last segment after __INLINE_CODE_90__ — running __INLINE_CODE_91__ infers __INLINE_CODE_92__, so the
__INLINE_CODE_93__ flag becomes redundant in per-env scripts:
{
"scripts": {
"start:dev": "env -m debug : node dist/main.js", // infers "dev"
"start:qa": "env -m debug : node dist/main.js", // infers "qa"
"start:prod": "env -m debug : node dist/main.js", // infers "prod"
},
}
The inferred value is validated against the environments defined in the
workspace, discovered dynamically as the union of:
- the __INLINE_CODE_94__ and __INLINE_CODE_95__ section keys of __INLINE_CODE_96__,
- per-env provider files in the root folder (__INLINE_CODE_97__,
__INLINE_CODE_98__, __INLINE_CODE_99__, __INLINE_CODE_100__).
Validation rules:
- inferred env unknown → the command aborts listing the defined
environments (catches typos like __INLINE_CODE_101__);
- explicit __INLINE_CODE_102__ unknown → only a warning;
- no defined environments → inference is skipped silently;
- scripts without a __INLINE_CODE_103__ suffix (i.e. __INLINE_CODE_104__) and direct CLI invocations
are unaffected.
Scripts whose suffix is not an environment (i.e. __INLINE_CODE_105__,
__INLINE_CODE_106__) must keep an explicit __INLINE_CODE_107__.
__INLINE_CODE_108__ inside __INLINE_CODE_109__ cannot reference an inferred environment —
the config file is loaded before inference runs.
__INLINE_CODE_110__
Inject environment variables into __INLINE_CODE_111__ and execute a command (the
command goes after __INLINE_CODE_112__).
env -e <env> [options..] [: <subcmd> :] [options..]
> env -e dev -m test unit : npm test
> env -e dev -m debug : npm start : -c [[root]]/[[env]].env.json
> env -e prod -m build optimize : npm run build
Option
Description
Type
Default
__INLINE_CODE_113__
Validate variables against the JSON schema
__INLINE_CODE_114__
__INLINE_CODE_115__
__INLINE_CODE_116__
Pull environment variables from the providers' stores (for providers that
implement __INLINE_CODE_117__, i.e. custom remote providers).
env pull -e <env> [options..]
Option
Description
Type
Default
__INLINE_CODE_118__
Overwrite local variables
__INLINE_CODE_119__
__INLINE_CODE_120__
__INLINE_CODE_121__
Push environment variables to the providers' stores (for providers that
implement __INLINE_CODE_122__).
env push -e <env> [options..]
Option
Description
Type
Default
__INLINE_CODE_123__
Force push for secrets
__INLINE_CODE_124__
__INLINE_CODE_125__
__INLINE_CODE_126__
Generate (or update) the validation schema from the providers' output.
> env schema -e dev -m build
__INLINE_CODE_127__
Export the unified environment to a file.
env export -e <env> -m <modes> [options..]
Option
Description
Type
Default
__INLINE_CODE_128__
Output file path
__INLINE_CODE_129__
__INLINE_CODE_130__
__INLINE_CODE_131__
Output format
__INLINE_CODE_132__/__INLINE_CODE_133__
__INLINE_CODE_134__
__INLINE_CODE_135__
Wrap values in quotes
__INLINE_CODE_136__
__INLINE_CODE_137__
> env export -e dev -m build -f json --uri [[env]].env.json
Providers
Providers are the core of this library. It ships with four integrated
providers, and you can add your own.
__INLINE_CODE_138__
Loads project info from your __INLINE_CODE_139__ (__INLINE_CODE_140__, __INLINE_CODE_141__, __INLINE_CODE_142__,
__INLINE_CODE_143__, __INLINE_CODE_144__) into __INLINE_CODE_145__, __INLINE_CODE_146__, __INLINE_CODE_147__, __INLINE_CODE_148__, __INLINE_CODE_149__,
__INLINE_CODE_150__.
Option
Description
Type
Default
__INLINE_CODE_151__
Prefix for the loaded variables
__INLINE_CODE_152__
__INLINE_CODE_153__
# i.e. expose them as REACT_APP_* for CRA
> env -e dev -m build : react-scripts build : --vp REACT_APP_
__INLINE_CODE_154__
Non-secret loader for __INLINE_CODE_155__, organized by sections:
{
"|DEFAULT|": { "VAR1": "v1_default" },
"|ENV|": {
"dev": {
"C1": "V1",
"GROUP1": { "VAR2": "G1V2", "GROUP2": { "VAR1": "G1G2V1" } },
},
},
"|MODE|": {
"build": { "NODE_ENV": "production" },
"debug": { "NODE_ENV": "development" },
},
"|LOCAL|": { "dev": { "LOCAL_VAR": "only-local" } },
}
It also merges the unitary files __INLINE_CODE_156__,
__INLINE_CODE_157__ and __INLINE_CODE_158__.
Precedence (lowest → highest)
- flat root object (only when no __INLINE_CODE_159__ section is present)
- __INLINE_CODE_160__
- __INLINE_CODE_161__ for the current __INLINE_CODE_162__
- __INLINE_CODE_163__ for each __INLINE_CODE_164__ entry (later modes win)
- __INLINE_CODE_165__
- __INLINE_CODE_166__ (one per mode, in __INLINE_CODE_167__ order)
- __INLINE_CODE_168__ for the current __INLINE_CODE_169__
- __INLINE_CODE_170__
Local layers (7 and 8) always win, and both are skipped in __INLINE_CODE_171__.
Option
Description
Type
Default
__INLINE_CODE_172__
Non-secret settings file path
__INLINE_CODE_173__
__INLINE_CODE_174__
__INLINE_CODE_175__
Loads secret variables from a per-environment JSON file
(__INLINE_CODE_176__). Keep this file out of version control.
Option
Description
Type
Default
__INLINE_CODE_177__
Secret variables file path
__INLINE_CODE_178__
__INLINE_CODE_179__
__INLINE_CODE_180__
Loads local-only variables (never loaded in __INLINE_CODE_181__). The file is auto-created
if missing.
Option
Description
Type
Default
__INLINE_CODE_182__
Local variables file path
__INLINE_CODE_183__
__INLINE_CODE_184__
Creating custom providers
Create a provider in two ways:
- Local script — a __INLINE_CODE_185__ file that __INLINE_CODE_186__s your provider.
- NPM package — a published module that __INLINE_CODE_187__s your provider.
Both are wired in the config file via the __INLINE_CODE_188__ list. A custom
provider can also implement __INLINE_CODE_189__/__INLINE_CODE_190__ to fetch and publish variables from a
remote store (i.e. a vault, a secrets manager or an API).
import type { CommandArguments, EnvProvider } from '@calvear/env';
import { logger, readJson, writeJson } from '@calvear/env/utils';
const KEY = 'my-unique-provider-key';
interface MyProviderArguments extends CommandArguments {
anyExtraOption: boolean;
}
const MyProvider: EnvProvider<MyProviderArguments> = {
// unique identifier
key: KEY,
// (optional) add custom options to the CLI via yargs
builder: (builder) => {
builder.options({
anyExtraOption: {
group: KEY,
alias: ['a', 'aeo'],
type: 'boolean',
default: false,
describe: 'Any option description',
},
});
},
// called on load — may be sync or async, and may return a list to merge
load: ({ env }) => {
if (env === 'dev') return { NODE_ENV: 'development' };
return [{ NODE_ENV: 'production' }, { ANY_GROUP: { INNER_VAR: 12 } }];
},
// (optional) called on `env pull`
pull: (argv, config) => {
/* fetch variables into your local cache */
},
// (optional) called on `env push`
push: (argv, config) => {
/* publish/update your variables */
},
};
export default MyProvider;
Config
Any CLI argument can be set in your config file
(__INLINE_CODE_191__ by default), but it is mainly used to declare
providers:
{
"logLevel": "silly",
// mask secrets in the debug output (see the Masking section)
"logMaskValuesOfKeys": ["SECRET", "/token/i", "/api_key/i"],
"logMaskAnyRegEx": ["AKIA[0-9A-Z]{16}"],
"providers": [
{ "path": "package-json" },
{ "path": "app-settings" },
{ "path": "secrets" },
{ "path": "local" },
// custom NPM package
{ "path": "@my-scope/my-provider", "type": "module", "config": {} },
// custom local script
{ "path": "scripts/custom-loader.js", "type": "script" },
],
}
Provider order matters — providers are merged in declaration order, so
later providers override earlier ones (__INLINE_CODE_192__ is the base, __INLINE_CODE_193__
wins).
Nested & global keys
Organize variables in nested objects. They are flattened into __INLINE_CODE_194__
using the nesting delimiter (__INLINE_CODE_195__ by default):
{
"GROUP1": {
"VAR": "anyValue1",
"GROUP2": { "VAR": "anyValue2" },
},
"VAR3": "anyValue3",
}
process.env.GROUP1__VAR; // "anyValue1"
process.env.GROUP1__GROUP2__VAR; // "anyValue2"
process.env.VAR3; // "anyValue3"
__INLINE_CODE_196__ global marker
Prefix a key with __INLINE_CODE_197__ to mark it as global/shared — relevant for the
__INLINE_CODE_198__ provider, which uses the marker to scope the secret across the project
rather than per-mode. The marker is stripped on injection at any nesting
depth, while the group prefix is kept:
{
"$TOKEN": "rootValue",
"GROUP1": {
"$SHARED": "groupValue",
"VAR": "anyValue",
},
}
is removed; the nesting prefix is preserved
process.env.TOKEN; // "rootValue" (was $TOKEN)
process.env.GROUP1__SHARED; // "groupValue" (was GROUP1.$SHARED)
process.env.GROUP1__VAR; // "anyValue"
The
$is removed only from the process environment and from JSON Schema validation keys. It is preserved in the secrets file storage, so thesecretsprovider round-trips the global marker intact.Skip a key entirely (never injected) by prefixing it with
#.
Priority (lowest → highest)
Providers are merged in declaration order — with the default providers list:
NODE_ENV=development(built-in base value)package-jsoninfoappsettings.json(app-settings— see the app-settings precedence above)<env>.env.json(secrets)<env>.local.env.json(local, skipped in--ci— overrides everything)
The resolved variables are written into the child process
process.env, so they override any variables inherited from the parent shell.
Masking secrets
When the resolved environment is rendered (at --log debug) and when objects
are logged, secrets are masked as *****. There are two complementary
mechanisms, configurable via CLI flags or the config file:
By key — logMaskValuesOfKeys (--mvk)
Masks a variable's whole value when its key matches. Each entry is
either an exact key name (case-insensitive) or a /source/flags regex
matched against the key:
{
"logMaskValuesOfKeys": [
"PASSWORD", // exact key (case-insensitive)
"/token/i", // any key containing "token"
"/_secret$/i", // any key ending in "_secret"
],
}
# same, via CLI flag
> env -e dev --log debug --mvk PASSWORD "/token/i" : node app.js
By value content — logMaskAnyRegEx (--mrx)
Masks only the matching portion of any string value, wherever it appears.
The global flag is always forced, so every occurrence is masked; use the
/source/flags form for extra flags such as i:
{
"logMaskAnyRegEx": [
"AKIA[0-9A-Z]{16}", // AWS access key ids
"/bearer .+/i", // bearer tokens (case-insensitive)
],
}
| Goal | Use |
|---|---|
| Know the key of the secret | logMaskValuesOfKeys |
| Know the shape of the secret (token, key, RUT…) | logMaskAnyRegEx |
In JSON the backslash must be escaped: write
\\dfor\d. Neither mechanism masks the variable name — keys are always shown. Masking only affects the output; the real (unmasked) values are still injected intoprocess.env.
Development scripts
| Script | Description |
|---|---|
pnpm build |
Build the library (Vite, ESM) into dist/ |
pnpm test |
Run unit tests (Vitest) |
pnpm test:cov |
Run unit tests with coverage (100% threshold) |
pnpm test:int |
Run integration tests against the built binary |
pnpm typecheck |
Type-check with tsc --noEmit |
pnpm lint |
Lint with ESLint (flat config) |
pnpm format |
Format with Prettier |
pnpm run pub |
Gate + build + publish to npm (latest) |
pnpm run pub:alpha |
Gate + build + publish a prerelease (alpha tag) |
Built with
- yargs — CLI argument parsing
- ajv + to-json-schema — JSON Schema
- tslog — logging
- picocolors — terminal colors
- subslate — interpolation
- merge-deep — deep merge
- Vite — build · Vitest — testing
License
This project is licensed under the MIT License — see LICENSE.md for details.