@calvear/env 3.0.0 → 3.1.0

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,357 +1,273 @@
1
1
  <div id="top" align="center">
2
- <img
3
- alt="logo"
4
- src="https://nodejs.org/static/images/logo.svg"
5
- width="256px"
6
- />
7
-
8
- </br>
9
-
10
- <h1><b>env</b></h1>
11
- <h4>¡Environment variables made easy!</h4>
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>
12
20
  </div>
13
21
 
14
22
  <br />
15
23
 
16
- <p align="center">
17
- <img
18
- src="https://img.shields.io/badge/version-3.0.0-blue?style=flat-square"
19
- alt="version"
20
- />
21
- &nbsp;
22
- <img
23
- src="https://img.shields.io/badge/TypeScript-007ACC?style=flat-square&logo=typescript&logoColor=white"
24
- alt="typescript"
25
- />
26
- &nbsp;
27
- <img
28
- src="https://img.shields.io/badge/ESM-only-f7df1e?style=flat-square"
29
- alt="esm only"
30
- />
31
- &nbsp;
32
- <img
33
- src="https://img.shields.io/badge/nodejs->=20-darkgreen?style=flat-square"
34
- alt="nodejs engine"
35
- />
36
- &nbsp;
37
- <img
38
- src="https://img.shields.io/badge/pnpm->=9-darkgreen?style=flat-square"
39
- alt="pnpm engine"
40
- />
41
- &nbsp;
42
- <img
43
- src="https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square"
44
- alt="license"
45
- />
46
- </p>
24
+ <!-- ABOUT -->
47
25
 
48
- <br />
26
+ ## 📖 About
49
27
 
50
- <!-- ABOUT THE PROJECT -->
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.
51
34
 
52
- ## 📖 **About**
35
+ ### Features
53
36
 
54
- Eases NodeJS <b>environment variable handling</b>, like [env-cmd](https://www.npmjs.com/package/env-cmd) or [dotenv](https://www.npmjs.com/package/dotenv), but with <b>powerfull features and extensibility</b> for adding custom providers (as plugins) for <u>load</u>, <u>pull</u> and <u>push</u> the variables from different stores.
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.
55
49
 
56
50
  <p align="right">(<a href="#top">back to top</a>)</p>
57
51
 
58
52
  <!-- REQUIREMENTS -->
59
53
 
60
- ## 📌 **Requirements**
54
+ ## 📌 Requirements
61
55
 
62
- First, [download](https://nodejs.org/) and install **NodeJS**. Version `20` or higher is required.
63
-
64
- > 🧩 **ESM only**: this package is published as native ECMAScript Modules
65
- > (`"type": "module"`). Import it from ESM code, and author any custom provider
66
- > as an ESM module with a `export default`.
67
-
68
- Validate installed versions of node and pnpm with:
56
+ [NodeJS](https://nodejs.org/) **20 or higher**.
69
57
 
70
58
  ```bash
71
59
  > node -v
72
60
  v20.0.0
73
-
74
- > pnpm -v
75
- 9.0.0
76
61
  ```
77
62
 
78
- You can initialize a new project using:
63
+ <p align="right">(<a href="#top">back to top</a>)</p>
79
64
 
80
- ```bash
81
- > pnpm init
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');
82
79
  ```
83
80
 
84
81
  <p align="right">(<a href="#top">back to top</a>)</p>
85
82
 
86
83
  <!-- QUICK START -->
87
84
 
88
- ## ⚡️ **Quick start**
85
+ ## ⚡️ Quick start
89
86
 
90
- > 🔔 Make sure that you have [NodeJS 20+](https://nodejs.org/) installed on your computer.
91
-
92
- - Installs the package:
87
+ Install the package:
93
88
 
94
89
  ```bash
95
90
  > npm install @calvear/env
96
-
97
- added 1 packages, and audited 1 packages in 1s
98
-
99
- found 0 vulnerabilities
100
-
101
- > _
102
91
  ```
103
92
 
104
- - Executes binary directly:
93
+ Run the binary directly:
105
94
 
106
95
  ```bash
107
- > node_modules/.bin/env --help
96
+ > npx env --help
108
97
 
109
98
  Usage: env [command] [options..] [: subcmd [:]] [options..]
110
99
 
111
100
  Commands:
112
- env [options..] [: <subcmd> :]
113
- env pull [options..]
114
- env push [options..]
115
- env schema [options..]
116
-
117
- > _
101
+ env [options..] [: <subcmd> :]
102
+ env pull [options..]
103
+ env push [options..]
104
+ env schema [options..]
105
+ env export [options..]
118
106
  ```
119
107
 
120
- - Or add desired commands in your **npm script** in `package.json`:
108
+ Or wire it into your **npm scripts**:
121
109
 
122
- ```javascript
110
+ ```jsonc
123
111
  {
124
- ...,
125
- "scripts": {
126
- // starts project injecting "dev" environment variables and debug log level
127
- "start:dev": "env -e dev -m debug : node dist/main.js : --log debug",
128
- // starts project injecting "prod" environment variables
129
- "start:prod": "env -e prod -m debug : node dist/main.js",
130
- ...,
131
- // builds project injecting "prod" environment variables
132
- "build:prod": "env -e prod -m build : tsc",
133
- ...,
134
- "env:schema": "env schema -e dev --ci",
135
- // uploads environment "dev" variables
136
- "env:push:dev": "env push -e dev",
137
- // downloads environment "dev" variables
138
- "env:pull:dev": "env pull -e dev"
139
- },
140
- ...
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
+ },
141
120
  }
142
121
  ```
143
122
 
144
- - Execs your command:
145
-
146
- **file**: _dist/main.js_
147
-
148
- ```javascript
149
- console.log(`My environment loaded is: ${process.env.ENV}`);
150
- ```
123
+ Run it:
151
124
 
152
125
  ```bash
153
126
  > npm run start:dev
154
127
 
155
- 13:31:59.865 INFO loading dev environment in debug mode
156
- 13:31:59.911 DEBUG using package-json provider
157
- 13:31:59.912 DEBUG using app-settings provider
158
- 13:31:59.914 DEBUG using secrets provider
159
- 13:32:00.109 DEBUG environment loaded:
160
- {
161
- NODE_ENV: 'development',
162
- ENV: 'dev',
163
- VERSION: '1.0.0',
164
- NAME: '@my-app',
165
- VAR1: true,
166
- VAR2: true,
167
- GROUP1__VAR1: 'G1V2',
168
- ARR1: '1,val,true',
169
- SECRET: '***'
170
- }
171
- 13:32:00.116 INFO executing command > node dist/main.js
172
- My environment loaded is: dev
173
- 13:32:00.232 INFO process finished successfully
128
+ ⚡ env v3.0.0 · 🌎 dev · 🧩 debug
174
129
 
175
- > _
176
- ```
130
+ 📦 package-json 6 vars
131
+ 🗂️ app-settings 5 vars
132
+ 🔐 secrets 1 secrets
133
+ 📂 local 1 vars
177
134
 
178
- <p align="right">(<a href="#top">back to top</a>)</p>
135
+ environment (12 variables)
136
+ ENV = dev
137
+ NODE_ENV = development
138
+ SECRET = *****
139
+ VERSION = 3.0.0
140
+ ...
179
141
 
180
- ## **Structure**
142
+ 12 variables loaded in 142ms
181
143
 
182
- ```bash
183
- ├── src/
184
- │   ├── commands/ # lib commands handlers
185
- │   │   ├── env.command.ts
186
- │   │   ├── export.command.ts
187
- │   │   ├── pull.command.ts
188
- │   │   ├── push.command.ts
189
- │   │   └── schema.command.ts
190
- │   ├── interfaces/ # provider interfaces
191
- │   ├── providers/ # integrated providers
192
- │   │   ├── package-json.provider.ts
193
- │   │   ├── app-settings.provider.ts
194
- │   │   ├── secrets.provider.ts
195
- │   │   └── local.provider.ts
196
- │   ├── utils/
197
- │   │   ├── command.util.ts
198
- │   │   ├── interpolate.util.ts
199
- │   │   ├── json.util.ts
200
- │   │   ├── normalize.util.ts
201
- │   │   ├── schema.util.ts
202
- │   │   └── logger.ts
203
- │   ├── arguments.ts # global arguments
204
- │   ├── exec.ts # initialization logic (load config, commands, etc.)
205
- │   └── main.ts
206
- ├── tests/ # integration tests
207
- ├── eslint.config.js
208
- ├── vite.config.ts
209
- ├── vitest.config.ts
210
- └── tsconfig.json
144
+ ▶ node dist/main.js
145
+ My environment loaded is: dev
146
+ finished in 168ms
211
147
  ```
212
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
+
213
153
  <p align="right">(<a href="#top">back to top</a>)</p>
214
154
 
215
155
  <!-- COMMANDS AND OPTIONS -->
216
156
 
217
- ## ⚙️ **Commands & Options**
157
+ ## ⚙️ Commands & Options
218
158
 
219
- Options handling has the ability of **replace arguments itself**, using `[[` and `]]` as delimiters.
220
- So, in example for define your config file path, you must use your _root_ argument,
221
- supposing root has the value of "config", this definition _`[[root]]/any-config-file.json`_ will be
222
- _`config/any-config-file.json`_, or if your _env_ argument is "dev", this definition
223
- _`[[root]]/config-file.[[env]].json`_ will be _`config/config-file.dev.json`_.
224
-
225
- <div align="center">
226
- <span style="font-size:20px;font-weight:bold" align="center">Options</span>
227
- </div>
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`.
228
163
 
229
164
  ### Global options
230
165
 
231
- | Option | Description | Type | Default | Required? |
232
- | ---------------------------------- | ----------------------------------------------- | ---------- | ------- | --------- |
233
- | `--help` | Shows help | `boolean` | | No |
234
- | `--e, --env` | Environment for load | `string` | | Yes |
235
- | `-m, --modes` | Execution modes | `string[]` | `[]` | No |
236
- | `--nd, --nestingDelimiter` | Nesting level delimiter for flatten | `string` | `__` | No |
237
- | `--arrDesc, --arrayDescomposition` | Whether serialize or break down arrays | `boolean` | `false` | No |
238
- | `-x, --expand` | Interpolates environment variables using itself | `boolean` | `false` | No |
239
- | `-ci` | Continuous Integration mode | `boolean` | `false` | No |
240
-
241
- </br>
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 |
242
175
 
243
176
  ### Workspace options
244
177
 
245
- | Option | Description | Type | Default | Required? |
246
- | ------------------ | --------------------------------- | -------- | --------------------------------- | --------- |
247
- | `--root` | Default environment folder path | `string` | `env` | No |
248
- | `-c, --configFile` | Config JSON file path | `string` | `[[root]]/settings/settings.json` | No |
249
- | `-s, --schemaFile` | Environment Schema JSON file path | `string` | `[[root]]/settings/schema.json` | No |
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_ |
250
184
 
251
185
  ### JSON Schema options
252
186
 
253
- | Option | Description | Type | Default | Required? |
254
- | ---------------------- | ---------------------------------------------------------- | ----------------- | ------- | --------- |
255
- | `-r, --resolve` | Whether merges new schema or override | `merge, override` | `merge` | No |
256
- | `--null, --nullable` | Whether variables are nullable by default | `boolean` | `true` | No |
257
- | `--df, --detectFormat` | Whether format of strings variables are included in schema | `boolean` | `false` | No |
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` |
258
192
 
259
193
  ### Logger options
260
194
 
261
- | Option | Description | Type | Default | Required? |
262
- | ------------------- | ----------- | ---------------------------------------- | ------- | --------- |
263
- | `--log, --logLevel` | Log level | `silly, trace, debug, info, warn, error` | `info` | No |
264
-
265
- <div align="center">
266
- <span style="font-size:20px;font-weight:bold" align="center">Commands</span>
267
- </div>
268
-
269
- - ## **`env`**
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[]` | `[]` |
270
200
 
271
- Inject your environment variables into `process.env` and executes a command.
201
+ See [🙈 Masking secrets](#-masking-secrets) for the full masking semantics.
272
202
 
273
- ```bash
274
- env -e [env] [options..] [: subcmd [:]] [options..]
275
- ```
203
+ ---
276
204
 
277
- Examples:
205
+ ### `env`
278
206
 
279
- ```bash
280
- > env -e dev -m test unit : npm test
281
- ```
207
+ Inject environment variables into `process.env` and execute a command (the
208
+ command goes after `:`).
282
209
 
283
210
  ```bash
284
- > env -e dev -m debug : npm start : -c [[root]]/[[env]].settings.json
211
+ env -e <env> [options..] [: <subcmd> :] [options..]
285
212
  ```
286
213
 
287
214
  ```bash
288
- > env -e prod -m build optimize : npm build
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
289
218
  ```
290
219
 
291
- - ## **`pull`**
292
-
293
- Pulls environment variables from providers stores.
294
-
295
- ```bash
296
- env pull -e [env] [options..]
297
- ```
220
+ | Option | Description | Type | Default |
221
+ | ------------------------------ | ------------------------------------------ | --------- | ------- |
222
+ | `--validate, --schemaValidate` | Validate variables against the JSON schema | `boolean` | `true` |
298
223
 
299
- | Option | Description | Type | Default | Required? |
300
- | ----------------- | ------------------------- | --------- | ------- | --------- |
301
- | `-o, --overwrite` | Overwrite local variables | `boolean` | `false` | No |
224
+ ### `pull`
302
225
 
303
- Examples:
226
+ Pull environment variables from the providers' stores (for providers that
227
+ implement `pull`, i.e. custom remote providers).
304
228
 
305
229
  ```bash
306
- > env pull -e dev
230
+ env pull -e <env> [options..]
307
231
  ```
308
232
 
309
- - ## **`push`**
233
+ | Option | Description | Type | Default |
234
+ | ----------------- | ------------------------- | --------- | ------- |
235
+ | `-o, --overwrite` | Overwrite local variables | `boolean` | `false` |
310
236
 
311
- Pushes environment variables to providers stores.
237
+ ### `push`
312
238
 
313
- ```bash
314
- env push -e [env] [options..]
315
- ```
316
-
317
- | Option | Description | Type | Default | Required? |
318
- | ------------- | ------------------------------------ | --------- | ------- | --------- |
319
- | `-f, --force` | Force push for secrets (replace all) | `boolean` | `false` | No |
320
-
321
- Examples:
239
+ Push environment variables to the providers' stores (for providers that
240
+ implement `push`).
322
241
 
323
242
  ```bash
324
- > env push -e dev
243
+ env push -e <env> [options..]
325
244
  ```
326
245
 
327
- - ## **`schema`**
246
+ | Option | Description | Type | Default |
247
+ | ------------- | ---------------------- | --------- | ------- |
248
+ | `-f, --force` | Force push for secrets | `boolean` | `false` |
328
249
 
329
- Generates validation schema from providers output variables.
250
+ ### `schema`
330
251
 
331
- ```bash
332
- env schema -e [env] -m [modes] [options..]
333
- ```
334
-
335
- Examples:
252
+ Generate (or update) the validation schema from the providers' output.
336
253
 
337
254
  ```bash
338
255
  > env schema -e dev -m build
339
256
  ```
340
257
 
341
- - ## **`export`**
258
+ ### `export`
342
259
 
343
- Export unified environment variables to a file from providers.
260
+ Export the unified environment to a file.
344
261
 
345
262
  ```bash
346
- env export -e [env] -m [modes] [options..]
263
+ env export -e <env> -m <modes> [options..]
347
264
  ```
348
265
 
349
- | Option | Description | Type | Default | Required? |
350
- | --------------- | ---------------------------------- | -------- | -------- | --------- |
351
- | `-u, -p, --uri` | Uri for export file with variables | `string` | `.env` | No |
352
- | `-f, --format` | Format for export variables | `string` | `dotenv` | No |
353
-
354
- Examples:
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` |
355
271
 
356
272
  ```bash
357
273
  > env export -e dev -m build -f json --uri [[env]].env.json
@@ -361,143 +277,99 @@ Examples:
361
277
 
362
278
  <!-- PROVIDERS -->
363
279
 
364
- ## 📡 **Providers**
280
+ ## 📡 Providers
365
281
 
366
- Main feature of this library is using providers for get and set environment variables.
367
- So, you can define your own provider, but lib came with 4 integrated providers:
282
+ Providers are the core of this library. It ships with **four integrated
283
+ providers**, and you can add your own.
368
284
 
369
- - ## **`package-json`**
285
+ ### `package-json`
370
286
 
371
- Load some info from your project `package.json`.
287
+ Loads project info from your `package.json` (`version`, `project`, `name`,
288
+ `title`, `description`) into `ENV`, `VERSION`, `PROJECT`, `NAME`, `TITLE`,
289
+ `DESCRIPTION`.
372
290
 
373
- Info read is:
374
-
375
- ```json
376
- {
377
- "version": "1.0.0",
378
- "project": "project-name",
379
- "name": "@package-name",
380
- "title": "app-name",
381
- "description": "any description"
382
- }
383
- ```
384
-
385
- | Option | Description | Type | Default | Required? |
386
- | ------------------- | --------------------------- | -------- | ------- | --------- |
387
- | `--vp, --varPrefix` | Prefix for loaded variables | `string` | `""` | No |
388
-
389
- Examples:
291
+ | Option | Description | Type | Default |
292
+ | ------------------- | ------------------------------- | -------- | ------- |
293
+ | `--vp, --varPrefix` | Prefix for the loaded variables | `string` | `""` |
390
294
 
391
295
  ```bash
392
- > env -e dev -m build : react-script build : --vp REACT_APP_
296
+ # i.e. expose them as REACT_APP_* for CRA
297
+ > env -e dev -m build : react-scripts build : --vp REACT_APP_
393
298
  ```
394
299
 
395
- </br>
396
-
397
- - ## **`app-settings`**
398
-
399
- Non secrets loader for `appsettings.json`.
300
+ ### `app-settings`
400
301
 
401
- `appsettings.json` file has the format below:
302
+ Non-secret loader for `appsettings.json`, organized by sections:
402
303
 
403
- ```json
304
+ ```jsonc
404
305
  {
405
- "|DEFAULT|": {},
406
- "|MODE|": {},
407
- "|ENV|": {}
408
- }
409
- ```
410
-
411
- In example:
412
-
413
- ```json
414
- {
415
- "|DEFAULT|": {
416
- "VAR1": "v1_default"
417
- },
306
+ "|DEFAULT|": { "VAR1": "v1_default" },
418
307
  "|MODE|": {
419
- "build": {
420
- "NODE_ENV": "production"
421
- },
422
- "debug": {
423
- "NODE_ENV": "development"
424
- },
425
- "test": {
426
- "NODE_ENV": "test"
427
- }
308
+ "build": { "NODE_ENV": "production" },
309
+ "debug": { "NODE_ENV": "development" },
428
310
  },
429
311
  "|ENV|": {
430
312
  "dev": {
431
313
  "C1": "V1",
432
- "C2": "V2",
433
- "C3": 3,
434
- "GROUP1": {
435
- "VAR1": null,
436
- "VAR2": "G1V2",
437
- "VAR3": true,
438
- "GROUP2": {
439
- "VAR1": "G1G2V1"
440
- }
441
- },
442
- "C4": "23"
443
- }
444
- }
314
+ "GROUP1": { "VAR2": "G1V2", "GROUP2": { "VAR1": "G1G2V1" } },
315
+ },
316
+ },
317
+ "|LOCAL|": { "dev": { "LOCAL_VAR": "only-local" } },
445
318
  }
446
319
  ```
447
320
 
448
- | Option | Description | Type | Default | Required? |
449
- | ----------------- | ------------------------------- | -------- | --------------------------- | --------- |
450
- | `--ef, --envFile` | Environment variables file path | `string` | `[[root]]/appsettings.json` | No |
321
+ | Option | Description | Type | Default |
322
+ | ----------------- | ----------------------------- | -------- | --------------------------- |
323
+ | `--ef, --envFile` | Non-secret settings file path | `string` | `[[root]]/appsettings.json` |
451
324
 
452
- </br>
325
+ ### `secrets`
453
326
 
454
- - ## **`secrets`**
327
+ Loads secret variables from a per-environment JSON file
328
+ (`[[root]]/[[env]].env.json`). Keep this file out of version control.
455
329
 
456
- Secrets loader for `env/[[env]].env.json`.
330
+ | Option | Description | Type | Default |
331
+ | --------------------- | -------------------------- | -------- | --------------------------- |
332
+ | `--sf, --secretsFile` | Secret variables file path | `string` | `[[root]]/[[env]].env.json` |
457
333
 
458
- | Option | Description | Type | Default | Required? |
459
- | --------------------- | -------------------------- | -------- | --------------------------- | --------- |
460
- | `--sf, --secretsFile` | Secret variables file path | `string` | `[[root]]/[[env]].env.json` | No |
334
+ ### `local`
461
335
 
462
- - ## **`local`**
336
+ Loads local-only variables (never loaded in `--ci`). The file is auto-created
337
+ if missing.
463
338
 
464
- Local variables loader for `env/[[env]].local.env.json`.
465
-
466
- | Option | Description | Type | Default | Required? |
467
- | ------------------- | ------------------------- | -------- | --------------------------------- | --------- |
468
- | `--lf, --localFile` | Local variables file path | `string` | `[[root]]/[[env]].local.env.json` | No |
339
+ | Option | Description | Type | Default |
340
+ | ------------------- | ------------------------- | -------- | --------------------------------- |
341
+ | `--lf, --localFile` | Local variables file path | `string` | `[[root]]/[[env]].local.env.json` |
469
342
 
470
343
  <p align="right">(<a href="#top">back to top</a>)</p>
471
344
 
472
345
  <!-- CUSTOM PROVIDERS -->
473
346
 
474
- ## **Creating Custom Providers**
475
-
476
- You can create your custom providers, in two ways:
347
+ ## ✒️ Creating custom providers
477
348
 
478
- - **Local Script**: you must create a JavaScript file (.js), exporting by default your "provider" following standard interface exported by this lib.
479
- - **NPM Package**: you must create your custom NPM library and export by default your "provider" using standard interface exported by this lib.
349
+ Create a provider in two ways:
480
350
 
481
- How to load your provider is shown in Config Section.
351
+ - **Local script** a `.js` file that `export default`s your provider.
352
+ - **NPM package** — a published module that `export default`s your provider.
482
353
 
483
- In example, a provider exported by your NPM package written in TypeScript should be like:
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).
484
357
 
485
358
  ```typescript
486
- import { CommandArguments, EnvProvider } from '@calvear/env';
359
+ import type { CommandArguments, EnvProvider } from '@calvear/env';
487
360
  import { logger, readJson, writeJson } from '@calvear/env/utils';
488
361
 
489
362
  const KEY = 'my-unique-provider-key';
490
363
 
491
- interface MyProviderCommandArguments extends CommandArguments {
364
+ interface MyProviderArguments extends CommandArguments {
492
365
  anyExtraOption: boolean;
493
366
  }
494
367
 
495
- export const MyProvider: EnvProvider<MyProviderCommandArguments> = {
496
- // unique identifier for provider
368
+ const MyProvider: EnvProvider<MyProviderArguments> = {
369
+ // unique identifier
497
370
  key: KEY,
498
371
 
499
- // (optional) allows to provider adds new arguments/options
500
- // to commands using yargs for builder
372
+ // (optional) add custom options to the CLI via yargs
501
373
  builder: (builder) => {
502
374
  builder.options({
503
375
  anyExtraOption: {
@@ -510,126 +382,194 @@ export const MyProvider: EnvProvider<MyProviderCommandArguments> = {
510
382
  });
511
383
  },
512
384
 
513
- // call on environment variables loading,
514
- // may be a Promise
515
- load: ({ env, modes, ...options }) => {
516
- if (env === 'dev')
517
- return {
518
- NODE_ENV: 'development',
519
- };
520
-
521
- // you can return a list of JSON environment variables for merge
522
- return [
523
- {
524
- NODE_ENV: 'production',
525
- ANY_VAR: 'ABC', // will be replaced by value below
526
- },
527
- {
528
- ANY_VAR: 'ANY_VALUE',
529
- ANY_GROUP: {
530
- INNER_VAR: 12,
531
- },
532
- },
533
- ];
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 } }];
534
390
  },
535
391
 
536
- // (optional) call on pulling variables from provider store,
537
- // config may pass in your config file
538
- pull: ({ env, modes, ...options }, config) => {
539
- // anyway you want for pulling variables to cache
392
+ // (optional) called on `env pull`
393
+ pull: (argv, config) => {
394
+ /* fetch variables into your local cache */
540
395
  },
541
396
 
542
- // (optional) call on pushing/updating variables to provider store,
543
- // config may pass in your config file
544
- push: ({ env, modes, ...options }, config) => {
545
- // anyway you should do for pushing or updating your variables
397
+ // (optional) called on `env push`
398
+ push: (argv, config) => {
399
+ /* publish/update your variables */
546
400
  },
547
401
  };
402
+
403
+ export default MyProvider;
548
404
  ```
549
405
 
550
406
  <p align="right">(<a href="#top">back to top</a>)</p>
551
407
 
552
408
  <!-- CONFIG -->
553
409
 
554
- ## 📥 **Config**
410
+ ## 📥 Config
555
411
 
556
- You can configure any config argument inside you config file, but commonly providers are designed for this purpose.
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**:
557
415
 
558
- ```javascript
416
+ ```jsonc
559
417
  {
560
- "log": "silly",
561
- // will hide values of keys SECRET and MY_API_KEY in logging
562
- "logMaskValuesOfKeys": ["SECRET", "MY_API_KEY"],
563
- // integrated providers and custom providers together
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}"],
564
422
  "providers": [
565
- {
566
- "path": "package-json",
567
- "type": "integrated"
568
- },
569
- {
570
- "path": "app-settings",
571
- "type": "integrated"
572
- },
573
- {
574
- "path": "secrets",
575
- "type": "integrated"
576
- },
577
- {
578
- "path": "local",
579
- "type": "integrated"
580
- },
581
- {
582
- // custom NPM package
583
- "path": "@npm-package",
584
- "type": "module",
585
- "config": {
586
- "any-config": "any value"
587
- }
588
- },
589
- {
590
- // custom script inside project
591
- "path": "scripts/custom-loader.js",
592
- "type": "script"
593
- }
594
- ]
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
+ ],
595
432
  }
596
433
  ```
597
434
 
598
- <!-- ROADMAP -->
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
599
444
 
600
- ## 📑 Roadmap
445
+ Organize variables in nested objects. They are **flattened** into `process.env`
446
+ using the nesting delimiter (`__` by default):
601
447
 
602
- - [x] Environment injection handling
603
- - [x] Customizable variables store providers
604
- - [x] Commands
605
- - [x] `push` executes a pushing action over every providers
606
- - [x] `pull` executes a pulling action over every providers
607
- - [x] `schema` regenerates JSON schema using providers output
608
- - [x] `export` exports environment variables in json or dotenv format
609
- - [ ] `prepare` prepares environment (creates folder and files required)
610
- - [ ] Improve documentation
611
- - [ ] Providers pull history
612
- - [ ] Providers pull and push difference calc and prompts
613
- - [ ] Providers dependsOn option
614
- - [ ] Programatic module
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`)
615
500
 
616
501
  <p align="right">(<a href="#top">back to top</a>)</p>
617
502
 
618
- ## 🧿 **Linting**
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):
619
510
 
620
- Project uses ESLint, for code formatting and code styling normalizing.
511
+ ### By key `logMaskValuesOfKeys` (`--mvk`)
621
512
 
622
- - **eslint**: linter integrated with TypeScript.
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:
623
516
 
624
- For correct interpretation of linters, is recommended to use [Visual Studio Code](https://code.visualstudio.com/) as IDE and install the plugins in .vscode folder at 'extensions.json', as well as use the config provided in 'settings.json'
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`.
625
555
 
626
556
  <p align="right">(<a href="#top">back to top</a>)</p>
627
557
 
628
- <!-- CHANGELOG -->
558
+ <!-- SCRIPTS -->
629
559
 
630
- ## 📋 Changelog
560
+ ## 🧰 Development scripts
631
561
 
632
- For last changes see [CHANGELOG.md](CHANGELOG.md) file for details.
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) |
633
573
 
634
574
  <p align="right">(<a href="#top">back to top</a>)</p>
635
575
 
@@ -637,14 +577,13 @@ For last changes see [CHANGELOG.md](CHANGELOG.md) file for details.
637
577
 
638
578
  ## 🛠️ Built with
639
579
 
640
- - [yargs](http://yargs.js.org/)
641
- - [tslog](https://tslog.js.org/#/) (v4)
642
- - [picocolors](https://github.com/alexeyraspopov/picocolors)
643
- - [subslate](https://github.com/josh-hemphill/subslate)
644
- - [merge-deep](https://github.com/jonschlinkert/merge-deep)
645
- - [ajv](https://ajv.js.org/)
646
- - [to-json-schema](https://www.npmjs.com/package/to-json-schema)
647
- - [vite](https://vite.dev/) + [vitest](https://vitest.dev/) (build & test)
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
648
587
 
649
588
  <p align="right">(<a href="#top">back to top</a>)</p>
650
589
 
@@ -652,10 +591,13 @@ For last changes see [CHANGELOG.md](CHANGELOG.md) file for details.
652
591
 
653
592
  ## 📄 License
654
593
 
655
- This project is licensed under the MIT License - see [LICENSE.md](LICENSE.md) file for details.
594
+ This project is licensed under the **MIT License** see [LICENSE.md](LICENSE.md)
595
+ for details.
656
596
 
657
597
  <p align="right">(<a href="#top">back to top</a>)</p>
658
598
 
659
599
  ---
660
600
 
661
- by [Alvear Candia, Cristopher Alejandro](https://github.com/calvear93)
601
+ <p align="center">
602
+ ⌨ with ❤️ by <a href="https://github.com/calvear93">Alvear Candia, Cristopher Alejandro</a>
603
+ </p>