npm.io
3.4.2 • Published 1 week agoCLI

@calvear/env

Licence
MIT
Version
3.4.2
Deps
9
Size
211 kB
Vulns
0
Weekly
0
@calvear/env logo

env

Environment variables made easy — load, validate, inject.

version   esm   typescript   node engine   coverage   license


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, secrets and local providers, plus your own (NPM package or local script).
  • Injection — load variables into process.env and run any command.
  • JSON Schema — auto-generate and validate variables before injecting.
  • Nested & global keysGROUP__VAR flattening 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 .env or JSON file.

(back to top)

Requirements

NodeJS 20 or higher.

> node -v
v20.0.0

(back to top)

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');

(back to top)

️ 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, booleans true/false in green/red.

(back to top)

Commands & Options

Interpolation — any option value can reference other arguments using [[ and ]] delimiters. With root: "config", the value [[root]]/file.json resolves to config/file.json; with env: "dev", [[root]]/config.[[env]].json resolves to config/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 of appsettings.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 -e unknown → 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 --configFile cannot 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

(back to top)

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)
  1. flat root object (only when no |...| section is present)
  2. |DEFAULT|
  3. |ENV| for the current --env
  4. |MODE| for each --modes entry (later modes win)
  5. appsettings.<env>.json
  6. appsettings.<mode>.json (one per mode, in --modes order)
  7. |LOCAL| for the current --env
  8. appsettings.<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

(back to top)

Creating custom providers

Create a provider in two ways:

  • Local script — a .js file that export 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;

(back to top)

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-json is the base, local wins).

(back to top)

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 `
@calvear/env logo

env

Environment variables made easy — load, validate, inject.

version   esm   typescript   node engine   coverage   license


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.

(back to top)

Requirements

NodeJS 20 or higher.

> node -v
v20.0.0

(back to top)

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');

(back to top)

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

(back to top)

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

(back to top)

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)
  1. flat root object (only when no __INLINE_CODE_159__ section is present)
  2. __INLINE_CODE_160__
  3. __INLINE_CODE_161__ for the current __INLINE_CODE_162__
  4. __INLINE_CODE_163__ for each __INLINE_CODE_164__ entry (later modes win)
  5. __INLINE_CODE_165__
  6. __INLINE_CODE_166__ (one per mode, in __INLINE_CODE_167__ order)
  7. __INLINE_CODE_168__ for the current __INLINE_CODE_169__
  8. __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__

(back to top)

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;

(back to top)

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

(back to top)

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 the secrets provider 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:

  1. NODE_ENV=development (built-in base value)
  2. package-json info
  3. appsettings.json (app-settings — see the app-settings precedence above)
  4. <env>.env.json (secrets)
  5. <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.

(back to top)

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 \\d for \d. Neither mechanism masks the variable name — keys are always shown. Masking only affects the output; the real (unmasked) values are still injected into process.env.

(back to top)

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)

(back to top)

Built with

(back to top)

License

This project is licensed under the MIT License — see LICENSE.md for details.

(back to top)


with by Alvear Candia, Cristopher Alejandro

Keywords