molex-env 0.2.2 → 0.2.3
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 +723 -114
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,181 +3,790 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/molex-env)
|
|
4
4
|
[](https://www.npmjs.com/package/molex-env)
|
|
5
5
|
[](LICENSE)
|
|
6
|
-
[](https://nodejs.org)
|
|
7
|
+
[](package.json)
|
|
7
8
|
|
|
8
|
-
> Native .menv loader with
|
|
9
|
+
> **Native .menv environment loader with profile support, typed parsing, origin tracking, and live reload. Zero dependencies.**
|
|
9
10
|
|
|
10
11
|
## Features
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
|
|
13
|
+
- **Zero dependencies** - Pure Node.js implementation
|
|
14
|
+
- **Profile support** - Environment-specific configs (dev, prod, staging)
|
|
15
|
+
- **Type-safe parsing** - Automatic conversion of booleans, numbers, JSON, and dates
|
|
16
|
+
- **Strict validation** - Schema enforcement with required fields and type checking
|
|
17
|
+
- **Origin tracking** - Know exactly which file and line each value came from
|
|
18
|
+
- **Immutable config** - Deep-freeze protection prevents accidental modifications
|
|
19
|
+
- **Live reload** - Watch mode automatically reloads on file changes
|
|
20
|
+
- **Deterministic merging** - Predictable cascading from base to profile files
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
19
24
|
```bash
|
|
20
25
|
npm install molex-env
|
|
21
26
|
```
|
|
22
27
|
|
|
23
|
-
## Quick
|
|
24
|
-
```js
|
|
25
|
-
// simplest usage
|
|
26
|
-
require('molex-env').load();
|
|
28
|
+
## Quick Start
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
```javascript
|
|
29
31
|
const { load } = require('molex-env');
|
|
30
32
|
|
|
33
|
+
// Simplest usage - loads .menv files and attaches to process.menv
|
|
34
|
+
require('molex-env').load();
|
|
35
|
+
console.log(process.menv.PORT); // Access typed values
|
|
36
|
+
|
|
37
|
+
// With profile and schema validation
|
|
31
38
|
const result = load({
|
|
32
39
|
profile: 'prod',
|
|
33
40
|
strict: true,
|
|
34
41
|
schema: {
|
|
35
42
|
PORT: 'number',
|
|
36
43
|
DEBUG: 'boolean',
|
|
37
|
-
SERVICE_URL: { type: 'string', required: true }
|
|
44
|
+
SERVICE_URL: { type: 'string', required: true },
|
|
45
|
+
METADATA: 'json'
|
|
38
46
|
}
|
|
39
47
|
});
|
|
40
48
|
|
|
41
|
-
console.log(result.parsed.PORT);
|
|
42
|
-
console.log(result.
|
|
43
|
-
console.log(
|
|
49
|
+
console.log(result.parsed.PORT); // 3000 (number)
|
|
50
|
+
console.log(result.parsed.DEBUG); // false (boolean)
|
|
51
|
+
console.log(result.origins.SERVICE_URL); // { file: '.menv', line: 3 }
|
|
52
|
+
console.log(process.menv.METADATA.region); // 'us-east-1' (parsed JSON)
|
|
44
53
|
```
|
|
45
54
|
|
|
46
55
|
## Setup
|
|
47
|
-
1) Add one or more .menv files in your project root.
|
|
48
|
-
2) Call `load()` during startup.
|
|
49
|
-
3) Optionally enable profile-specific files (e.g. `prod`).
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
**1. Create .menv files in your project root:**
|
|
58
|
+
|
|
59
|
+
```env
|
|
60
|
+
# .menv (base configuration)
|
|
61
|
+
PORT=3000
|
|
62
|
+
DEBUG=false
|
|
63
|
+
SERVICE_URL=https://api.example.com
|
|
64
|
+
DATABASE_URL=postgres://localhost:5432/myapp
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**2. Add profile-specific overrides (optional):**
|
|
68
|
+
|
|
69
|
+
```env
|
|
70
|
+
# .menv.prod (production overrides)
|
|
71
|
+
DEBUG=false
|
|
72
|
+
SERVICE_URL=https://api.production.com
|
|
73
|
+
DATABASE_URL=postgres://prod-server:5432/myapp
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```env
|
|
77
|
+
# .menv.local (local machine overrides - add to .gitignore)
|
|
78
|
+
DEBUG=true
|
|
79
|
+
DATABASE_URL=postgres://localhost:5432/myapp_dev
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**3. Load during application startup:**
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
// Load with production profile
|
|
86
|
+
require('molex-env').load({ profile: 'prod' });
|
|
87
|
+
|
|
88
|
+
// Now use your typed config
|
|
89
|
+
const app = express();
|
|
90
|
+
app.listen(process.menv.PORT);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## File Format
|
|
94
|
+
|
|
95
|
+
molex-env supports simple key=value syntax with automatic type detection:
|
|
96
|
+
|
|
52
97
|
```env
|
|
53
98
|
# Comments start with #
|
|
99
|
+
# Strings (quotes are optional)
|
|
100
|
+
SERVICE_URL=https://api.example.com
|
|
101
|
+
API_KEY="secret-key-123"
|
|
102
|
+
|
|
103
|
+
# Numbers (integers and floats)
|
|
54
104
|
PORT=3000
|
|
105
|
+
TIMEOUT=30.5
|
|
106
|
+
|
|
107
|
+
# Booleans (case-insensitive)
|
|
55
108
|
DEBUG=true
|
|
56
|
-
|
|
57
|
-
|
|
109
|
+
ENABLE_CACHE=FALSE
|
|
110
|
+
|
|
111
|
+
# JSON objects and arrays
|
|
112
|
+
METADATA={"region":"us-east-1","tier":"premium"}
|
|
113
|
+
ALLOWED_IPS=["192.168.1.1","10.0.0.1"]
|
|
114
|
+
|
|
115
|
+
# Dates (ISO 8601 format)
|
|
58
116
|
START_DATE=2026-02-02
|
|
117
|
+
EXPIRES_AT=2026-12-31T23:59:59Z
|
|
118
|
+
|
|
119
|
+
# Empty values
|
|
120
|
+
OPTIONAL_KEY=
|
|
59
121
|
```
|
|
60
122
|
|
|
61
|
-
## File
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
123
|
+
## File Precedence
|
|
124
|
+
|
|
125
|
+
Files are loaded and merged in this order (later files override earlier ones):
|
|
126
|
+
|
|
127
|
+
1. `.menv` - Base configuration (committed to git)
|
|
128
|
+
2. `.menv.local` - Local overrides (ignored by git)
|
|
129
|
+
3. `.menv.{profile}` - Profile-specific config (e.g., `.menv.prod`)
|
|
130
|
+
4. `.menv.{profile}.local` - Profile + local overrides (e.g., `.menv.prod.local`)
|
|
131
|
+
|
|
132
|
+
**Example with `profile: 'prod'`:**
|
|
133
|
+
```
|
|
134
|
+
.menv → PORT=3000, DEBUG=true
|
|
135
|
+
.menv.local → (overrides) DEBUG=false
|
|
136
|
+
.menv.prod → (overrides) PORT=8080
|
|
137
|
+
.menv.prod.local → (overrides) PORT=9000
|
|
138
|
+
Final result: PORT=9000, DEBUG=false
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## API Reference
|
|
142
|
+
|
|
143
|
+
### `load(options)` → `Object`
|
|
144
|
+
|
|
145
|
+
Load, merge, parse, and validate .menv files. This is the primary method you'll use.
|
|
146
|
+
|
|
147
|
+
**Options:**
|
|
148
|
+
|
|
149
|
+
| Option | Type | Default | Description |
|
|
150
|
+
|--------|------|---------|-------------|
|
|
151
|
+
| `cwd` | `string` | `process.cwd()` | Base directory to resolve files from |
|
|
152
|
+
| `profile` | `string` | `undefined` | Profile name for `.menv.{profile}` files |
|
|
153
|
+
| `files` | `Array<string>` | Auto-detected | Custom file list (absolute or relative to cwd) |
|
|
154
|
+
| `schema` | `Object` | `{}` | Schema definition for validation and typing |
|
|
155
|
+
| `strict` | `boolean` | `false` | Reject unknown keys, duplicates, and invalid lines |
|
|
156
|
+
| `cast` | `boolean\|Object` | `true` | Enable/disable type casting (see Type Casting) |
|
|
157
|
+
| `exportEnv` | `boolean` | `false` | Write parsed values to `process.env` |
|
|
158
|
+
| `override` | `boolean` | `false` | Override existing `process.env` values |
|
|
159
|
+
| `attach` | `boolean` | `true` | Attach parsed values to `process.menv` |
|
|
160
|
+
| `freeze` | `boolean` | `true` | Deep-freeze the parsed config object |
|
|
161
|
+
| `onWarning` | `Function` | `undefined` | Callback for non-strict warnings |
|
|
162
|
+
|
|
163
|
+
**Returns:**
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
87
166
|
{
|
|
88
|
-
parsed, //
|
|
89
|
-
raw, //
|
|
90
|
-
origins, // { KEY: { file, line } }
|
|
91
|
-
files
|
|
167
|
+
parsed: Object, // Typed configuration values
|
|
168
|
+
raw: Object, // Raw string values before parsing
|
|
169
|
+
origins: Object, // Source tracking: { KEY: { file, line } }
|
|
170
|
+
files: Array // List of resolved file paths
|
|
92
171
|
}
|
|
93
172
|
```
|
|
94
173
|
|
|
95
|
-
|
|
96
|
-
Parse a string of .menv content using the same typing rules as `load()`.
|
|
174
|
+
**Examples:**
|
|
97
175
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
- freeze
|
|
176
|
+
```javascript
|
|
177
|
+
// Basic usage
|
|
178
|
+
const result = load();
|
|
179
|
+
console.log(result.parsed);
|
|
103
180
|
|
|
104
|
-
|
|
105
|
-
|
|
181
|
+
// With profile
|
|
182
|
+
const result = load({ profile: 'production' });
|
|
106
183
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
- onChange: function(err, result)
|
|
184
|
+
// Custom directory
|
|
185
|
+
const result = load({ cwd: '/app/config' });
|
|
110
186
|
|
|
111
|
-
|
|
112
|
-
|
|
187
|
+
// Export to process.env
|
|
188
|
+
load({ exportEnv: true });
|
|
189
|
+
console.log(process.env.PORT); // Now available in process.env
|
|
113
190
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
191
|
+
// Custom files
|
|
192
|
+
load({
|
|
193
|
+
files: ['config/.menv', 'config/.menv.custom'],
|
|
194
|
+
schema: { PORT: 'number', HOST: 'string' }
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Override existing environment variables
|
|
198
|
+
load({
|
|
199
|
+
exportEnv: true,
|
|
200
|
+
override: true // Will replace existing process.env values
|
|
201
|
+
});
|
|
122
202
|
```
|
|
123
203
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- required: true | false
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### `parse(text, options)` → `Object`
|
|
128
207
|
|
|
129
|
-
|
|
130
|
-
- boolean: true/false (case-insensitive)
|
|
131
|
-
- number: integer or float
|
|
132
|
-
- json: JSON.parse on the value
|
|
133
|
-
- date: Date.parse on the value
|
|
208
|
+
Parse a string of .menv content without loading files. Useful for testing or processing environment strings from other sources.
|
|
134
209
|
|
|
135
|
-
|
|
136
|
-
When `strict` is true, the loader rejects:
|
|
137
|
-
- unknown keys not in `schema`
|
|
138
|
-
- duplicate keys across files
|
|
139
|
-
- invalid lines or parse errors
|
|
210
|
+
**Options:**
|
|
140
211
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
212
|
+
| Option | Type | Default | Description |
|
|
213
|
+
|--------|------|---------|-------------|
|
|
214
|
+
| `schema` | `Object` | `{}` | Schema definition for validation |
|
|
215
|
+
| `strict` | `boolean` | `false` | Enable strict validation |
|
|
216
|
+
| `cast` | `boolean\|Object` | `true` | Enable/disable type casting |
|
|
217
|
+
| `freeze` | `boolean` | `true` | Deep-freeze the result |
|
|
218
|
+
|
|
219
|
+
**Returns:**
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
144
222
|
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
223
|
+
parsed: Object, // Typed values
|
|
224
|
+
raw: Object, // Raw string values
|
|
225
|
+
origins: Object // Line numbers: { KEY: { line } }
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Example:**
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
const { parse } = require('molex-env');
|
|
233
|
+
|
|
234
|
+
const envContent = `
|
|
235
|
+
PORT=3000
|
|
236
|
+
DEBUG=true
|
|
237
|
+
METADATA={"env":"production"}
|
|
238
|
+
`;
|
|
239
|
+
|
|
240
|
+
const result = parse(envContent, {
|
|
241
|
+
schema: {
|
|
242
|
+
PORT: 'number',
|
|
243
|
+
DEBUG: 'boolean',
|
|
244
|
+
METADATA: 'json'
|
|
245
|
+
},
|
|
246
|
+
strict: true
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
console.log(result.parsed.PORT); // 3000 (number)
|
|
250
|
+
console.log(result.parsed.DEBUG); // true (boolean)
|
|
251
|
+
console.log(result.parsed.METADATA); // { env: 'production' } (object)
|
|
252
|
+
console.log(result.origins.PORT); // { line: 2 }
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
### `watch(options, onChange)`
|
|
258
|
+
|
|
259
|
+
Watch .menv files and reload automatically when they change. Perfect for development environments.
|
|
260
|
+
|
|
261
|
+
**Arguments:**
|
|
262
|
+
|
|
263
|
+
- `options` - Same options as `load()`
|
|
264
|
+
- `onChange(error, result)` - Callback fired on file changes
|
|
265
|
+
|
|
266
|
+
**Example:**
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
const { watch } = require('molex-env');
|
|
270
|
+
|
|
271
|
+
// Watch with callback
|
|
272
|
+
watch({ profile: 'dev', strict: true }, (err, result) => {
|
|
273
|
+
if (err) {
|
|
274
|
+
console.error('Config reload failed:', err.message);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.log('Config reloaded!');
|
|
279
|
+
console.log('New PORT:', result.parsed.PORT);
|
|
280
|
+
|
|
281
|
+
// Restart your server or update app state here
|
|
282
|
+
if (global.server) {
|
|
283
|
+
global.server.close();
|
|
284
|
+
global.server = startServer(result.parsed);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
console.log('Watching for .menv file changes...');
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Example with Express hot reload:**
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
const express = require('express');
|
|
295
|
+
const { watch } = require('molex-env');
|
|
296
|
+
|
|
297
|
+
let server;
|
|
298
|
+
|
|
299
|
+
function startServer(config) {
|
|
300
|
+
const app = express();
|
|
301
|
+
app.get('/', (req, res) => res.json({ port: config.PORT }));
|
|
302
|
+
return app.listen(config.PORT, () => {
|
|
303
|
+
console.log(`Server running on port ${config.PORT}`);
|
|
304
|
+
});
|
|
148
305
|
}
|
|
306
|
+
|
|
307
|
+
// Start with initial config
|
|
308
|
+
const initial = require('molex-env').load({ profile: 'dev' });
|
|
309
|
+
server = startServer(initial.parsed);
|
|
310
|
+
|
|
311
|
+
// Watch for changes
|
|
312
|
+
watch({ profile: 'dev' }, (err, result) => {
|
|
313
|
+
if (!err && result.parsed.PORT !== initial.parsed.PORT) {
|
|
314
|
+
console.log('Port changed, restarting...');
|
|
315
|
+
server.close(() => {
|
|
316
|
+
server = startServer(result.parsed);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Schema Definition
|
|
325
|
+
|
|
326
|
+
Schemas provide type validation, required field enforcement, and default values.
|
|
327
|
+
|
|
328
|
+
### Schema Formats
|
|
329
|
+
|
|
330
|
+
**Simple string format:**
|
|
331
|
+
```javascript
|
|
332
|
+
const schema = {
|
|
333
|
+
PORT: 'number',
|
|
334
|
+
DEBUG: 'boolean',
|
|
335
|
+
SERVICE_URL: 'string',
|
|
336
|
+
METADATA: 'json',
|
|
337
|
+
START_DATE: 'date'
|
|
338
|
+
};
|
|
149
339
|
```
|
|
150
340
|
|
|
151
|
-
|
|
341
|
+
**Object format with options:**
|
|
342
|
+
```javascript
|
|
343
|
+
const schema = {
|
|
344
|
+
PORT: {
|
|
345
|
+
type: 'number',
|
|
346
|
+
default: 3000
|
|
347
|
+
},
|
|
348
|
+
DEBUG: {
|
|
349
|
+
type: 'boolean',
|
|
350
|
+
default: false
|
|
351
|
+
},
|
|
352
|
+
SERVICE_URL: {
|
|
353
|
+
type: 'string',
|
|
354
|
+
required: true // Will throw error if missing
|
|
355
|
+
},
|
|
356
|
+
METADATA: {
|
|
357
|
+
type: 'json',
|
|
358
|
+
default: { region: 'us-east-1' }
|
|
359
|
+
},
|
|
360
|
+
START_DATE: {
|
|
361
|
+
type: 'date'
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
```
|
|
152
365
|
|
|
153
|
-
|
|
154
|
-
|
|
366
|
+
### Schema Options
|
|
367
|
+
|
|
368
|
+
| Option | Type | Description |
|
|
369
|
+
|--------|------|-------------|
|
|
370
|
+
| `type` | `string` | Value type: `'string'`, `'boolean'`, `'number'`, `'json'`, or `'date'` |
|
|
371
|
+
| `default` | `any` | Default value if key is missing (must match type) |
|
|
372
|
+
| `required` | `boolean` | If `true`, throws error when key is missing |
|
|
373
|
+
|
|
374
|
+
### Type Parsing Rules
|
|
375
|
+
|
|
376
|
+
| Type | Description | Examples |
|
|
377
|
+
|------|-------------|----------|
|
|
378
|
+
| `string` | Plain text (default) | `"hello"`, `hello`, `"123"` |
|
|
379
|
+
| `boolean` | Case-insensitive true/false | `true`, `TRUE`, `false`, `False` |
|
|
380
|
+
| `number` | Integer or float | `3000`, `3.14`, `-42`, `1e6` |
|
|
381
|
+
| `json` | Valid JSON string | `{"key":"value"}`, `[1,2,3]`, `null` |
|
|
382
|
+
| `date` | ISO 8601 date string | `2026-02-02`, `2026-02-02T10:30:00Z` |
|
|
383
|
+
|
|
384
|
+
**Example with all types:**
|
|
385
|
+
|
|
386
|
+
```javascript
|
|
155
387
|
load({
|
|
156
|
-
|
|
157
|
-
|
|
388
|
+
schema: {
|
|
389
|
+
// String (explicit)
|
|
390
|
+
API_KEY: { type: 'string', required: true },
|
|
391
|
+
|
|
392
|
+
// Boolean
|
|
393
|
+
DEBUG: { type: 'boolean', default: false },
|
|
394
|
+
ENABLE_LOGGING: 'boolean',
|
|
395
|
+
|
|
396
|
+
// Number
|
|
397
|
+
PORT: { type: 'number', default: 3000 },
|
|
398
|
+
TIMEOUT: 'number',
|
|
399
|
+
RETRY_COUNT: { type: 'number', default: 3 },
|
|
400
|
+
|
|
401
|
+
// JSON (objects and arrays)
|
|
402
|
+
METADATA: { type: 'json', default: {} },
|
|
403
|
+
ALLOWED_HOSTS: 'json', // Can be array or object
|
|
404
|
+
|
|
405
|
+
// Date
|
|
406
|
+
START_DATE: 'date',
|
|
407
|
+
EXPIRES_AT: { type: 'date', required: true }
|
|
408
|
+
},
|
|
409
|
+
strict: true
|
|
158
410
|
});
|
|
159
411
|
```
|
|
160
412
|
|
|
161
|
-
|
|
162
|
-
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## Type Casting
|
|
416
|
+
|
|
417
|
+
Control how values are automatically converted from strings.
|
|
418
|
+
|
|
419
|
+
### Enable/Disable All Casting
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
// Default: all types are cast
|
|
423
|
+
load({ cast: true });
|
|
424
|
+
|
|
425
|
+
// Disable all casting (everything stays as strings)
|
|
163
426
|
load({ cast: false });
|
|
427
|
+
console.log(typeof process.menv.PORT); // 'string' (was '3000')
|
|
164
428
|
```
|
|
165
429
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
430
|
+
### Selective Casting
|
|
431
|
+
|
|
432
|
+
```javascript
|
|
433
|
+
// Only cast specific types
|
|
434
|
+
load({
|
|
435
|
+
cast: {
|
|
436
|
+
boolean: true, // Cast booleans
|
|
437
|
+
number: true, // Cast numbers
|
|
438
|
+
json: false, // Keep JSON as strings
|
|
439
|
+
date: false // Keep dates as strings
|
|
440
|
+
}
|
|
441
|
+
});
|
|
169
442
|
```
|
|
170
443
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Strict Mode
|
|
447
|
+
|
|
448
|
+
Strict mode provides rigorous validation to catch configuration errors early.
|
|
449
|
+
|
|
450
|
+
### What Strict Mode Enforces
|
|
451
|
+
|
|
452
|
+
When `strict: true`:
|
|
453
|
+
- ❌ **Unknown keys** - Keys not in schema are rejected
|
|
454
|
+
- ❌ **Duplicate keys** - Same key in multiple files throws error
|
|
455
|
+
- ❌ **Invalid lines** - Malformed lines throw errors
|
|
456
|
+
- ❌ **Type mismatches** - Values that can't be parsed as specified type
|
|
457
|
+
|
|
458
|
+
**Example:**
|
|
459
|
+
|
|
460
|
+
```javascript
|
|
461
|
+
// .menv file
|
|
462
|
+
PORT=3000
|
|
463
|
+
DEBUG=true
|
|
464
|
+
UNKNOWN_KEY=value // ← Not in schema
|
|
465
|
+
|
|
466
|
+
load({
|
|
467
|
+
schema: {
|
|
468
|
+
PORT: 'number',
|
|
469
|
+
DEBUG: 'boolean'
|
|
470
|
+
},
|
|
471
|
+
strict: true // Will throw error about UNKNOWN_KEY
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Non-Strict Mode (Default)
|
|
476
|
+
|
|
477
|
+
Without strict mode:
|
|
478
|
+
- ✅ Unknown keys are allowed and parsed
|
|
479
|
+
- ✅ Duplicates override (later files win)
|
|
480
|
+
- ✅ Invalid lines are skipped
|
|
481
|
+
- ⚠️ Warnings can be logged via `onWarning` callback
|
|
482
|
+
|
|
483
|
+
**Example with warning handler:**
|
|
484
|
+
|
|
485
|
+
```javascript
|
|
486
|
+
load({
|
|
487
|
+
schema: { PORT: 'number' },
|
|
488
|
+
strict: false,
|
|
489
|
+
onWarning: (info) => {
|
|
490
|
+
console.warn(`Warning: ${info.key} redefined`);
|
|
491
|
+
console.warn(` Previous: ${info.previous.file}:${info.previous.line}`);
|
|
492
|
+
console.warn(` New: ${info.next.file}:${info.next.line}`);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Origin Tracking
|
|
500
|
+
|
|
501
|
+
Every configuration value includes its source file and line number, making debugging easy.
|
|
502
|
+
|
|
503
|
+
**Example:**
|
|
504
|
+
|
|
505
|
+
```javascript
|
|
506
|
+
const result = load({ profile: 'prod' });
|
|
507
|
+
|
|
508
|
+
console.log(result.origins);
|
|
509
|
+
// {
|
|
510
|
+
// PORT: { file: '.menv', line: 1 },
|
|
511
|
+
// DEBUG: { file: '.menv.local', line: 2 },
|
|
512
|
+
// SERVICE_URL: { file: '.menv.prod', line: 3 }
|
|
513
|
+
// }
|
|
514
|
+
|
|
515
|
+
// Debug where a value came from
|
|
516
|
+
const portOrigin = result.origins.PORT;
|
|
517
|
+
console.log(`PORT is defined in ${portOrigin.file} at line ${portOrigin.line}`);
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**Practical debugging use case:**
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
const { load } = require('molex-env');
|
|
524
|
+
|
|
525
|
+
const result = load({ profile: 'prod', strict: true });
|
|
526
|
+
|
|
527
|
+
// Verify configuration sources before deployment
|
|
528
|
+
Object.keys(result.parsed).forEach(key => {
|
|
529
|
+
const origin = result.origins[key];
|
|
530
|
+
console.log(`${key}=${result.parsed[key]} (from ${origin.file}:${origin.line})`);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Example output:
|
|
534
|
+
// PORT=8080 (from .menv.prod:1)
|
|
535
|
+
// DEBUG=false (from .menv.prod:2)
|
|
536
|
+
// DATABASE_URL=postgres://prod:5432/db (from .menv.prod.local:3)
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Advanced Examples
|
|
542
|
+
|
|
543
|
+
### Complete Production Setup
|
|
544
|
+
|
|
545
|
+
```javascript
|
|
546
|
+
const { load } = require('molex-env');
|
|
547
|
+
|
|
548
|
+
const config = load({
|
|
549
|
+
profile: process.env.NODE_ENV || 'development',
|
|
550
|
+
strict: true,
|
|
551
|
+
exportEnv: true,
|
|
552
|
+
schema: {
|
|
553
|
+
// Server config
|
|
554
|
+
NODE_ENV: { type: 'string', required: true },
|
|
555
|
+
PORT: { type: 'number', default: 3000 },
|
|
556
|
+
HOST: { type: 'string', default: '0.0.0.0' },
|
|
557
|
+
|
|
558
|
+
// Database
|
|
559
|
+
DATABASE_URL: { type: 'string', required: true },
|
|
560
|
+
DB_POOL_SIZE: { type: 'number', default: 10 },
|
|
561
|
+
|
|
562
|
+
// Redis
|
|
563
|
+
REDIS_URL: { type: 'string', required: true },
|
|
564
|
+
REDIS_TTL: { type: 'number', default: 3600 },
|
|
565
|
+
|
|
566
|
+
// Feature flags
|
|
567
|
+
ENABLE_CACHE: { type: 'boolean', default: true },
|
|
568
|
+
ENABLE_METRICS: { type: 'boolean', default: false },
|
|
569
|
+
|
|
570
|
+
// API config
|
|
571
|
+
API_KEYS: { type: 'json', required: true },
|
|
572
|
+
RATE_LIMITS: { type: 'json', default: { default: 100 } },
|
|
573
|
+
|
|
574
|
+
// Dates
|
|
575
|
+
MAINTENANCE_START: 'date',
|
|
576
|
+
MAINTENANCE_END: 'date'
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
console.log('Configuration loaded successfully');
|
|
581
|
+
console.log(`Running in ${config.parsed.NODE_ENV} mode on port ${config.parsed.PORT}`);
|
|
582
|
+
|
|
583
|
+
module.exports = config.parsed;
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Dynamic Profile from Command Line
|
|
587
|
+
|
|
588
|
+
```javascript
|
|
589
|
+
// Load profile from CLI argument
|
|
590
|
+
// Usage: node app.js --env=staging
|
|
591
|
+
|
|
592
|
+
const args = process.argv.slice(2);
|
|
593
|
+
const envArg = args.find(arg => arg.startsWith('--env='));
|
|
594
|
+
const profile = envArg ? envArg.split('=')[1] : 'development';
|
|
595
|
+
|
|
596
|
+
require('molex-env').load({
|
|
597
|
+
profile,
|
|
598
|
+
strict: true,
|
|
599
|
+
schema: {
|
|
600
|
+
PORT: 'number',
|
|
601
|
+
DATABASE_URL: { type: 'string', required: true }
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
console.log(`Started with profile: ${profile}`);
|
|
606
|
+
console.log(`PORT: ${process.menv.PORT}`);
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### Development with Hot Reload
|
|
610
|
+
|
|
611
|
+
```javascript
|
|
612
|
+
const { watch } = require('molex-env');
|
|
613
|
+
|
|
614
|
+
let currentConfig;
|
|
615
|
+
|
|
616
|
+
watch({
|
|
617
|
+
profile: 'dev',
|
|
618
|
+
schema: {
|
|
619
|
+
PORT: 'number',
|
|
620
|
+
DEBUG: 'boolean',
|
|
621
|
+
API_URL: 'string'
|
|
622
|
+
}
|
|
623
|
+
}, (err, result) => {
|
|
624
|
+
if (err) {
|
|
625
|
+
console.error('Config error:', err.message);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const changed = [];
|
|
630
|
+
if (!currentConfig) {
|
|
631
|
+
console.log('Initial config loaded');
|
|
632
|
+
} else {
|
|
633
|
+
// Detect what changed
|
|
634
|
+
Object.keys(result.parsed).forEach(key => {
|
|
635
|
+
if (currentConfig[key] !== result.parsed[key]) {
|
|
636
|
+
changed.push(`${key}: ${currentConfig[key]} → ${result.parsed[key]}`);
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
if (changed.length > 0) {
|
|
641
|
+
console.log('Config updated:', changed.join(', '));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
currentConfig = result.parsed;
|
|
646
|
+
});
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Validation and Error Handling
|
|
650
|
+
|
|
651
|
+
```javascript
|
|
652
|
+
const { load } = require('molex-env');
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
const config = load({
|
|
656
|
+
profile: 'prod',
|
|
657
|
+
strict: true,
|
|
658
|
+
schema: {
|
|
659
|
+
PORT: { type: 'number', required: true },
|
|
660
|
+
DATABASE_URL: { type: 'string', required: true },
|
|
661
|
+
REDIS_URL: { type: 'string', required: true }
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// Validate ranges
|
|
666
|
+
if (config.parsed.PORT < 1024 || config.parsed.PORT > 65535) {
|
|
667
|
+
throw new Error(`Invalid PORT: ${config.parsed.PORT} (must be 1024-65535)`);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Validate URLs
|
|
671
|
+
if (!config.parsed.DATABASE_URL.startsWith('postgres://')) {
|
|
672
|
+
throw new Error('DATABASE_URL must be a PostgreSQL connection string');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
console.log('Configuration validated successfully');
|
|
676
|
+
|
|
677
|
+
} catch (err) {
|
|
678
|
+
console.error('Configuration error:', err.message);
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
## Best Practices
|
|
686
|
+
|
|
687
|
+
### Git Configuration
|
|
688
|
+
|
|
689
|
+
Add to `.gitignore`:
|
|
690
|
+
```gitignore
|
|
691
|
+
# Keep base configs in git
|
|
692
|
+
# .menv
|
|
693
|
+
# .menv.dev
|
|
694
|
+
# .menv.prod
|
|
695
|
+
|
|
696
|
+
# Ignore local overrides (machine-specific, secrets)
|
|
697
|
+
.menv.local
|
|
698
|
+
.menv.*.local
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### Environment Strategy
|
|
702
|
+
|
|
703
|
+
```
|
|
704
|
+
Development: .menv + .menv.local
|
|
705
|
+
Staging: .menv + .menv.staging
|
|
706
|
+
Production: .menv + .menv.prod + .menv.prod.local (secrets)
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
### Security Tips
|
|
710
|
+
|
|
711
|
+
- ✅ **DO** use `.menv.local` for secrets and add to `.gitignore`
|
|
712
|
+
- ✅ **DO** use `strict: true` in production to catch misconfigurations
|
|
713
|
+
- ✅ **DO** validate sensitive values (URLs, ports, etc.) after loading
|
|
714
|
+
- ❌ **DON'T** commit production secrets to git
|
|
715
|
+
- ❌ **DON'T** use `exportEnv: true` if you need immutable config
|
|
716
|
+
|
|
717
|
+
### Performance
|
|
718
|
+
|
|
719
|
+
- Config loading is synchronous and fast (~1-2ms for typical files)
|
|
720
|
+
- Frozen configs (default) prevent accidental mutations
|
|
721
|
+
- Use `watch()` only in development (slight memory overhead)
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
725
|
+
## Example Project
|
|
726
|
+
|
|
727
|
+
A complete example application is included in `examples/basic`.
|
|
175
728
|
|
|
176
|
-
## Example project
|
|
177
|
-
An example app is included in examples/basic.
|
|
178
|
-
Run it with:
|
|
179
729
|
```bash
|
|
180
730
|
cd examples/basic
|
|
181
731
|
npm install
|
|
182
732
|
npm start
|
|
183
733
|
```
|
|
734
|
+
|
|
735
|
+
The example demonstrates:
|
|
736
|
+
- Profile switching (dev/prod)
|
|
737
|
+
- Schema validation
|
|
738
|
+
- Type casting
|
|
739
|
+
- Origin tracking
|
|
740
|
+
- Live reload with watch mode
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
## Troubleshooting
|
|
745
|
+
|
|
746
|
+
### "Unknown key" error in strict mode
|
|
747
|
+
|
|
748
|
+
**Problem:** Getting errors about unknown keys when loading config.
|
|
749
|
+
|
|
750
|
+
**Solution:** Add all keys to your schema or disable strict mode:
|
|
751
|
+
```javascript
|
|
752
|
+
load({ strict: false }); // Allow unknown keys
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Values are strings instead of typed
|
|
756
|
+
|
|
757
|
+
**Problem:** `PORT` is `"3000"` (string) instead of `3000` (number).
|
|
758
|
+
|
|
759
|
+
**Solution:** Enable casting or add schema:
|
|
760
|
+
```javascript
|
|
761
|
+
load({
|
|
762
|
+
cast: true, // Ensure casting is enabled
|
|
763
|
+
schema: { PORT: 'number' }
|
|
764
|
+
});
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
### Changes to .menv not reflected
|
|
768
|
+
|
|
769
|
+
**Problem:** Modified .menv file but app still uses old values.
|
|
770
|
+
|
|
771
|
+
**Solution:**
|
|
772
|
+
- If using `attach: true` (default), restart the app
|
|
773
|
+
- Or use `watch()` for automatic reloading in development
|
|
774
|
+
|
|
775
|
+
### Type casting fails
|
|
776
|
+
|
|
777
|
+
**Problem:** Getting parse errors for JSON or dates.
|
|
778
|
+
|
|
779
|
+
**Solution:** Verify the format in your .menv file:
|
|
780
|
+
```env
|
|
781
|
+
# Valid JSON (use double quotes)
|
|
782
|
+
METADATA={"key":"value"}
|
|
783
|
+
|
|
784
|
+
# Valid date (ISO 8601)
|
|
785
|
+
START_DATE=2026-02-02
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
---
|
|
789
|
+
|
|
790
|
+
## License
|
|
791
|
+
|
|
792
|
+
ISC License
|