@calvear/env 3.0.1 โ†’ 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,515 +1,603 @@
1
- <div id="top" align="center">
2
- <img alt="@calvear/env logo" src="assets/logo.svg" width="150" />
3
-
4
- <h1 align="center"><b>env</b></h1>
5
- <h4 align="center">Environment variables made easy โ€” load, validate, inject.</h4>
6
-
7
- <p align="center">
8
- <img src="https://img.shields.io/npm/v/@calvear/env?style=flat-square&color=2563eb&label=version" alt="version" />
9
- &nbsp;
10
- <img src="https://img.shields.io/badge/module-ESM-f59e0b?style=flat-square" alt="esm" />
11
- &nbsp;
12
- <img src="https://img.shields.io/badge/TypeScript-007ACC?style=flat-square&logo=typescript&logoColor=white" alt="typescript" />
13
- &nbsp;
14
- <img src="https://img.shields.io/badge/node->=20-339933?style=flat-square&logo=node.js&logoColor=white" alt="node engine" />
15
- &nbsp;
16
- <img src="https://img.shields.io/badge/coverage-100%25-22c55e?style=flat-square" alt="coverage" />
17
- &nbsp;
18
- <img src="https://img.shields.io/npm/l/@calvear/env?style=flat-square&color=eab308" alt="license" />
19
- </p>
20
- </div>
21
-
22
- <br />
23
-
24
- <!-- ABOUT -->
25
-
26
- ## ๐Ÿ“– About
27
-
28
- `@calvear/env` eases **environment variable handling** for NodeJS apps โ€” like
29
- [env-cmd](https://www.npmjs.com/package/env-cmd) or
30
- [dotenv](https://www.npmjs.com/package/dotenv), but with **powerful, extensible
31
- features**: pluggable **providers** to `load`, `pull` and `push` variables from
32
- different stores, **JSON Schema validation**, value **interpolation**, secret
33
- **masking** and nested/shared keys.
34
-
35
- ### โœจ Features
36
-
37
- - ๐Ÿงฉ **Provider plugins** โ€” bundled `package-json`, `app-settings`, `secrets`
38
- and `local` providers, plus your own (NPM package or local script).
39
- - ๐Ÿ’‰ **Injection** โ€” load variables into `process.env` and run any command.
40
- - โœ… **JSON Schema** โ€” auto-generate and validate variables before injecting.
41
- - ๐Ÿช† **Nested & shared keys** โ€” `GROUP__VAR` flattening and `$`-prefixed shared
42
- keys.
43
- - ๐Ÿงต **Interpolation** โ€” reference other args/vars with `[[ ]]` delimiters.
44
- - ๐Ÿ™ˆ **Masking** โ€” hide secret values in logs.
45
- - ๐Ÿ“ค **Export** โ€” write the unified environment to a `.env` or JSON file.
46
-
47
- <p align="right">(<a href="#top">back to top</a>)</p>
48
-
49
- <!-- REQUIREMENTS -->
50
-
51
- ## ๐Ÿ“Œ Requirements
52
-
53
- [NodeJS](https://nodejs.org/) **20 or higher**.
54
-
55
- ```bash
56
- > node -v
57
- v20.0.0
58
- ```
59
-
60
- <p align="right">(<a href="#top">back to top</a>)</p>
61
-
62
- <!-- ESM -->
63
-
64
- ## ๐Ÿ“ฆ ESM only
65
-
66
- Since **v3** this package is **ESM-only** (`"type": "module"`). Consumers must
67
- use ESM `import` syntax, and custom providers must be ESM modules that `export`
68
- their provider object.
69
-
70
- ```javascript
71
- // โœ… ESM
72
- import { EnvProvider } from '@calvear/env';
73
-
74
- // โŒ CommonJS require is not supported
75
- // const { EnvProvider } = require('@calvear/env');
76
- ```
77
-
78
- <p align="right">(<a href="#top">back to top</a>)</p>
79
-
80
- <!-- QUICK START -->
81
-
82
- ## โšก๏ธ Quick start
83
-
84
- Install the package:
85
-
86
- ```bash
87
- > npm install @calvear/env
88
- ```
89
-
90
- Run the binary directly:
91
-
92
- ```bash
93
- > npx env --help
94
-
95
- Usage: env [command] [options..] [: subcmd [:]] [options..]
96
-
97
- Commands:
98
- env [options..] [: <subcmd> :]
99
- env pull [options..]
100
- env push [options..]
101
- env schema [options..]
102
- env export [options..]
103
- ```
104
-
105
- Or wire it into your **npm scripts**:
106
-
107
- ```jsonc
108
- {
109
- "scripts": {
110
- // inject "dev" variables (debug mode) and start the app
111
- "start:dev": "env -e dev -m debug : node dist/main.js",
112
- // inject "prod" variables for the build
113
- "build:prod": "env -e prod -m build : tsc",
114
- // regenerate the validation schema
115
- "env:schema": "env schema -e dev",
116
- },
117
- }
118
- ```
119
-
120
- Run it:
121
-
122
- ```bash
123
- > npm run start:dev
124
-
125
- โšก env v3.0.0 ยท ๐ŸŒŽ dev ยท ๐Ÿงฉ debug
126
-
127
- ๐Ÿ“ฆ package-json 6 vars
128
- ๐Ÿ—‚๏ธ app-settings 5 vars
129
- ๐Ÿ” secrets 1 secrets
130
- ๐Ÿ“‚ local 1 vars
131
-
132
- environment (12 variables)
133
- ENV = dev
134
- NODE_ENV = development
135
- SECRET = ***
136
- VERSION = 3.0.0
137
- ...
138
-
139
- โœ“ 12 variables loaded in 142ms
140
-
141
- โ–ถ node dist/main.js
142
- My environment loaded is: dev
143
- โœ“ finished in 168ms
144
- ```
145
-
146
- <p align="right">(<a href="#top">back to top</a>)</p>
147
-
148
- <!-- COMMANDS AND OPTIONS -->
149
-
150
- ## โš™๏ธ Commands & Options
151
-
152
- > **Interpolation** โ€” any option value can reference other arguments using `[[`
153
- > and `]]` delimiters. With `root: "config"`, the value `[[root]]/file.json`
154
- > resolves to `config/file.json`; with `env: "dev"`,
155
- > `[[root]]/config.[[env]].json` resolves to `config/config.dev.json`.
156
-
157
- ### Global options
158
-
159
- | Option | Description | Type | Default |
160
- | ---------------------------------- | ------------------------------------------------- | ---------- | ------- |
161
- | `--help` | Show help | `boolean` | |
162
- | `-e, --env` | Environment to load (i.e. `dev`, `prod`) | `string` | |
163
- | `-m, --modes` | Execution modes (i.e. `debug`, `test`) | `string[]` | `[]` |
164
- | `--nd, --nestingDelimiter` | Nesting-level delimiter for flatten | `string` | `__` |
165
- | `--arrDesc, --arrayDescomposition` | Serialize (`false`) or break down (`true`) arrays | `boolean` | `false` |
166
- | `-x, --expand` | Interpolate environment variables using itself | `boolean` | `false` |
167
- | `--ci` | Continuous Integration mode (skips local files) | `boolean` | auto |
168
-
169
- ### Workspace options
170
-
171
- | Option | Description | Type | Default |
172
- | ---------------------- | --------------------------------- | -------- | --------------------------------- |
173
- | `--root` | Base environment folder path | `string` | `env` |
174
- | `-c, --configFile` | Config JSON file path | `string` | `[[root]]/settings/settings.json` |
175
- | `-s, --schemaFile` | Environment schema JSON file path | `string` | `[[root]]/settings/schema.json` |
176
- | `--pkg, --packageJson` | `package.json` path | `string` | _cwd_ |
177
-
178
- ### JSON Schema options
179
-
180
- | Option | Description | Type | Default |
181
- | ---------------------- | ---------------------------------------------- | ------------------ | ------- |
182
- | `-r, --resolve` | Merge new schema or override it | `merge`/`override` | `merge` |
183
- | `--null, --nullable` | Whether variables are nullable by default | `boolean` | `true` |
184
- | `--df, --detectFormat` | Include string formats in the generated schema | `boolean` | `false` |
185
-
186
- ### Logger options
187
-
188
- | Option | Description | Type | Default |
189
- | ------------------------------ | -------------------------------------- | --------------------------------------------- | ------- |
190
- | `--log, --logLevel` | Log level | `silly`/`trace`/`debug`/`info`/`warn`/`error` | `info` |
191
- | `--mvk, --logMaskValuesOfKeys` | Mask values of the given keys in logs | `string[]` | `[]` |
192
- | `--mrx, --logMaskAnyRegEx` | Mask values matching the given regexes | `string[]` | `[]` |
193
-
194
- ---
195
-
196
- ### `env`
197
-
198
- Inject environment variables into `process.env` and execute a command (the
199
- command goes after `:`).
200
-
201
- ```bash
202
- env -e <env> [options..] [: <subcmd> :] [options..]
203
- ```
204
-
205
- ```bash
206
- > env -e dev -m test unit : npm test
207
- > env -e dev -m debug : npm start : -c [[root]]/[[env]].env.json
208
- > env -e prod -m build optimize : npm run build
209
- ```
210
-
211
- | Option | Description | Type | Default |
212
- | ------------------------------ | ------------------------------------------ | --------- | ------- |
213
- | `--validate, --schemaValidate` | Validate variables against the JSON schema | `boolean` | `true` |
214
-
215
- ### `pull`
216
-
217
- Pull environment variables from the providers' stores (for providers that
218
- implement `pull`, i.e. custom remote providers).
219
-
220
- ```bash
221
- env pull -e <env> [options..]
222
- ```
223
-
224
- | Option | Description | Type | Default |
225
- | ----------------- | ------------------------- | --------- | ------- |
226
- | `-o, --overwrite` | Overwrite local variables | `boolean` | `false` |
227
-
228
- ### `push`
229
-
230
- Push environment variables to the providers' stores (for providers that
231
- implement `push`).
232
-
233
- ```bash
234
- env push -e <env> [options..]
235
- ```
236
-
237
- | Option | Description | Type | Default |
238
- | ------------- | ---------------------- | --------- | ------- |
239
- | `-f, --force` | Force push for secrets | `boolean` | `false` |
240
-
241
- ### `schema`
242
-
243
- Generate (or update) the validation schema from the providers' output.
244
-
245
- ```bash
246
- > env schema -e dev -m build
247
- ```
248
-
249
- ### `export`
250
-
251
- Export the unified environment to a file.
252
-
253
- ```bash
254
- env export -e <env> -m <modes> [options..]
255
- ```
256
-
257
- | Option | Description | Type | Default |
258
- | --------------- | --------------------- | --------------- | -------- |
259
- | `-u, -p, --uri` | Output file path | `string` | `.env` |
260
- | `-f, --format` | Output format | `dotenv`/`json` | `dotenv` |
261
- | `-q, --quotes` | Wrap values in quotes | `boolean` | `false` |
262
-
263
- ```bash
264
- > env export -e dev -m build -f json --uri [[env]].env.json
265
- ```
266
-
267
- <p align="right">(<a href="#top">back to top</a>)</p>
268
-
269
- <!-- PROVIDERS -->
270
-
271
- ## ๐Ÿ“ก Providers
272
-
273
- Providers are the core of this library. It ships with **four integrated
274
- providers**, and you can add your own.
275
-
276
- ### `package-json`
277
-
278
- Loads project info from your `package.json` (`version`, `project`, `name`,
279
- `title`, `description`) into `ENV`, `VERSION`, `PROJECT`, `NAME`, `TITLE`,
280
- `DESCRIPTION`.
281
-
282
- | Option | Description | Type | Default |
283
- | ------------------- | ------------------------------- | -------- | ------- |
284
- | `--vp, --varPrefix` | Prefix for the loaded variables | `string` | `""` |
285
-
286
- ```bash
287
- # i.e. expose them as REACT_APP_* for CRA
288
- > env -e dev -m build : react-scripts build : --vp REACT_APP_
289
- ```
290
-
291
- ### `app-settings`
292
-
293
- Non-secret loader for `appsettings.json`, organized by sections:
294
-
295
- ```jsonc
296
- {
297
- "|DEFAULT|": { "VAR1": "v1_default" },
298
- "|MODE|": {
299
- "build": { "NODE_ENV": "production" },
300
- "debug": { "NODE_ENV": "development" },
301
- },
302
- "|ENV|": {
303
- "dev": {
304
- "C1": "V1",
305
- "GROUP1": { "VAR2": "G1V2", "GROUP2": { "VAR1": "G1G2V1" } },
306
- },
307
- },
308
- "|LOCAL|": { "dev": { "LOCAL_VAR": "only-local" } },
309
- }
310
- ```
311
-
312
- | Option | Description | Type | Default |
313
- | ----------------- | ----------------------------- | -------- | --------------------------- |
314
- | `--ef, --envFile` | Non-secret settings file path | `string` | `[[root]]/appsettings.json` |
315
-
316
- ### `secrets`
317
-
318
- Loads secret variables from a per-environment JSON file
319
- (`[[root]]/[[env]].env.json`). Keep this file out of version control.
320
-
321
- | Option | Description | Type | Default |
322
- | --------------------- | -------------------------- | -------- | --------------------------- |
323
- | `--sf, --secretsFile` | Secret variables file path | `string` | `[[root]]/[[env]].env.json` |
324
-
325
- ### `local`
326
-
327
- Loads local-only variables (never loaded in `--ci`). The file is auto-created
328
- if missing.
329
-
330
- | Option | Description | Type | Default |
331
- | ------------------- | ------------------------- | -------- | --------------------------------- |
332
- | `--lf, --localFile` | Local variables file path | `string` | `[[root]]/[[env]].local.env.json` |
333
-
334
- <p align="right">(<a href="#top">back to top</a>)</p>
335
-
336
- <!-- CUSTOM PROVIDERS -->
337
-
338
- ## โœ’๏ธ Creating custom providers
339
-
340
- Create a provider in two ways:
341
-
342
- - **Local script** โ€” a `.js` file that `export default`s your provider.
343
- - **NPM package** โ€” a published module that `export default`s your provider.
344
-
345
- Both are wired in the [config file](#-config) via the `providers` list. A custom
346
- provider can also implement `pull`/`push` to fetch and publish variables from a
347
- remote store (i.e. a vault, a secrets manager or an API).
348
-
349
- ```typescript
350
- import type { CommandArguments, EnvProvider } from '@calvear/env';
351
- import { logger, readJson, writeJson } from '@calvear/env/utils';
352
-
353
- const KEY = 'my-unique-provider-key';
354
-
355
- interface MyProviderArguments extends CommandArguments {
356
- anyExtraOption: boolean;
357
- }
358
-
359
- const MyProvider: EnvProvider<MyProviderArguments> = {
360
- // unique identifier
361
- key: KEY,
362
-
363
- // (optional) add custom options to the CLI via yargs
364
- builder: (builder) => {
365
- builder.options({
366
- anyExtraOption: {
367
- group: KEY,
368
- alias: ['a', 'aeo'],
369
- type: 'boolean',
370
- default: false,
371
- describe: 'Any option description',
372
- },
373
- });
374
- },
375
-
376
- // called on load โ€” may be sync or async, and may return a list to merge
377
- load: ({ env }) => {
378
- if (env === 'dev') return { NODE_ENV: 'development' };
379
-
380
- return [{ NODE_ENV: 'production' }, { ANY_GROUP: { INNER_VAR: 12 } }];
381
- },
382
-
383
- // (optional) called on `env pull`
384
- pull: (argv, config) => {
385
- /* fetch variables into your local cache */
386
- },
387
-
388
- // (optional) called on `env push`
389
- push: (argv, config) => {
390
- /* publish/update your variables */
391
- },
392
- };
393
-
394
- export default MyProvider;
395
- ```
396
-
397
- <p align="right">(<a href="#top">back to top</a>)</p>
398
-
399
- <!-- CONFIG -->
400
-
401
- ## ๐Ÿ“ฅ Config
402
-
403
- Any CLI argument can be set in your config file
404
- (`[[root]]/settings/settings.json` by default), but it is mainly used to declare
405
- **providers**:
406
-
407
- ```jsonc
408
- {
409
- "log": "silly",
410
- // hide values of these keys in the logs
411
- "logMaskValuesOfKeys": ["SECRET", "MY_API_KEY"],
412
- "providers": [
413
- { "path": "package-json" },
414
- { "path": "app-settings" },
415
- { "path": "secrets" },
416
- { "path": "local" },
417
- // custom NPM package
418
- { "path": "@my-scope/my-provider", "type": "module", "config": {} },
419
- // custom local script
420
- { "path": "scripts/custom-loader.js", "type": "script" },
421
- ],
422
- }
423
- ```
424
-
425
- > **Provider order matters** โ€” providers are merged in declaration order, so
426
- > later providers override earlier ones (`package-json` is the base, `local`
427
- > wins).
428
-
429
- <p align="right">(<a href="#top">back to top</a>)</p>
430
-
431
- <!-- SHARED / NESTED -->
432
-
433
- ## ๐Ÿช† Shared & nested keys
434
-
435
- Organize keys in nested objects. Declare **shared** keys (skipping group
436
- separation) by prefixing them with `$`:
437
-
438
- ```jsonc
439
- {
440
- "$SHARED": "sharedValue",
441
- "GROUP1": {
442
- "$SHARED": "sharedValue2",
443
- "VAR": "anyValue1",
444
- },
445
- "GROUP2": { "SUBGROUP1": { "VAR": "anyValue1" } },
446
- "VAR3": "anyValue3",
447
- }
448
- ```
449
-
450
- Consumed in your app as flattened keys (`__` separator by default):
451
-
452
- ```javascript
453
- process.env.GROUP1__VAR; // "anyValue1"
454
- process.env.GROUP2__SUBGROUP1__VAR; // "anyValue1"
455
- process.env.VAR3; // "anyValue3"
456
- // shared keys drop the `$` and the group prefix
457
- process.env.SHARED; // "sharedValue"
458
- process.env.GROUP1__SHARED; // "sharedValue2"
459
- ```
460
-
461
- ### Priority (lowest โ†’ highest)
462
-
463
- 1. `package-json` info
464
- 2. `appsettings.json` (`app-settings`)
465
- 3. `<env>.env.json` (`secrets`)
466
- 4. `<env>.local.env.json` (`local`, skipped in `--ci`)
467
-
468
- <p align="right">(<a href="#top">back to top</a>)</p>
469
-
470
- <!-- SCRIPTS -->
471
-
472
- ## ๐Ÿงฐ Development scripts
473
-
474
- | Script | Description |
475
- | -------------------- | ------------------------------------------------- |
476
- | `pnpm build` | Build the library (Vite, ESM) into `dist/` |
477
- | `pnpm test` | Run unit tests (Vitest) |
478
- | `pnpm test:cov` | Run unit tests with coverage (100% threshold) |
479
- | `pnpm test:int` | Run integration tests against the built binary |
480
- | `pnpm typecheck` | Type-check with `tsc --noEmit` |
481
- | `pnpm lint` | Lint with ESLint (flat config) |
482
- | `pnpm format` | Format with Prettier |
483
- | `pnpm run pub` | Gate + build + publish to npm (`latest`) |
484
- | `pnpm run pub:alpha` | Gate + build + publish a prerelease (`alpha` tag) |
485
-
486
- <p align="right">(<a href="#top">back to top</a>)</p>
487
-
488
- <!-- BUILT WITH -->
489
-
490
- ## ๐Ÿ› ๏ธ Built with
491
-
492
- - [yargs](http://yargs.js.org/) โ€” CLI argument parsing
493
- - [ajv](https://ajv.js.org/) + [to-json-schema](https://www.npmjs.com/package/to-json-schema) โ€” JSON Schema
494
- - [tslog](https://tslog.js.org/#/) โ€” logging
495
- - [picocolors](https://github.com/alexeyraspopov/picocolors) โ€” terminal colors
496
- - [subslate](https://github.com/josh-hemphill/subslate) โ€” interpolation
497
- - [merge-deep](https://github.com/jonschlinkert/merge-deep) โ€” deep merge
498
- - [Vite](https://vite.dev/) โ€” build ยท [Vitest](https://vitest.dev/) โ€” testing
499
-
500
- <p align="right">(<a href="#top">back to top</a>)</p>
501
-
502
- <!-- LICENSE -->
503
-
504
- ## ๐Ÿ“„ License
505
-
506
- This project is licensed under the **MIT License** โ€” see [LICENSE.md](LICENSE.md)
507
- for details.
508
-
509
- <p align="right">(<a href="#top">back to top</a>)</p>
510
-
511
- ---
512
-
513
- <p align="center">
514
- โŒจ with โค๏ธ by <a href="https://github.com/calvear93">Alvear Candia, Cristopher Alejandro</a>
515
- </p>
1
+ <div id="top" align="center">
2
+ <img alt="@calvear/env logo" src="assets/logo.svg" width="150" />
3
+
4
+ <h1 align="center"><b>env</b></h1>
5
+ <h4 align="center">Environment variables made easy โ€” load, validate, inject.</h4>
6
+
7
+ <p align="center">
8
+ <img src="https://img.shields.io/npm/v/@calvear/env?style=flat-square&color=2563eb&label=version" alt="version" />
9
+ &nbsp;
10
+ <img src="https://img.shields.io/badge/module-ESM-f59e0b?style=flat-square" alt="esm" />
11
+ &nbsp;
12
+ <img src="https://img.shields.io/badge/TypeScript-007ACC?style=flat-square&logo=typescript&logoColor=white" alt="typescript" />
13
+ &nbsp;
14
+ <img src="https://img.shields.io/badge/node->=20-339933?style=flat-square&logo=node.js&logoColor=white" alt="node engine" />
15
+ &nbsp;
16
+ <img src="https://img.shields.io/badge/coverage-100%25-22c55e?style=flat-square" alt="coverage" />
17
+ &nbsp;
18
+ <img src="https://img.shields.io/npm/l/@calvear/env?style=flat-square&color=eab308" alt="license" />
19
+ </p>
20
+ </div>
21
+
22
+ <br />
23
+
24
+ <!-- ABOUT -->
25
+
26
+ ## ๐Ÿ“– About
27
+
28
+ `@calvear/env` eases **environment variable handling** for NodeJS apps โ€” like
29
+ [env-cmd](https://www.npmjs.com/package/env-cmd) or
30
+ [dotenv](https://www.npmjs.com/package/dotenv), but with **powerful, extensible
31
+ features**: pluggable **providers** to `load`, `pull` and `push` variables from
32
+ different stores, **JSON Schema validation**, value **interpolation**, secret
33
+ **masking** and nested/shared keys.
34
+
35
+ ### โœจ Features
36
+
37
+ - ๐Ÿงฉ **Provider plugins** โ€” bundled `package-json`, `app-settings`, `secrets`
38
+ and `local` providers, plus your own (NPM package or local script).
39
+ - ๐Ÿ’‰ **Injection** โ€” load variables into `process.env` and run any command.
40
+ - โœ… **JSON Schema** โ€” auto-generate and validate variables before injecting.
41
+ - ๐Ÿช† **Nested & global keys** โ€” `GROUP__VAR` flattening and `$`-prefixed global
42
+ keys (the `$` marker is stripped on injection).
43
+ - ๐Ÿงต **Interpolation** โ€” reference other args/vars with `[[ ]]` delimiters.
44
+ - ๐Ÿ™ˆ **Masking** โ€” hide secrets in the debug output by key name, key regex, or
45
+ value content regex.
46
+ - ๐ŸŽจ **Pretty output** โ€” colorized, sorted, masked debug render of the resolved
47
+ environment.
48
+ - ๐Ÿ“ค **Export** โ€” write the unified environment to a `.env` or JSON file.
49
+
50
+ <p align="right">(<a href="#top">back to top</a>)</p>
51
+
52
+ <!-- REQUIREMENTS -->
53
+
54
+ ## ๐Ÿ“Œ Requirements
55
+
56
+ [NodeJS](https://nodejs.org/) **20 or higher**.
57
+
58
+ ```bash
59
+ > node -v
60
+ v20.0.0
61
+ ```
62
+
63
+ <p align="right">(<a href="#top">back to top</a>)</p>
64
+
65
+ <!-- ESM -->
66
+
67
+ ## ๐Ÿ“ฆ ESM only
68
+
69
+ Since **v3** this package is **ESM-only** (`"type": "module"`). Consumers must
70
+ use ESM `import` syntax, and custom providers must be ESM modules that `export`
71
+ their provider object.
72
+
73
+ ```javascript
74
+ // โœ… ESM
75
+ import { EnvProvider } from '@calvear/env';
76
+
77
+ // โŒ CommonJS require is not supported
78
+ // const { EnvProvider } = require('@calvear/env');
79
+ ```
80
+
81
+ <p align="right">(<a href="#top">back to top</a>)</p>
82
+
83
+ <!-- QUICK START -->
84
+
85
+ ## โšก๏ธ Quick start
86
+
87
+ Install the package:
88
+
89
+ ```bash
90
+ > npm install @calvear/env
91
+ ```
92
+
93
+ Run the binary directly:
94
+
95
+ ```bash
96
+ > npx env --help
97
+
98
+ Usage: env [command] [options..] [: subcmd [:]] [options..]
99
+
100
+ Commands:
101
+ env [options..] [: <subcmd> :]
102
+ env pull [options..]
103
+ env push [options..]
104
+ env schema [options..]
105
+ env export [options..]
106
+ ```
107
+
108
+ Or wire it into your **npm scripts**:
109
+
110
+ ```jsonc
111
+ {
112
+ "scripts": {
113
+ // inject "dev" variables (debug mode) and start the app
114
+ "start:dev": "env -e dev -m debug : node dist/main.js",
115
+ // inject "prod" variables for the build
116
+ "build:prod": "env -e prod -m build : tsc",
117
+ // regenerate the validation schema
118
+ "env:schema": "env schema -e dev",
119
+ },
120
+ }
121
+ ```
122
+
123
+ Run it:
124
+
125
+ ```bash
126
+ > npm run start:dev
127
+
128
+ โšก env v3.0.0 ยท ๐ŸŒŽ dev ยท ๐Ÿงฉ debug
129
+
130
+ ๐Ÿ“ฆ package-json 6 vars
131
+ ๐Ÿ—‚๏ธ app-settings 5 vars
132
+ ๐Ÿ” secrets 1 secrets
133
+ ๐Ÿ“‚ local 1 vars
134
+
135
+ environment (12 variables)
136
+ ENV = dev
137
+ NODE_ENV = development
138
+ SECRET = *****
139
+ VERSION = 3.0.0
140
+ ...
141
+
142
+ โœ“ 12 variables loaded in 142ms
143
+
144
+ โ–ถ node dist/main.js
145
+ My environment loaded is: dev
146
+ โœ“ finished in 168ms
147
+ ```
148
+
149
+ > The resolved environment is only rendered at `--log debug`. Values are
150
+ > **sorted**, **secrets are masked**, and colored by type โ€” strings in gray,
151
+ > numbers in orange, booleans `true`/`false` in green/red.
152
+
153
+ <p align="right">(<a href="#top">back to top</a>)</p>
154
+
155
+ <!-- COMMANDS AND OPTIONS -->
156
+
157
+ ## โš™๏ธ Commands & Options
158
+
159
+ > **Interpolation** โ€” any option value can reference other arguments using `[[`
160
+ > and `]]` delimiters. With `root: "config"`, the value `[[root]]/file.json`
161
+ > resolves to `config/file.json`; with `env: "dev"`,
162
+ > `[[root]]/config.[[env]].json` resolves to `config/config.dev.json`.
163
+
164
+ ### Global options
165
+
166
+ | Option | Description | Type | Default |
167
+ | ---------------------------------- | ------------------------------------------------- | ---------- | ------- |
168
+ | `--help` | Show help | `boolean` | |
169
+ | `-e, --env` | Environment to load (i.e. `dev`, `prod`) | `string` | |
170
+ | `-m, --modes` | Execution modes (i.e. `debug`, `test`) | `string[]` | `[]` |
171
+ | `--nd, --nestingDelimiter` | Nesting-level delimiter for flatten | `string` | `__` |
172
+ | `--arrDesc, --arrayDescomposition` | Serialize (`false`) or break down (`true`) arrays | `boolean` | `false` |
173
+ | `-x, --expand` | Interpolate environment variables using itself | `boolean` | `false` |
174
+ | `--ci` | Continuous Integration mode (skips local files) | `boolean` | auto |
175
+
176
+ ### Workspace options
177
+
178
+ | Option | Description | Type | Default |
179
+ | ---------------------- | --------------------------------- | -------- | --------------------------------- |
180
+ | `--root` | Base environment folder path | `string` | `env` |
181
+ | `-c, --configFile` | Config JSON file path | `string` | `[[root]]/settings/settings.json` |
182
+ | `-s, --schemaFile` | Environment schema JSON file path | `string` | `[[root]]/settings/schema.json` |
183
+ | `--pkg, --packageJson` | `package.json` path | `string` | _cwd_ |
184
+
185
+ ### JSON Schema options
186
+
187
+ | Option | Description | Type | Default |
188
+ | ---------------------- | ---------------------------------------------- | ------------------ | ------- |
189
+ | `-r, --resolve` | Merge new schema or override it | `merge`/`override` | `merge` |
190
+ | `--null, --nullable` | Whether variables are nullable by default | `boolean` | `true` |
191
+ | `--df, --detectFormat` | Include string formats in the generated schema | `boolean` | `false` |
192
+
193
+ ### Logger options
194
+
195
+ | Option | Description | Type | Default |
196
+ | ------------------------------ | ------------------------------------------------------ | --------------------------------------------- | ------- |
197
+ | `--log, --logLevel` | Log level | `silly`/`trace`/`debug`/`info`/`warn`/`error` | `info` |
198
+ | `--mvk, --logMaskValuesOfKeys` | Mask a value when its **key** matches (exact or regex) | `string[]` | `[]` |
199
+ | `--mrx, --logMaskAnyRegEx` | Mask **value content** matching a regex (every match) | `string[]` | `[]` |
200
+
201
+ See [๐Ÿ™ˆ Masking secrets](#-masking-secrets) for the full masking semantics.
202
+
203
+ ---
204
+
205
+ ### `env`
206
+
207
+ Inject environment variables into `process.env` and execute a command (the
208
+ command goes after `:`).
209
+
210
+ ```bash
211
+ env -e <env> [options..] [: <subcmd> :] [options..]
212
+ ```
213
+
214
+ ```bash
215
+ > env -e dev -m test unit : npm test
216
+ > env -e dev -m debug : npm start : -c [[root]]/[[env]].env.json
217
+ > env -e prod -m build optimize : npm run build
218
+ ```
219
+
220
+ | Option | Description | Type | Default |
221
+ | ------------------------------ | ------------------------------------------ | --------- | ------- |
222
+ | `--validate, --schemaValidate` | Validate variables against the JSON schema | `boolean` | `true` |
223
+
224
+ ### `pull`
225
+
226
+ Pull environment variables from the providers' stores (for providers that
227
+ implement `pull`, i.e. custom remote providers).
228
+
229
+ ```bash
230
+ env pull -e <env> [options..]
231
+ ```
232
+
233
+ | Option | Description | Type | Default |
234
+ | ----------------- | ------------------------- | --------- | ------- |
235
+ | `-o, --overwrite` | Overwrite local variables | `boolean` | `false` |
236
+
237
+ ### `push`
238
+
239
+ Push environment variables to the providers' stores (for providers that
240
+ implement `push`).
241
+
242
+ ```bash
243
+ env push -e <env> [options..]
244
+ ```
245
+
246
+ | Option | Description | Type | Default |
247
+ | ------------- | ---------------------- | --------- | ------- |
248
+ | `-f, --force` | Force push for secrets | `boolean` | `false` |
249
+
250
+ ### `schema`
251
+
252
+ Generate (or update) the validation schema from the providers' output.
253
+
254
+ ```bash
255
+ > env schema -e dev -m build
256
+ ```
257
+
258
+ ### `export`
259
+
260
+ Export the unified environment to a file.
261
+
262
+ ```bash
263
+ env export -e <env> -m <modes> [options..]
264
+ ```
265
+
266
+ | Option | Description | Type | Default |
267
+ | --------------- | --------------------- | --------------- | -------- |
268
+ | `-u, -p, --uri` | Output file path | `string` | `.env` |
269
+ | `-f, --format` | Output format | `dotenv`/`json` | `dotenv` |
270
+ | `-q, --quotes` | Wrap values in quotes | `boolean` | `false` |
271
+
272
+ ```bash
273
+ > env export -e dev -m build -f json --uri [[env]].env.json
274
+ ```
275
+
276
+ <p align="right">(<a href="#top">back to top</a>)</p>
277
+
278
+ <!-- PROVIDERS -->
279
+
280
+ ## ๐Ÿ“ก Providers
281
+
282
+ Providers are the core of this library. It ships with **four integrated
283
+ providers**, and you can add your own.
284
+
285
+ ### `package-json`
286
+
287
+ Loads project info from your `package.json` (`version`, `project`, `name`,
288
+ `title`, `description`) into `ENV`, `VERSION`, `PROJECT`, `NAME`, `TITLE`,
289
+ `DESCRIPTION`.
290
+
291
+ | Option | Description | Type | Default |
292
+ | ------------------- | ------------------------------- | -------- | ------- |
293
+ | `--vp, --varPrefix` | Prefix for the loaded variables | `string` | `""` |
294
+
295
+ ```bash
296
+ # i.e. expose them as REACT_APP_* for CRA
297
+ > env -e dev -m build : react-scripts build : --vp REACT_APP_
298
+ ```
299
+
300
+ ### `app-settings`
301
+
302
+ Non-secret loader for `appsettings.json`, organized by sections:
303
+
304
+ ```jsonc
305
+ {
306
+ "|DEFAULT|": { "VAR1": "v1_default" },
307
+ "|MODE|": {
308
+ "build": { "NODE_ENV": "production" },
309
+ "debug": { "NODE_ENV": "development" },
310
+ },
311
+ "|ENV|": {
312
+ "dev": {
313
+ "C1": "V1",
314
+ "GROUP1": { "VAR2": "G1V2", "GROUP2": { "VAR1": "G1G2V1" } },
315
+ },
316
+ },
317
+ "|LOCAL|": { "dev": { "LOCAL_VAR": "only-local" } },
318
+ }
319
+ ```
320
+
321
+ | Option | Description | Type | Default |
322
+ | ----------------- | ----------------------------- | -------- | --------------------------- |
323
+ | `--ef, --envFile` | Non-secret settings file path | `string` | `[[root]]/appsettings.json` |
324
+
325
+ ### `secrets`
326
+
327
+ Loads secret variables from a per-environment JSON file
328
+ (`[[root]]/[[env]].env.json`). Keep this file out of version control.
329
+
330
+ | Option | Description | Type | Default |
331
+ | --------------------- | -------------------------- | -------- | --------------------------- |
332
+ | `--sf, --secretsFile` | Secret variables file path | `string` | `[[root]]/[[env]].env.json` |
333
+
334
+ ### `local`
335
+
336
+ Loads local-only variables (never loaded in `--ci`). The file is auto-created
337
+ if missing.
338
+
339
+ | Option | Description | Type | Default |
340
+ | ------------------- | ------------------------- | -------- | --------------------------------- |
341
+ | `--lf, --localFile` | Local variables file path | `string` | `[[root]]/[[env]].local.env.json` |
342
+
343
+ <p align="right">(<a href="#top">back to top</a>)</p>
344
+
345
+ <!-- CUSTOM PROVIDERS -->
346
+
347
+ ## โœ’๏ธ Creating custom providers
348
+
349
+ Create a provider in two ways:
350
+
351
+ - **Local script** โ€” a `.js` file that `export default`s your provider.
352
+ - **NPM package** โ€” a published module that `export default`s your provider.
353
+
354
+ Both are wired in the [config file](#-config) via the `providers` list. A custom
355
+ provider can also implement `pull`/`push` to fetch and publish variables from a
356
+ remote store (i.e. a vault, a secrets manager or an API).
357
+
358
+ ```typescript
359
+ import type { CommandArguments, EnvProvider } from '@calvear/env';
360
+ import { logger, readJson, writeJson } from '@calvear/env/utils';
361
+
362
+ const KEY = 'my-unique-provider-key';
363
+
364
+ interface MyProviderArguments extends CommandArguments {
365
+ anyExtraOption: boolean;
366
+ }
367
+
368
+ const MyProvider: EnvProvider<MyProviderArguments> = {
369
+ // unique identifier
370
+ key: KEY,
371
+
372
+ // (optional) add custom options to the CLI via yargs
373
+ builder: (builder) => {
374
+ builder.options({
375
+ anyExtraOption: {
376
+ group: KEY,
377
+ alias: ['a', 'aeo'],
378
+ type: 'boolean',
379
+ default: false,
380
+ describe: 'Any option description',
381
+ },
382
+ });
383
+ },
384
+
385
+ // called on load โ€” may be sync or async, and may return a list to merge
386
+ load: ({ env }) => {
387
+ if (env === 'dev') return { NODE_ENV: 'development' };
388
+
389
+ return [{ NODE_ENV: 'production' }, { ANY_GROUP: { INNER_VAR: 12 } }];
390
+ },
391
+
392
+ // (optional) called on `env pull`
393
+ pull: (argv, config) => {
394
+ /* fetch variables into your local cache */
395
+ },
396
+
397
+ // (optional) called on `env push`
398
+ push: (argv, config) => {
399
+ /* publish/update your variables */
400
+ },
401
+ };
402
+
403
+ export default MyProvider;
404
+ ```
405
+
406
+ <p align="right">(<a href="#top">back to top</a>)</p>
407
+
408
+ <!-- CONFIG -->
409
+
410
+ ## ๐Ÿ“ฅ Config
411
+
412
+ Any CLI argument can be set in your config file
413
+ (`[[root]]/settings/settings.json` by default), but it is mainly used to declare
414
+ **providers**:
415
+
416
+ ```jsonc
417
+ {
418
+ "logLevel": "silly",
419
+ // mask secrets in the debug output (see the Masking section)
420
+ "logMaskValuesOfKeys": ["SECRET", "/token/i", "/api_key/i"],
421
+ "logMaskAnyRegEx": ["AKIA[0-9A-Z]{16}"],
422
+ "providers": [
423
+ { "path": "package-json" },
424
+ { "path": "app-settings" },
425
+ { "path": "secrets" },
426
+ { "path": "local" },
427
+ // custom NPM package
428
+ { "path": "@my-scope/my-provider", "type": "module", "config": {} },
429
+ // custom local script
430
+ { "path": "scripts/custom-loader.js", "type": "script" },
431
+ ],
432
+ }
433
+ ```
434
+
435
+ > **Provider order matters** โ€” providers are merged in declaration order, so
436
+ > later providers override earlier ones (`package-json` is the base, `local`
437
+ > wins).
438
+
439
+ <p align="right">(<a href="#top">back to top</a>)</p>
440
+
441
+ <!-- SHARED / NESTED -->
442
+
443
+ ## ๐Ÿช† Nested & global keys
444
+
445
+ Organize variables in nested objects. They are **flattened** into `process.env`
446
+ using the nesting delimiter (`__` by default):
447
+
448
+ ```jsonc
449
+ {
450
+ "GROUP1": {
451
+ "VAR": "anyValue1",
452
+ "GROUP2": { "VAR": "anyValue2" },
453
+ },
454
+ "VAR3": "anyValue3",
455
+ }
456
+ ```
457
+
458
+ ```javascript
459
+ process.env.GROUP1__VAR; // "anyValue1"
460
+ process.env.GROUP1__GROUP2__VAR; // "anyValue2"
461
+ process.env.VAR3; // "anyValue3"
462
+ ```
463
+
464
+ ### `$` global marker
465
+
466
+ Prefix a key with `$` to mark it as **global/shared** โ€” relevant for the
467
+ `secrets` provider, which uses the marker to scope the secret across the project
468
+ rather than per-mode. The marker is **stripped on injection** at any nesting
469
+ depth, while the group prefix is **kept**:
470
+
471
+ ```jsonc
472
+ {
473
+ "$TOKEN": "rootValue",
474
+ "GROUP1": {
475
+ "$SHARED": "groupValue",
476
+ "VAR": "anyValue",
477
+ },
478
+ }
479
+ ```
480
+
481
+ ```javascript
482
+ // the `$` is removed; the nesting prefix is preserved
483
+ process.env.TOKEN; // "rootValue" (was $TOKEN)
484
+ process.env.GROUP1__SHARED; // "groupValue" (was GROUP1.$SHARED)
485
+ process.env.GROUP1__VAR; // "anyValue"
486
+ ```
487
+
488
+ > The `$` is removed only from the **process environment** and from JSON Schema
489
+ > validation keys. It is **preserved** in the secrets file storage, so the
490
+ > `secrets` provider round-trips the global marker intact.
491
+ >
492
+ > Skip a key entirely (never injected) by prefixing it with `#`.
493
+
494
+ ### Priority (lowest โ†’ highest)
495
+
496
+ 1. `package-json` info
497
+ 2. `appsettings.json` (`app-settings`)
498
+ 3. `<env>.env.json` (`secrets`)
499
+ 4. `<env>.local.env.json` (`local`, skipped in `--ci`)
500
+
501
+ <p align="right">(<a href="#top">back to top</a>)</p>
502
+
503
+ <!-- MASKING -->
504
+
505
+ ## ๐Ÿ™ˆ Masking secrets
506
+
507
+ When the resolved environment is rendered (at `--log debug`) and when objects
508
+ are logged, secrets are masked as `*****`. There are two complementary
509
+ mechanisms, configurable via CLI flags or the [config file](#-config):
510
+
511
+ ### By key โ€” `logMaskValuesOfKeys` (`--mvk`)
512
+
513
+ Masks a variable's **whole value** when its **key** matches. Each entry is
514
+ either an **exact key name** (case-insensitive) or a `/source/flags` **regex**
515
+ matched against the key:
516
+
517
+ ```jsonc
518
+ {
519
+ "logMaskValuesOfKeys": [
520
+ "PASSWORD", // exact key (case-insensitive)
521
+ "/token/i", // any key containing "token"
522
+ "/_secret$/i", // any key ending in "_secret"
523
+ ],
524
+ }
525
+ ```
526
+
527
+ ```bash
528
+ # same, via CLI flag
529
+ > env -e dev --log debug --mvk PASSWORD "/token/i" : node app.js
530
+ ```
531
+
532
+ ### By value content โ€” `logMaskAnyRegEx` (`--mrx`)
533
+
534
+ Masks **only the matching portion** of any string value, wherever it appears.
535
+ The global flag is always forced, so **every** occurrence is masked; use the
536
+ `/source/flags` form for extra flags such as `i`:
537
+
538
+ ```jsonc
539
+ {
540
+ "logMaskAnyRegEx": [
541
+ "AKIA[0-9A-Z]{16}", // AWS access key ids
542
+ "/bearer .+/i", // bearer tokens (case-insensitive)
543
+ ],
544
+ }
545
+ ```
546
+
547
+ | Goal | Use |
548
+ | --------------------------------------------------- | --------------------- |
549
+ | Know the **key** of the secret | `logMaskValuesOfKeys` |
550
+ | Know the **shape** of the secret (token, key, RUTโ€ฆ) | `logMaskAnyRegEx` |
551
+
552
+ > In JSON the backslash must be escaped: write `\\d` for `\d`. Neither mechanism
553
+ > masks the variable **name** โ€” keys are always shown. Masking only affects the
554
+ > output; the real (unmasked) values are still injected into `process.env`.
555
+
556
+ <p align="right">(<a href="#top">back to top</a>)</p>
557
+
558
+ <!-- SCRIPTS -->
559
+
560
+ ## ๐Ÿงฐ Development scripts
561
+
562
+ | Script | Description |
563
+ | -------------------- | ------------------------------------------------- |
564
+ | `pnpm build` | Build the library (Vite, ESM) into `dist/` |
565
+ | `pnpm test` | Run unit tests (Vitest) |
566
+ | `pnpm test:cov` | Run unit tests with coverage (100% threshold) |
567
+ | `pnpm test:int` | Run integration tests against the built binary |
568
+ | `pnpm typecheck` | Type-check with `tsc --noEmit` |
569
+ | `pnpm lint` | Lint with ESLint (flat config) |
570
+ | `pnpm format` | Format with Prettier |
571
+ | `pnpm run pub` | Gate + build + publish to npm (`latest`) |
572
+ | `pnpm run pub:alpha` | Gate + build + publish a prerelease (`alpha` tag) |
573
+
574
+ <p align="right">(<a href="#top">back to top</a>)</p>
575
+
576
+ <!-- BUILT WITH -->
577
+
578
+ ## ๐Ÿ› ๏ธ Built with
579
+
580
+ - [yargs](http://yargs.js.org/) โ€” CLI argument parsing
581
+ - [ajv](https://ajv.js.org/) + [to-json-schema](https://www.npmjs.com/package/to-json-schema) โ€” JSON Schema
582
+ - [tslog](https://tslog.js.org/#/) โ€” logging
583
+ - [picocolors](https://github.com/alexeyraspopov/picocolors) โ€” terminal colors
584
+ - [subslate](https://github.com/josh-hemphill/subslate) โ€” interpolation
585
+ - [merge-deep](https://github.com/jonschlinkert/merge-deep) โ€” deep merge
586
+ - [Vite](https://vite.dev/) โ€” build ยท [Vitest](https://vitest.dev/) โ€” testing
587
+
588
+ <p align="right">(<a href="#top">back to top</a>)</p>
589
+
590
+ <!-- LICENSE -->
591
+
592
+ ## ๐Ÿ“„ License
593
+
594
+ This project is licensed under the **MIT License** โ€” see [LICENSE.md](LICENSE.md)
595
+ for details.
596
+
597
+ <p align="right">(<a href="#top">back to top</a>)</p>
598
+
599
+ ---
600
+
601
+ <p align="center">
602
+ โŒจ with โค๏ธ by <a href="https://github.com/calvear93">Alvear Candia, Cristopher Alejandro</a>
603
+ </p>