node-safe-env 0.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/LICENSE +21 -0
- package/README.md +381 -0
- package/dist/cli.js +1019 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +938 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +196 -0
- package/dist/index.d.ts +196 -0
- package/dist/index.js +892 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# node-safe-env
|
|
2
|
+
|
|
3
|
+
Schema-based environment loading and validation for Node.js.
|
|
4
|
+
|
|
5
|
+
`node-safe-env` helps you fail fast at startup by validating environment variables
|
|
6
|
+
with a typed schema, parsing values into runtime types, and reporting all validation
|
|
7
|
+
issues in one error.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install node-safe-env
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createEnv, defineEnv } from "node-safe-env";
|
|
19
|
+
|
|
20
|
+
const schema = defineEnv({
|
|
21
|
+
APP_NAME: { type: "string", required: true },
|
|
22
|
+
PORT: { type: "port", default: 3000 },
|
|
23
|
+
DEBUG: { type: "boolean", default: false },
|
|
24
|
+
NODE_ENV: {
|
|
25
|
+
type: "enum",
|
|
26
|
+
values: ["development", "test", "production"],
|
|
27
|
+
default: "development",
|
|
28
|
+
},
|
|
29
|
+
} as const);
|
|
30
|
+
|
|
31
|
+
const env = createEnv(schema);
|
|
32
|
+
|
|
33
|
+
env.APP_NAME; // string
|
|
34
|
+
env.PORT; // number
|
|
35
|
+
env.DEBUG; // boolean
|
|
36
|
+
env.NODE_ENV; // "development" | "test" | "production"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## `defineEnv()`
|
|
40
|
+
|
|
41
|
+
`defineEnv()` locks schema literal types with a single top-level `as const`. Use it when you want to export or reuse a schema across modules.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// env.schema.ts
|
|
45
|
+
import { defineEnv } from "node-safe-env";
|
|
46
|
+
|
|
47
|
+
export const schema = defineEnv({
|
|
48
|
+
PORT: { type: "port", default: 3000 },
|
|
49
|
+
ADMIN_EMAIL: { type: "email", required: true },
|
|
50
|
+
} as const);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// main.ts
|
|
55
|
+
import { createEnv } from "node-safe-env";
|
|
56
|
+
import { schema } from "./env.schema.js";
|
|
57
|
+
|
|
58
|
+
const env = createEnv(schema);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Nested Schemas
|
|
62
|
+
|
|
63
|
+
Schemas can be nested. Input env keys are flattened using uppercase segments joined by `_`.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const env = createEnv({
|
|
67
|
+
server: {
|
|
68
|
+
port: { type: "port", default: 3000 },
|
|
69
|
+
},
|
|
70
|
+
database: {
|
|
71
|
+
url: { type: "string", required: true },
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```env
|
|
77
|
+
SERVER_PORT=4000
|
|
78
|
+
DATABASE_URL=postgres://localhost:5432/app
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
env.server.port; // number
|
|
83
|
+
env.database.url; // string
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Loading Order
|
|
87
|
+
|
|
88
|
+
When `options.source` is not provided, values are loaded and merged in this order (last one wins):
|
|
89
|
+
|
|
90
|
+
1. `.env`
|
|
91
|
+
2. `.env.local`
|
|
92
|
+
3. `.env.<NODE_ENV>`
|
|
93
|
+
4. custom file from `options.envFile`
|
|
94
|
+
5. `process.env`
|
|
95
|
+
|
|
96
|
+
## Supported Rule Types
|
|
97
|
+
|
|
98
|
+
- `string`
|
|
99
|
+
- `number`
|
|
100
|
+
- `boolean`
|
|
101
|
+
- `enum`
|
|
102
|
+
- `url`
|
|
103
|
+
- `port`
|
|
104
|
+
- `json`
|
|
105
|
+
- `int`
|
|
106
|
+
- `float`
|
|
107
|
+
- `array`
|
|
108
|
+
- `email`
|
|
109
|
+
- `date`
|
|
110
|
+
- `custom`
|
|
111
|
+
|
|
112
|
+
## Email Rule
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const env = createEnv({
|
|
116
|
+
ADMIN_EMAIL: { type: "email", required: true },
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Date Rule
|
|
121
|
+
|
|
122
|
+
`date` parses ISO-compatible values into `Date`.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const env = createEnv({
|
|
126
|
+
START_DATE: { type: "date", required: true },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
env.START_DATE; // Date
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Custom Rule
|
|
133
|
+
|
|
134
|
+
Supply a `parse` function that returns the typed value or throws for invalid input.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const env = createEnv({
|
|
138
|
+
RETRY_DELAY_MS: {
|
|
139
|
+
type: "custom",
|
|
140
|
+
parse: (raw) => {
|
|
141
|
+
const n = Number(raw);
|
|
142
|
+
if (!Number.isInteger(n) || n < 0)
|
|
143
|
+
throw new Error("Must be a non-negative integer.");
|
|
144
|
+
return n;
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
env.RETRY_DELAY_MS; // number
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
If `parse` throws, `createEnv` records an `invalid_custom` validation issue with the thrown message.
|
|
153
|
+
|
|
154
|
+
## Array Rule Options
|
|
155
|
+
|
|
156
|
+
Array parsing supports separator and item controls.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const env = createEnv({
|
|
160
|
+
ALLOWED_HOSTS: {
|
|
161
|
+
type: "array",
|
|
162
|
+
separator: "|", // default: ","
|
|
163
|
+
trimItems: false, // default: true
|
|
164
|
+
allowEmptyItems: true, // default: false
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Defaults:
|
|
170
|
+
|
|
171
|
+
- `separator`: `","`
|
|
172
|
+
- `trimItems`: `true`
|
|
173
|
+
- `allowEmptyItems`: `false`
|
|
174
|
+
|
|
175
|
+
## Defaults (Static and Functional)
|
|
176
|
+
|
|
177
|
+
Defaults go through the same parse/validation pipeline as environment input.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const env = createEnv({
|
|
181
|
+
PORT: { type: "port", default: 3000 },
|
|
182
|
+
START_DATE: { type: "date", default: () => "2026-01-01" },
|
|
183
|
+
ADMIN_EMAIL: { type: "email", default: () => "admin@example.com" },
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
If a functional default throws, `createEnv` returns a structured `invalid_default` validation issue.
|
|
188
|
+
|
|
189
|
+
## Debug Mode
|
|
190
|
+
|
|
191
|
+
Enable debug reporting during `createEnv` execution.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { createEnv, type EnvDebugReport } from "node-safe-env";
|
|
195
|
+
|
|
196
|
+
const reports: EnvDebugReport[] = [];
|
|
197
|
+
|
|
198
|
+
createEnv(schema, {
|
|
199
|
+
debug: {
|
|
200
|
+
logger: (report) => reports.push(report),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Also supported:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
createEnv(schema, { debug: true }); // emits one report via console.info
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Debug reports include:
|
|
212
|
+
|
|
213
|
+
- loaded file metadata (`loadedFiles`)
|
|
214
|
+
- per-key entries (`keys`) with source, status, default usage, and issue details
|
|
215
|
+
|
|
216
|
+
Sensitive rules (`sensitive: true`) are masked as `"***"` in debug `raw` and `parsed` fields.
|
|
217
|
+
|
|
218
|
+
## Strict Mode
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
createEnv(schema, { strict: true });
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Strict mode reports unknown environment keys as validation issues.
|
|
225
|
+
|
|
226
|
+
## Masking Sensitive Values
|
|
227
|
+
|
|
228
|
+
Use `maskEnv()` to sanitize parsed env output for logs.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { createEnv, maskEnv } from "node-safe-env";
|
|
232
|
+
|
|
233
|
+
const schema = {
|
|
234
|
+
API_TOKEN: { type: "string", required: true, sensitive: true },
|
|
235
|
+
PORT: { type: "port", default: 3000 },
|
|
236
|
+
} as const;
|
|
237
|
+
|
|
238
|
+
const env = createEnv(schema);
|
|
239
|
+
const safeEnv = maskEnv(schema, env);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Source Tracing
|
|
243
|
+
|
|
244
|
+
Use `traceEnv()` when you have a source map of raw values and origin labels.
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import { traceEnv } from "node-safe-env";
|
|
248
|
+
|
|
249
|
+
const trace = traceEnv(
|
|
250
|
+
{
|
|
251
|
+
PORT: { type: "port" },
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
PORT: { source: "process.env", raw: "3000" },
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
PORT: 3000,
|
|
258
|
+
},
|
|
259
|
+
);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## `.env.example` Validation
|
|
263
|
+
|
|
264
|
+
Validate an example file against your schema:
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
import { validateExampleEnvFile } from "node-safe-env";
|
|
268
|
+
|
|
269
|
+
const issues = validateExampleEnvFile(schema, {
|
|
270
|
+
cwd: process.cwd(),
|
|
271
|
+
exampleFile: ".env.example",
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Or validate an in-memory object:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { validateExampleEnv } from "node-safe-env";
|
|
279
|
+
|
|
280
|
+
const issues = validateExampleEnv(schema, {
|
|
281
|
+
PORT: "",
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## CLI
|
|
286
|
+
|
|
287
|
+
`node-safe-env` includes a CLI with two commands.
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
# installed locally (package.json scripts or npx)
|
|
291
|
+
node-safe-env validate --schema ./dist/env.schema.js
|
|
292
|
+
node-safe-env validate-example --schema ./dist/env.schema.js
|
|
293
|
+
|
|
294
|
+
# one-off with npx
|
|
295
|
+
npx node-safe-env validate --schema ./dist/env.schema.js
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`validate` options:
|
|
299
|
+
|
|
300
|
+
- `--schema <path>` (required)
|
|
301
|
+
- `--cwd <path>`
|
|
302
|
+
- `--env-file <path>`
|
|
303
|
+
- `--node-env <value>`
|
|
304
|
+
- `--strict`
|
|
305
|
+
|
|
306
|
+
`validate-example` options:
|
|
307
|
+
|
|
308
|
+
- `--schema <path>` (required)
|
|
309
|
+
- `--cwd <path>`
|
|
310
|
+
- `--example-file <path>`
|
|
311
|
+
|
|
312
|
+
CLI schema loading note:
|
|
313
|
+
|
|
314
|
+
- the `--schema` target must be an executable JavaScript module
|
|
315
|
+
- accepted export shapes: default export or named export `schema`
|
|
316
|
+
- use built `.js`, `.mjs`, or `.cjs` files for CLI usage
|
|
317
|
+
|
|
318
|
+
## Error Handling
|
|
319
|
+
|
|
320
|
+
Validation issues are aggregated and thrown as `EnvValidationError`.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { createEnv, EnvValidationError } from "node-safe-env";
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
createEnv(schema);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
if (error instanceof EnvValidationError) {
|
|
329
|
+
console.error(error.issues);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## API Summary
|
|
335
|
+
|
|
336
|
+
Exports:
|
|
337
|
+
|
|
338
|
+
- `createEnv`
|
|
339
|
+
- `defineEnv`
|
|
340
|
+
- `EnvValidationError`
|
|
341
|
+
- `maskEnv`
|
|
342
|
+
- `mergeSources`
|
|
343
|
+
- `traceEnv`
|
|
344
|
+
- `validateExampleEnv`
|
|
345
|
+
- `validateExampleEnvFile`
|
|
346
|
+
- `readEnvFileSource`
|
|
347
|
+
- `resolveExampleEnvPath`
|
|
348
|
+
|
|
349
|
+
`createEnv(schema, options?)` options:
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
{
|
|
353
|
+
source?: Record<string, string | undefined>;
|
|
354
|
+
cwd?: string;
|
|
355
|
+
nodeEnv?: string;
|
|
356
|
+
envFile?: string;
|
|
357
|
+
strict?: boolean;
|
|
358
|
+
debug?: boolean | { logger?: (report: EnvDebugReport) => void };
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Compatibility
|
|
363
|
+
|
|
364
|
+
- Node.js >= 18
|
|
365
|
+
- ESM and CommonJS builds
|
|
366
|
+
- TypeScript declarations included
|
|
367
|
+
|
|
368
|
+
## Development Scripts
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
npm run lint
|
|
372
|
+
npm run build
|
|
373
|
+
npm run typecheck
|
|
374
|
+
npm run test
|
|
375
|
+
npm run test:watch
|
|
376
|
+
npm run check
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## License
|
|
380
|
+
|
|
381
|
+
MIT
|