@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 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
@@ -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 };
@@ -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
+ }