@osmn-byhn/envguard 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 +539 -0
- package/dist/index.d.mts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +220 -0
- package/dist/index.mjs +183 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
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,539 @@
|
|
|
1
|
+
# EnvGuard
|
|
2
|
+
|
|
3
|
+
A type-safe environment variable validation and management library for Node.js. EnvGuard helps you validate, parse, and manage environment variables with a simple schema-based approach.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Type-safe validation** - Validate environment variables with TypeScript support
|
|
8
|
+
- 🔒 **Automatic type conversion** - Numbers, booleans, dates, JSON, arrays, and more
|
|
9
|
+
- 🛡️ **Strict validation** - Catch missing or invalid variables at startup
|
|
10
|
+
- 📝 **Schema-based** - Define your environment structure with a simple schema
|
|
11
|
+
- 🔐 **Security** - Automatic masking of sensitive values (SECRET, TOKEN)
|
|
12
|
+
- 📋 **Example generation** - Auto-generate `.env.example` files
|
|
13
|
+
- 🎯 **Multiple types** - Support for strings, numbers, booleans, URLs, emails, enums, JSON, arrays, dates, and custom patterns
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install envguard
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { loadEnv } from 'envguard';
|
|
25
|
+
|
|
26
|
+
const schema = {
|
|
27
|
+
PORT: "number",
|
|
28
|
+
DATABASE_URL: "string",
|
|
29
|
+
DEBUG: "boolean",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const env = loadEnv(schema);
|
|
33
|
+
|
|
34
|
+
console.log(env.PORT); // 3000 (number)
|
|
35
|
+
console.log(env.DATABASE_URL); // "postgresql://..." (string)
|
|
36
|
+
console.log(env.DEBUG); // true (boolean)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Documentation
|
|
40
|
+
|
|
41
|
+
### Basic Usage
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { loadEnv } from 'envguard';
|
|
45
|
+
import { EnvSchema } from 'envguard';
|
|
46
|
+
|
|
47
|
+
const schema: EnvSchema = {
|
|
48
|
+
PORT: "number",
|
|
49
|
+
DATABASE_URL: "string",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const env = loadEnv(schema);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Type System
|
|
56
|
+
|
|
57
|
+
EnvGuard supports multiple types for environment variable validation:
|
|
58
|
+
|
|
59
|
+
#### 1. String Type
|
|
60
|
+
|
|
61
|
+
Basic string validation. Use `"string"` for required strings, or `"string?"` for optional strings.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
const schema = {
|
|
65
|
+
API_KEY: "string", // Required
|
|
66
|
+
OPTIONAL_KEY: "string?", // Optional
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// .env
|
|
70
|
+
// API_KEY=my-secret-key
|
|
71
|
+
// OPTIONAL_KEY=optional-value (or omit this line)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Example:**
|
|
75
|
+
```typescript
|
|
76
|
+
const env = loadEnv(schema);
|
|
77
|
+
console.log(env.API_KEY); // "my-secret-key" (string)
|
|
78
|
+
console.log(env.OPTIONAL_KEY); // "optional-value" or undefined
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### 2. Number Type
|
|
82
|
+
|
|
83
|
+
Validates and converts string values to numbers.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const schema = {
|
|
87
|
+
PORT: "number",
|
|
88
|
+
TIMEOUT: "number?",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// .env
|
|
92
|
+
// PORT=3000
|
|
93
|
+
// TIMEOUT=5000
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Example:**
|
|
97
|
+
```typescript
|
|
98
|
+
const env = loadEnv(schema);
|
|
99
|
+
console.log(env.PORT); // 3000 (number, not string)
|
|
100
|
+
console.log(typeof env.PORT); // "number"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Error cases:**
|
|
104
|
+
- `PORT=abc` → Throws error: `[EnvGuard] PORT must be a number`
|
|
105
|
+
|
|
106
|
+
#### 3. Boolean Type
|
|
107
|
+
|
|
108
|
+
Converts string values to boolean. Accepts `"true"`, `"1"` (case-insensitive) as `true`, everything else as `false`.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const schema = {
|
|
112
|
+
DEBUG: "boolean",
|
|
113
|
+
ENABLE_FEATURE: "boolean?",
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// .env
|
|
117
|
+
// DEBUG=true
|
|
118
|
+
// ENABLE_FEATURE=1
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Example:**
|
|
122
|
+
```typescript
|
|
123
|
+
const env = loadEnv(schema);
|
|
124
|
+
console.log(env.DEBUG); // true (boolean)
|
|
125
|
+
console.log(env.ENABLE_FEATURE); // true (boolean)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Accepted values:**
|
|
129
|
+
- `true`, `True`, `TRUE`, `1` → `true`
|
|
130
|
+
- `false`, `False`, `FALSE`, `0`, `""`, or any other value → `false`
|
|
131
|
+
|
|
132
|
+
#### 4. URL Type
|
|
133
|
+
|
|
134
|
+
Validates that the value is a valid URL.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const schema = {
|
|
138
|
+
API_BASE_URL: "url",
|
|
139
|
+
WEBHOOK_URL: "url?",
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// .env
|
|
143
|
+
// API_BASE_URL=https://api.example.com
|
|
144
|
+
// WEBHOOK_URL=https://webhook.example.com/callback
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Example:**
|
|
148
|
+
```typescript
|
|
149
|
+
const env = loadEnv(schema);
|
|
150
|
+
console.log(env.API_BASE_URL); // "https://api.example.com"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Error cases:**
|
|
154
|
+
- `API_BASE_URL=not-a-url` → Throws error: `[EnvGuard] API_BASE_URL must be a valid URL`
|
|
155
|
+
|
|
156
|
+
#### 5. Email Type
|
|
157
|
+
|
|
158
|
+
Validates that the value is a valid email address.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const schema = {
|
|
162
|
+
ADMIN_EMAIL: "email",
|
|
163
|
+
SUPPORT_EMAIL: "email?",
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// .env
|
|
167
|
+
// ADMIN_EMAIL=admin@example.com
|
|
168
|
+
// SUPPORT_EMAIL=support@example.com
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Example:**
|
|
172
|
+
```typescript
|
|
173
|
+
const env = loadEnv(schema);
|
|
174
|
+
console.log(env.ADMIN_EMAIL); // "admin@example.com"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Error cases:**
|
|
178
|
+
- `ADMIN_EMAIL=invalid-email` → Throws error: `[EnvGuard] ADMIN_EMAIL must be a valid email`
|
|
179
|
+
|
|
180
|
+
#### 6. Enum Type
|
|
181
|
+
|
|
182
|
+
Restricts values to a specific set of allowed strings.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const schema = {
|
|
186
|
+
NODE_ENV: ["development", "production", "test"],
|
|
187
|
+
LOG_LEVEL: ["debug", "info", "warn", "error"],
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// .env
|
|
191
|
+
// NODE_ENV=production
|
|
192
|
+
// LOG_LEVEL=info
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Example:**
|
|
196
|
+
```typescript
|
|
197
|
+
const env = loadEnv(schema);
|
|
198
|
+
console.log(env.NODE_ENV); // "production"
|
|
199
|
+
console.log(env.LOG_LEVEL); // "info"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Error cases:**
|
|
203
|
+
- `NODE_ENV=staging` → Throws error: `[EnvGuard] Invalid value for NODE_ENV: staging, allowed: development, production, test`
|
|
204
|
+
|
|
205
|
+
#### 7. JSON Type
|
|
206
|
+
|
|
207
|
+
Parses and validates JSON strings.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const schema = {
|
|
211
|
+
CONFIG: "json",
|
|
212
|
+
FEATURE_FLAGS: "json?",
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// .env
|
|
216
|
+
// CONFIG={"timeout":5000,"retries":3}
|
|
217
|
+
// FEATURE_FLAGS={"feature1":true,"feature2":false}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Example:**
|
|
221
|
+
```typescript
|
|
222
|
+
const env = loadEnv(schema);
|
|
223
|
+
console.log(env.CONFIG); // { timeout: 5000, retries: 3 } (object)
|
|
224
|
+
console.log(env.FEATURE_FLAGS); // { feature1: true, feature2: false } (object)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Error cases:**
|
|
228
|
+
- `CONFIG={invalid json` → Throws error: `[EnvGuard] CONFIG must be a valid JSON`
|
|
229
|
+
|
|
230
|
+
#### 8. Array Type
|
|
231
|
+
|
|
232
|
+
Parses comma-separated values into an array.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const schema = {
|
|
236
|
+
ALLOWED_ORIGINS: "array",
|
|
237
|
+
FEATURES: "array?",
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// .env
|
|
241
|
+
// ALLOWED_ORIGINS=http://localhost:3000,https://example.com,https://app.example.com
|
|
242
|
+
// FEATURES=feature1,feature2,feature3
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Example:**
|
|
246
|
+
```typescript
|
|
247
|
+
const env = loadEnv(schema);
|
|
248
|
+
console.log(env.ALLOWED_ORIGINS); // ["http://localhost:3000", "https://example.com", "https://app.example.com"]
|
|
249
|
+
console.log(env.FEATURES); // ["feature1", "feature2", "feature3"]
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Note:** Values are automatically trimmed of whitespace.
|
|
253
|
+
|
|
254
|
+
#### 9. Date Type
|
|
255
|
+
|
|
256
|
+
Parses and validates date strings.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const schema = {
|
|
260
|
+
START_DATE: "date",
|
|
261
|
+
END_DATE: "date?",
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// .env
|
|
265
|
+
// START_DATE=2024-01-01
|
|
266
|
+
// END_DATE=2024-12-31
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Example:**
|
|
270
|
+
```typescript
|
|
271
|
+
const env = loadEnv(schema);
|
|
272
|
+
console.log(env.START_DATE); // Date object: 2024-01-01T00:00:00.000Z
|
|
273
|
+
console.log(env.START_DATE instanceof Date); // true
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Error cases:**
|
|
277
|
+
- `START_DATE=invalid-date` → Throws error: `[EnvGuard] START_DATE must be a valid date`
|
|
278
|
+
|
|
279
|
+
#### 10. Custom Type
|
|
280
|
+
|
|
281
|
+
Advanced validation with custom patterns, default values, and error handling.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const schema = {
|
|
285
|
+
SECRET_TOKEN: {
|
|
286
|
+
type: "string",
|
|
287
|
+
pattern: /^[A-Za-z0-9]{32,}$/,
|
|
288
|
+
description: "32 characters or longer alphanumeric token",
|
|
289
|
+
error: (err) => {
|
|
290
|
+
console.error("Custom error handler:", err.message);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
},
|
|
293
|
+
required: true,
|
|
294
|
+
},
|
|
295
|
+
LOG_LEVEL: {
|
|
296
|
+
type: ["debug", "info", "warn", "error"],
|
|
297
|
+
default: "info",
|
|
298
|
+
description: "Log level",
|
|
299
|
+
error: (err) => console.error(err),
|
|
300
|
+
},
|
|
301
|
+
API_VERSION: {
|
|
302
|
+
type: "string",
|
|
303
|
+
pattern: /^v\d+\.\d+\.\d+$/,
|
|
304
|
+
default: "v1.0.0",
|
|
305
|
+
description: "API version in semver format",
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// .env
|
|
310
|
+
// SECRET_TOKEN=MySuperSecretToken12345678901234567890
|
|
311
|
+
// LOG_LEVEL=debug (or omit for default "info")
|
|
312
|
+
// API_VERSION=v2.1.0
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Custom Type Options:**
|
|
316
|
+
- `type`: The base type (string, number, boolean, url, email, json, array, date, or enum array)
|
|
317
|
+
- `pattern`: Regular expression for pattern matching
|
|
318
|
+
- `default`: Default value if variable is missing or empty
|
|
319
|
+
- `description`: Human-readable description
|
|
320
|
+
- `error`: Custom error handler function
|
|
321
|
+
- `required`: Whether the variable is required (default: `true`)
|
|
322
|
+
|
|
323
|
+
**Example:**
|
|
324
|
+
```typescript
|
|
325
|
+
const env = loadEnv(schema);
|
|
326
|
+
console.log(env.SECRET_TOKEN); // "MySuperSecretToken12345678901234567890"
|
|
327
|
+
console.log(env.LOG_LEVEL); // "debug" or "info" (default)
|
|
328
|
+
console.log(env.API_VERSION); // "v2.1.0"
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Error cases:**
|
|
332
|
+
- `SECRET_TOKEN=short` → Throws error (doesn't match pattern)
|
|
333
|
+
- Pattern validation happens before type conversion
|
|
334
|
+
|
|
335
|
+
### Options
|
|
336
|
+
|
|
337
|
+
The `loadEnv` function accepts an optional second parameter with configuration options:
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
const env = loadEnv(schema, {
|
|
341
|
+
path: ".env", // Path to .env file (default: ".env")
|
|
342
|
+
strict: true, // Warn about unknown variables (default: false)
|
|
343
|
+
example: true, // Generate .env.example file (default: false)
|
|
344
|
+
error: (err) => { // Custom error handler
|
|
345
|
+
console.error(err.message);
|
|
346
|
+
process.exit(1);
|
|
347
|
+
},
|
|
348
|
+
debug: true, // Enable debug mode (default: false)
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### Options Reference
|
|
353
|
+
|
|
354
|
+
| Option | Type | Default | Description |
|
|
355
|
+
|--------|------|---------|-------------|
|
|
356
|
+
| `path` | `string` | `".env"` | Path to the environment file |
|
|
357
|
+
| `strict` | `boolean` | `false` | If `true`, warns about variables in `.env` that are not in the schema |
|
|
358
|
+
| `example` | `boolean` | `false` | If `true`, generates a `.env.example` file with all schema keys |
|
|
359
|
+
| `error` | `(error: Error) => void` | `undefined` | Custom error handler function |
|
|
360
|
+
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
361
|
+
|
|
362
|
+
### Error Handling
|
|
363
|
+
|
|
364
|
+
EnvGuard throws errors when validation fails. You can handle them with try-catch or use the `error` callback:
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// Method 1: Try-catch
|
|
368
|
+
try {
|
|
369
|
+
const env = loadEnv(schema);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error("Validation failed:", error.message);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Method 2: Error callback
|
|
376
|
+
const env = loadEnv(schema, {
|
|
377
|
+
error: (err) => {
|
|
378
|
+
console.error("Validation failed:", err.message);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Common Errors:**
|
|
385
|
+
- `[EnvGuard] Missing env variable: PORT` - Required variable is missing
|
|
386
|
+
- `[EnvGuard] PORT must be a number` - Type validation failed
|
|
387
|
+
- `[EnvGuard] Invalid value for NODE_ENV: staging, allowed: development, production, test` - Enum validation failed
|
|
388
|
+
- `[EnvGuard] Failed to parse .env file at /path/to/.env: ...` - File parsing error
|
|
389
|
+
|
|
390
|
+
### Security Features
|
|
391
|
+
|
|
392
|
+
EnvGuard automatically masks sensitive values when accessed through the returned object:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
const schema = {
|
|
396
|
+
SECRET_TOKEN: "string",
|
|
397
|
+
API_KEY: "string",
|
|
398
|
+
DATABASE_PASSWORD: "string",
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const env = loadEnv(schema);
|
|
402
|
+
|
|
403
|
+
// .env
|
|
404
|
+
// SECRET_TOKEN=my-secret
|
|
405
|
+
// API_KEY=my-key
|
|
406
|
+
// DATABASE_PASSWORD=my-password
|
|
407
|
+
|
|
408
|
+
console.log(env.SECRET_TOKEN); // "[HIDDEN]"
|
|
409
|
+
console.log(env.API_KEY); // "[HIDDEN]"
|
|
410
|
+
console.log(env.DATABASE_PASSWORD); // "[HIDDEN]"
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Note:** Variables containing "SECRET" or "TOKEN" in their names are automatically masked.
|
|
414
|
+
|
|
415
|
+
### Generating .env.example
|
|
416
|
+
|
|
417
|
+
You can automatically generate a `.env.example` file:
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
const env = loadEnv(schema, {
|
|
421
|
+
example: true,
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
This creates a `.env.example` file with all schema keys (without values):
|
|
426
|
+
|
|
427
|
+
```env
|
|
428
|
+
DATABASE_URL=
|
|
429
|
+
PORT=
|
|
430
|
+
DEBUG=
|
|
431
|
+
API_BASE_URL=
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Strict Mode
|
|
435
|
+
|
|
436
|
+
Enable strict mode to get warnings about unknown variables in your `.env` file:
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
const env = loadEnv(schema, {
|
|
440
|
+
strict: true,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// If .env contains variables not in schema:
|
|
444
|
+
// [EnvGuard] Unknown env variables: UNKNOWN_VAR, ANOTHER_UNKNOWN
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Complete Example
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
import { loadEnv } from 'envguard';
|
|
451
|
+
import { EnvSchema } from 'envguard';
|
|
452
|
+
|
|
453
|
+
const schema: EnvSchema = {
|
|
454
|
+
// Required variables
|
|
455
|
+
DATABASE_URL: "string",
|
|
456
|
+
PORT: "number",
|
|
457
|
+
DEBUG: "boolean",
|
|
458
|
+
API_BASE_URL: "url",
|
|
459
|
+
ADMIN_EMAIL: "email",
|
|
460
|
+
NODE_ENV: ["development", "production", "test"],
|
|
461
|
+
|
|
462
|
+
// Optional variables
|
|
463
|
+
API_KEY: "string?",
|
|
464
|
+
TIMEOUT: "number?",
|
|
465
|
+
|
|
466
|
+
// Complex types
|
|
467
|
+
CONFIG: "json",
|
|
468
|
+
ALLOWED_ORIGINS: "array",
|
|
469
|
+
START_DATE: "date",
|
|
470
|
+
|
|
471
|
+
// Custom validation
|
|
472
|
+
SECRET_TOKEN: {
|
|
473
|
+
type: "string",
|
|
474
|
+
pattern: /^[A-Za-z0-9]{32,}$/,
|
|
475
|
+
required: true,
|
|
476
|
+
},
|
|
477
|
+
LOG_LEVEL: {
|
|
478
|
+
type: ["debug", "info", "warn", "error"],
|
|
479
|
+
default: "info",
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const env = loadEnv(schema, {
|
|
484
|
+
strict: true,
|
|
485
|
+
example: true,
|
|
486
|
+
error: (err) => {
|
|
487
|
+
console.error("❌ Environment validation failed:", err.message);
|
|
488
|
+
process.exit(1);
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Use validated environment variables
|
|
493
|
+
console.log(`Server starting on port ${env.PORT}`);
|
|
494
|
+
console.log(`Database: ${env.DATABASE_URL}`);
|
|
495
|
+
console.log(`Environment: ${env.NODE_ENV}`);
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## TypeScript Support
|
|
499
|
+
|
|
500
|
+
EnvGuard is written in TypeScript and provides full type definitions:
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
import { loadEnv, EnvSchema, ParsedEnv } from 'envguard';
|
|
504
|
+
|
|
505
|
+
const schema: EnvSchema = {
|
|
506
|
+
PORT: "number",
|
|
507
|
+
DATABASE_URL: "string",
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const env: ParsedEnv = loadEnv(schema);
|
|
511
|
+
// env is fully typed!
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## Best Practices
|
|
515
|
+
|
|
516
|
+
1. **Define all environment variables in your schema** - This serves as documentation
|
|
517
|
+
2. **Use strict mode in production** - Catch unknown variables early
|
|
518
|
+
3. **Generate .env.example** - Help other developers know what variables are needed
|
|
519
|
+
4. **Use custom types for complex validation** - Patterns, defaults, and custom error handling
|
|
520
|
+
5. **Handle errors gracefully** - Use error callbacks or try-catch blocks
|
|
521
|
+
6. **Use optional types (`?`)** - For variables that have sensible defaults or are truly optional
|
|
522
|
+
|
|
523
|
+
## Contributing
|
|
524
|
+
|
|
525
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
526
|
+
|
|
527
|
+
## License
|
|
528
|
+
|
|
529
|
+
MIT
|
|
530
|
+
|
|
531
|
+
## Changelog
|
|
532
|
+
|
|
533
|
+
### 0.1.0
|
|
534
|
+
- Initial release
|
|
535
|
+
- Support for all basic types (string, number, boolean, url, email, enum, json, array, date)
|
|
536
|
+
- Custom type validation with patterns
|
|
537
|
+
- Automatic sensitive value masking
|
|
538
|
+
- .env.example generation
|
|
539
|
+
- Strict mode for unknown variables
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type PrimitiveType = "string" | "number" | "boolean" | "url" | "email" | "json" | "date" | "array" | `${string}?`;
|
|
2
|
+
type EnumType = string[];
|
|
3
|
+
type CustomType = {
|
|
4
|
+
type: PrimitiveType | EnumType;
|
|
5
|
+
pattern?: RegExp;
|
|
6
|
+
default?: any;
|
|
7
|
+
description?: string;
|
|
8
|
+
error: (error: Error) => void;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
};
|
|
11
|
+
type EnvType = PrimitiveType | EnumType | CustomType;
|
|
12
|
+
type EnvSchema = Record<string, EnvType>;
|
|
13
|
+
interface EnvGuardOptions {
|
|
14
|
+
path?: string;
|
|
15
|
+
strict?: boolean;
|
|
16
|
+
example?: boolean;
|
|
17
|
+
maskSensitive?: boolean;
|
|
18
|
+
defaults?: boolean;
|
|
19
|
+
error?: (error: Error) => void;
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
}
|
|
22
|
+
type ParsedEnv = Record<string, string | number | boolean | object | Date | string[]>;
|
|
23
|
+
|
|
24
|
+
declare function loadEnv(schema: EnvSchema, options?: EnvGuardOptions): ParsedEnv;
|
|
25
|
+
|
|
26
|
+
export { type CustomType, type EnumType, type EnvGuardOptions, type EnvSchema, type EnvType, type ParsedEnv, type PrimitiveType, loadEnv };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type PrimitiveType = "string" | "number" | "boolean" | "url" | "email" | "json" | "date" | "array" | `${string}?`;
|
|
2
|
+
type EnumType = string[];
|
|
3
|
+
type CustomType = {
|
|
4
|
+
type: PrimitiveType | EnumType;
|
|
5
|
+
pattern?: RegExp;
|
|
6
|
+
default?: any;
|
|
7
|
+
description?: string;
|
|
8
|
+
error: (error: Error) => void;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
};
|
|
11
|
+
type EnvType = PrimitiveType | EnumType | CustomType;
|
|
12
|
+
type EnvSchema = Record<string, EnvType>;
|
|
13
|
+
interface EnvGuardOptions {
|
|
14
|
+
path?: string;
|
|
15
|
+
strict?: boolean;
|
|
16
|
+
example?: boolean;
|
|
17
|
+
maskSensitive?: boolean;
|
|
18
|
+
defaults?: boolean;
|
|
19
|
+
error?: (error: Error) => void;
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
}
|
|
22
|
+
type ParsedEnv = Record<string, string | number | boolean | object | Date | string[]>;
|
|
23
|
+
|
|
24
|
+
declare function loadEnv(schema: EnvSchema, options?: EnvGuardOptions): ParsedEnv;
|
|
25
|
+
|
|
26
|
+
export { type CustomType, type EnumType, type EnvGuardOptions, type EnvSchema, type EnvType, type ParsedEnv, type PrimitiveType, loadEnv };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
loadEnv: () => loadEnv
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/parser.ts
|
|
38
|
+
var import_fs = __toESM(require("fs"));
|
|
39
|
+
var import_path = __toESM(require("path"));
|
|
40
|
+
var import_dotenv = __toESM(require("dotenv"));
|
|
41
|
+
function parseEnvFile(filePath) {
|
|
42
|
+
const fullPath = import_path.default.resolve(process.cwd(), filePath);
|
|
43
|
+
if (!import_fs.default.existsSync(fullPath)) return {};
|
|
44
|
+
try {
|
|
45
|
+
const content = import_fs.default.readFileSync(fullPath, "utf-8");
|
|
46
|
+
return import_dotenv.default.parse(content);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`[EnvGuard] Failed to parse .env file at ${fullPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/validator.ts
|
|
55
|
+
function isValidUrl(value) {
|
|
56
|
+
try {
|
|
57
|
+
new URL(value);
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function isValidEmail(value) {
|
|
64
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
65
|
+
}
|
|
66
|
+
function validateEnv(raw, schema) {
|
|
67
|
+
const result = {};
|
|
68
|
+
for (const key in schema) {
|
|
69
|
+
const rule = schema[key];
|
|
70
|
+
let value = raw[key];
|
|
71
|
+
let optional = false;
|
|
72
|
+
let type;
|
|
73
|
+
let pattern;
|
|
74
|
+
let defaultValue = void 0;
|
|
75
|
+
if (typeof rule === "string") {
|
|
76
|
+
optional = rule.endsWith("?");
|
|
77
|
+
type = rule.replace("?", "");
|
|
78
|
+
} else if (Array.isArray(rule)) {
|
|
79
|
+
type = rule;
|
|
80
|
+
} else {
|
|
81
|
+
type = rule.type;
|
|
82
|
+
optional = rule.required === false || String(rule.type).endsWith("?");
|
|
83
|
+
pattern = rule.pattern;
|
|
84
|
+
defaultValue = rule.default;
|
|
85
|
+
}
|
|
86
|
+
if ((value === void 0 || value === "") && defaultValue !== void 0) {
|
|
87
|
+
value = defaultValue;
|
|
88
|
+
}
|
|
89
|
+
if ((value === void 0 || value === "") && !optional) {
|
|
90
|
+
throw new Error(`[EnvGuard] Missing env variable: ${key}`);
|
|
91
|
+
}
|
|
92
|
+
if (value === void 0 || value === "") continue;
|
|
93
|
+
if (Array.isArray(type)) {
|
|
94
|
+
if (!type.includes(value)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`[EnvGuard] Invalid value for ${key}: ${value}, allowed: ${type.join(", ")}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
result[key] = value;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (pattern && !pattern.test(value)) {
|
|
103
|
+
throw new Error(`[EnvGuard] ${key} does not match pattern: ${pattern}`);
|
|
104
|
+
}
|
|
105
|
+
switch (type) {
|
|
106
|
+
case "number":
|
|
107
|
+
if (isNaN(Number(value))) {
|
|
108
|
+
throw new Error(`[EnvGuard] ${key} must be a number`);
|
|
109
|
+
}
|
|
110
|
+
result[key] = Number(value);
|
|
111
|
+
break;
|
|
112
|
+
case "boolean":
|
|
113
|
+
result[key] = ["true", "1"].includes(String(value).toLowerCase());
|
|
114
|
+
break;
|
|
115
|
+
case "url":
|
|
116
|
+
if (!isValidUrl(value)) {
|
|
117
|
+
throw new Error(`[EnvGuard] ${key} must be a valid URL`);
|
|
118
|
+
}
|
|
119
|
+
result[key] = value;
|
|
120
|
+
break;
|
|
121
|
+
case "email":
|
|
122
|
+
if (!isValidEmail(value)) {
|
|
123
|
+
throw new Error(`[EnvGuard] ${key} must be a valid email`);
|
|
124
|
+
}
|
|
125
|
+
result[key] = value;
|
|
126
|
+
break;
|
|
127
|
+
case "json":
|
|
128
|
+
try {
|
|
129
|
+
result[key] = JSON.parse(value);
|
|
130
|
+
} catch {
|
|
131
|
+
throw new Error(`[EnvGuard] ${key} must be a valid JSON`);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
case "array":
|
|
135
|
+
result[key] = value.split(",").map((v) => v.trim());
|
|
136
|
+
break;
|
|
137
|
+
case "date":
|
|
138
|
+
const d = new Date(value);
|
|
139
|
+
if (isNaN(d.getTime())) {
|
|
140
|
+
throw new Error(`[EnvGuard] ${key} must be a valid date`);
|
|
141
|
+
}
|
|
142
|
+
result[key] = d;
|
|
143
|
+
break;
|
|
144
|
+
default:
|
|
145
|
+
result[key] = value;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/exampleGenerator.ts
|
|
152
|
+
var import_fs2 = __toESM(require("fs"));
|
|
153
|
+
function generateExampleFile(schema) {
|
|
154
|
+
const lines = Object.keys(schema).map((key) => `${key}=`).join("\n");
|
|
155
|
+
import_fs2.default.writeFileSync(".env.example", lines);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/index.ts
|
|
159
|
+
var import_fs3 = __toESM(require("fs"));
|
|
160
|
+
var import_path2 = __toESM(require("path"));
|
|
161
|
+
function loadEnv(schema, options = {}) {
|
|
162
|
+
const { path: envPath = ".env", example = false, strict = false, error } = options;
|
|
163
|
+
const fullPath = import_path2.default.resolve(process.cwd(), envPath);
|
|
164
|
+
const envFileExists = import_fs3.default.existsSync(fullPath);
|
|
165
|
+
if (envFileExists) {
|
|
166
|
+
try {
|
|
167
|
+
const raw = parseEnvFile(envPath);
|
|
168
|
+
const env = validateEnv(raw, schema);
|
|
169
|
+
if (example) generateExampleFile(schema);
|
|
170
|
+
if (strict) {
|
|
171
|
+
const extraKeys = Object.keys(raw).filter((k) => !schema[k]);
|
|
172
|
+
if (extraKeys.length) {
|
|
173
|
+
const warning = `[EnvGuard] Unknown env variables: ${extraKeys.join(", ")}`;
|
|
174
|
+
console.warn(warning);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return new Proxy(env, {
|
|
178
|
+
get(target, prop) {
|
|
179
|
+
if (prop.includes("SECRET") || prop.includes("TOKEN")) {
|
|
180
|
+
return "[HIDDEN]";
|
|
181
|
+
}
|
|
182
|
+
return target[prop];
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
} catch (validationError) {
|
|
186
|
+
const errorMessage = validationError instanceof Error ? validationError.message : String(validationError);
|
|
187
|
+
if (error) {
|
|
188
|
+
error(new Error(errorMessage));
|
|
189
|
+
} else {
|
|
190
|
+
throw validationError;
|
|
191
|
+
}
|
|
192
|
+
throw new Error(errorMessage);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
try {
|
|
196
|
+
const env = validateEnv({}, schema);
|
|
197
|
+
if (example) generateExampleFile(schema);
|
|
198
|
+
return new Proxy(env, {
|
|
199
|
+
get(target, prop) {
|
|
200
|
+
if (prop.includes("SECRET") || prop.includes("TOKEN")) {
|
|
201
|
+
return "[HIDDEN]";
|
|
202
|
+
}
|
|
203
|
+
return target[prop];
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
} catch (validationError) {
|
|
207
|
+
const errorMessage = validationError instanceof Error ? validationError.message : String(validationError);
|
|
208
|
+
if (error) {
|
|
209
|
+
error(new Error(errorMessage));
|
|
210
|
+
} else {
|
|
211
|
+
throw validationError;
|
|
212
|
+
}
|
|
213
|
+
throw new Error(errorMessage);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
218
|
+
0 && (module.exports = {
|
|
219
|
+
loadEnv
|
|
220
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// src/parser.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
function parseEnvFile(filePath) {
|
|
6
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
7
|
+
if (!fs.existsSync(fullPath)) return {};
|
|
8
|
+
try {
|
|
9
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
10
|
+
return dotenv.parse(content);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`[EnvGuard] Failed to parse .env file at ${fullPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/validator.ts
|
|
19
|
+
function isValidUrl(value) {
|
|
20
|
+
try {
|
|
21
|
+
new URL(value);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isValidEmail(value) {
|
|
28
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
29
|
+
}
|
|
30
|
+
function validateEnv(raw, schema) {
|
|
31
|
+
const result = {};
|
|
32
|
+
for (const key in schema) {
|
|
33
|
+
const rule = schema[key];
|
|
34
|
+
let value = raw[key];
|
|
35
|
+
let optional = false;
|
|
36
|
+
let type;
|
|
37
|
+
let pattern;
|
|
38
|
+
let defaultValue = void 0;
|
|
39
|
+
if (typeof rule === "string") {
|
|
40
|
+
optional = rule.endsWith("?");
|
|
41
|
+
type = rule.replace("?", "");
|
|
42
|
+
} else if (Array.isArray(rule)) {
|
|
43
|
+
type = rule;
|
|
44
|
+
} else {
|
|
45
|
+
type = rule.type;
|
|
46
|
+
optional = rule.required === false || String(rule.type).endsWith("?");
|
|
47
|
+
pattern = rule.pattern;
|
|
48
|
+
defaultValue = rule.default;
|
|
49
|
+
}
|
|
50
|
+
if ((value === void 0 || value === "") && defaultValue !== void 0) {
|
|
51
|
+
value = defaultValue;
|
|
52
|
+
}
|
|
53
|
+
if ((value === void 0 || value === "") && !optional) {
|
|
54
|
+
throw new Error(`[EnvGuard] Missing env variable: ${key}`);
|
|
55
|
+
}
|
|
56
|
+
if (value === void 0 || value === "") continue;
|
|
57
|
+
if (Array.isArray(type)) {
|
|
58
|
+
if (!type.includes(value)) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`[EnvGuard] Invalid value for ${key}: ${value}, allowed: ${type.join(", ")}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
result[key] = value;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (pattern && !pattern.test(value)) {
|
|
67
|
+
throw new Error(`[EnvGuard] ${key} does not match pattern: ${pattern}`);
|
|
68
|
+
}
|
|
69
|
+
switch (type) {
|
|
70
|
+
case "number":
|
|
71
|
+
if (isNaN(Number(value))) {
|
|
72
|
+
throw new Error(`[EnvGuard] ${key} must be a number`);
|
|
73
|
+
}
|
|
74
|
+
result[key] = Number(value);
|
|
75
|
+
break;
|
|
76
|
+
case "boolean":
|
|
77
|
+
result[key] = ["true", "1"].includes(String(value).toLowerCase());
|
|
78
|
+
break;
|
|
79
|
+
case "url":
|
|
80
|
+
if (!isValidUrl(value)) {
|
|
81
|
+
throw new Error(`[EnvGuard] ${key} must be a valid URL`);
|
|
82
|
+
}
|
|
83
|
+
result[key] = value;
|
|
84
|
+
break;
|
|
85
|
+
case "email":
|
|
86
|
+
if (!isValidEmail(value)) {
|
|
87
|
+
throw new Error(`[EnvGuard] ${key} must be a valid email`);
|
|
88
|
+
}
|
|
89
|
+
result[key] = value;
|
|
90
|
+
break;
|
|
91
|
+
case "json":
|
|
92
|
+
try {
|
|
93
|
+
result[key] = JSON.parse(value);
|
|
94
|
+
} catch {
|
|
95
|
+
throw new Error(`[EnvGuard] ${key} must be a valid JSON`);
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
case "array":
|
|
99
|
+
result[key] = value.split(",").map((v) => v.trim());
|
|
100
|
+
break;
|
|
101
|
+
case "date":
|
|
102
|
+
const d = new Date(value);
|
|
103
|
+
if (isNaN(d.getTime())) {
|
|
104
|
+
throw new Error(`[EnvGuard] ${key} must be a valid date`);
|
|
105
|
+
}
|
|
106
|
+
result[key] = d;
|
|
107
|
+
break;
|
|
108
|
+
default:
|
|
109
|
+
result[key] = value;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/exampleGenerator.ts
|
|
116
|
+
import fs2 from "fs";
|
|
117
|
+
function generateExampleFile(schema) {
|
|
118
|
+
const lines = Object.keys(schema).map((key) => `${key}=`).join("\n");
|
|
119
|
+
fs2.writeFileSync(".env.example", lines);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/index.ts
|
|
123
|
+
import fs3 from "fs";
|
|
124
|
+
import path2 from "path";
|
|
125
|
+
function loadEnv(schema, options = {}) {
|
|
126
|
+
const { path: envPath = ".env", example = false, strict = false, error } = options;
|
|
127
|
+
const fullPath = path2.resolve(process.cwd(), envPath);
|
|
128
|
+
const envFileExists = fs3.existsSync(fullPath);
|
|
129
|
+
if (envFileExists) {
|
|
130
|
+
try {
|
|
131
|
+
const raw = parseEnvFile(envPath);
|
|
132
|
+
const env = validateEnv(raw, schema);
|
|
133
|
+
if (example) generateExampleFile(schema);
|
|
134
|
+
if (strict) {
|
|
135
|
+
const extraKeys = Object.keys(raw).filter((k) => !schema[k]);
|
|
136
|
+
if (extraKeys.length) {
|
|
137
|
+
const warning = `[EnvGuard] Unknown env variables: ${extraKeys.join(", ")}`;
|
|
138
|
+
console.warn(warning);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return new Proxy(env, {
|
|
142
|
+
get(target, prop) {
|
|
143
|
+
if (prop.includes("SECRET") || prop.includes("TOKEN")) {
|
|
144
|
+
return "[HIDDEN]";
|
|
145
|
+
}
|
|
146
|
+
return target[prop];
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
} catch (validationError) {
|
|
150
|
+
const errorMessage = validationError instanceof Error ? validationError.message : String(validationError);
|
|
151
|
+
if (error) {
|
|
152
|
+
error(new Error(errorMessage));
|
|
153
|
+
} else {
|
|
154
|
+
throw validationError;
|
|
155
|
+
}
|
|
156
|
+
throw new Error(errorMessage);
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
try {
|
|
160
|
+
const env = validateEnv({}, schema);
|
|
161
|
+
if (example) generateExampleFile(schema);
|
|
162
|
+
return new Proxy(env, {
|
|
163
|
+
get(target, prop) {
|
|
164
|
+
if (prop.includes("SECRET") || prop.includes("TOKEN")) {
|
|
165
|
+
return "[HIDDEN]";
|
|
166
|
+
}
|
|
167
|
+
return target[prop];
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
} catch (validationError) {
|
|
171
|
+
const errorMessage = validationError instanceof Error ? validationError.message : String(validationError);
|
|
172
|
+
if (error) {
|
|
173
|
+
error(new Error(errorMessage));
|
|
174
|
+
} else {
|
|
175
|
+
throw validationError;
|
|
176
|
+
}
|
|
177
|
+
throw new Error(errorMessage);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
loadEnv
|
|
183
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@osmn-byhn/envguard",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Type-safe environment variable validation and management for Node.js",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"env",
|
|
22
|
+
"environment",
|
|
23
|
+
"variables",
|
|
24
|
+
"validation",
|
|
25
|
+
"schema",
|
|
26
|
+
"typescript",
|
|
27
|
+
"dotenv",
|
|
28
|
+
"config",
|
|
29
|
+
"envguard"
|
|
30
|
+
],
|
|
31
|
+
"author": "",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/osmn-byhn/envGuard"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
39
|
+
"dev": "tsup src/index.ts --watch",
|
|
40
|
+
"lint": "eslint src --ext .ts",
|
|
41
|
+
"example": "tsx example.ts",
|
|
42
|
+
"prepublishOnly": "npm run build"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"dotenv": "^16.4.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^20.0.0",
|
|
49
|
+
"tsup": "^8.0.1",
|
|
50
|
+
"typescript": "^5.5.0",
|
|
51
|
+
"eslint": "^9.0.0",
|
|
52
|
+
"prettier": "^3.2.0",
|
|
53
|
+
"tsx": "^4.7.0"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=16.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|