molex-env 0.2.5 → 0.2.6
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 +74 -12
- package/package.json +1 -1
- package/src/lib/apply.js +22 -5
- package/src/lib/core.js +4 -3
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- **Type-safe parsing** - Automatic conversion of booleans, numbers, JSON, and dates
|
|
17
17
|
- **Strict validation** - Schema enforcement with required fields and type checking
|
|
18
18
|
- **Origin tracking** - Know exactly which file and line each value came from
|
|
19
|
+
- **Debug mode** - See which files override values during cascading
|
|
19
20
|
- **Immutable config** - Deep-freeze protection prevents accidental modifications
|
|
20
21
|
- **Live reload** - Watch mode automatically reloads on file changes
|
|
21
22
|
- **Deterministic merging** - Predictable cascading from base to profile files
|
|
@@ -139,6 +140,18 @@ Files are loaded and merged in this order (later files override earlier ones):
|
|
|
139
140
|
Final result: PORT=9000, DEBUG=false
|
|
140
141
|
```
|
|
141
142
|
|
|
143
|
+
**Debug mode** - Use `debug: true` to see which files override values:
|
|
144
|
+
```javascript
|
|
145
|
+
load({ profile: 'prod', debug: true });
|
|
146
|
+
// Console output:
|
|
147
|
+
// [molex-env] Override: DEBUG
|
|
148
|
+
// Previous: .menv:2 = true
|
|
149
|
+
// New: .menv.local:1 = false
|
|
150
|
+
// [molex-env] Override: PORT
|
|
151
|
+
// Previous: .menv.local:3 = 3000
|
|
152
|
+
// New: .menv.prod:1 = 8080
|
|
153
|
+
```
|
|
154
|
+
|
|
142
155
|
## API Reference
|
|
143
156
|
|
|
144
157
|
### `load(options)` → `Object`
|
|
@@ -153,12 +166,13 @@ Load, merge, parse, and validate .menv files. This is the primary method you'll
|
|
|
153
166
|
| `profile` | `string` | `undefined` | Profile name for `.menv.{profile}` files |
|
|
154
167
|
| `files` | `Array<string>` | Auto-detected | Custom file list (absolute or relative to cwd) |
|
|
155
168
|
| `schema` | `Object` | `{}` | Schema definition for validation and typing |
|
|
156
|
-
| `strict` | `boolean` | `false` | Reject unknown keys, duplicates, and invalid lines |
|
|
169
|
+
| `strict` | `boolean` | `false` | Reject unknown keys, within-file duplicates, and invalid lines |
|
|
157
170
|
| `cast` | `boolean\|Object` | `true` | Enable/disable type casting (see Type Casting) |
|
|
158
171
|
| `exportEnv` | `boolean` | `false` | Write parsed values to `process.env` |
|
|
159
172
|
| `override` | `boolean` | `false` | Override existing `process.env` values |
|
|
160
173
|
| `attach` | `boolean` | `true` | Attach parsed values to `process.menv` |
|
|
161
174
|
| `freeze` | `boolean` | `true` | Deep-freeze the parsed config object |
|
|
175
|
+
| `debug` | `boolean` | `false` | Log file precedence overrides to console |
|
|
162
176
|
| `onWarning` | `Function` | `undefined` | Callback for non-strict warnings |
|
|
163
177
|
|
|
164
178
|
**Returns:**
|
|
@@ -213,10 +227,12 @@ Parse a string of .menv content without loading files. Useful for testing or pro
|
|
|
213
227
|
| Option | Type | Default | Description |
|
|
214
228
|
|--------|------|---------|-------------|
|
|
215
229
|
| `schema` | `Object` | `{}` | Schema definition for validation |
|
|
216
|
-
| `strict` | `boolean` | `false` | Enable strict validation |
|
|
230
|
+
| `strict` | `boolean` | `false` | Enable strict validation (rejects unknown keys, within-file duplicates, invalid lines) |
|
|
217
231
|
| `cast` | `boolean\|Object` | `true` | Enable/disable type casting |
|
|
218
232
|
| `freeze` | `boolean` | `true` | Deep-freeze the result |
|
|
219
233
|
|
|
234
|
+
> **Note:** The `parse()` function processes a single string, so the `debug` option for file precedence and cross-file features don't apply here.
|
|
235
|
+
|
|
220
236
|
**Returns:**
|
|
221
237
|
|
|
222
238
|
```javascript
|
|
@@ -452,9 +468,10 @@ Strict mode provides rigorous validation to catch configuration errors early.
|
|
|
452
468
|
|
|
453
469
|
When `strict: true`:
|
|
454
470
|
- ❌ **Unknown keys** - Keys not in schema are rejected
|
|
455
|
-
- ❌ **Duplicate keys** - Same key in
|
|
471
|
+
- ❌ **Duplicate keys** - Same key appearing twice **in the same file** throws error
|
|
472
|
+
- **Note:** File precedence still works - different files can define the same key
|
|
456
473
|
- ❌ **Invalid lines** - Malformed lines throw errors
|
|
457
|
-
-
|
|
474
|
+
- ✅ **Type validation** - When schema is present, type mismatches throw errors (enabled by default with schema)
|
|
458
475
|
|
|
459
476
|
**Example:**
|
|
460
477
|
|
|
@@ -473,28 +490,56 @@ load({
|
|
|
473
490
|
});
|
|
474
491
|
```
|
|
475
492
|
|
|
493
|
+
**Valid with strict mode (different files):**
|
|
494
|
+
```javascript
|
|
495
|
+
// .menv
|
|
496
|
+
PORT=3000
|
|
497
|
+
|
|
498
|
+
// .menv.prod
|
|
499
|
+
PORT=8080 // ✅ OK - overrides from different file
|
|
500
|
+
|
|
501
|
+
load({ profile: 'prod', strict: true });
|
|
502
|
+
// Result: PORT=8080
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**Invalid with strict mode (same file):**
|
|
506
|
+
```javascript
|
|
507
|
+
// .menv
|
|
508
|
+
PORT=3000
|
|
509
|
+
PORT=8080 // ❌ ERROR - duplicate in same file
|
|
510
|
+
|
|
511
|
+
load({ strict: true }); // Throws error
|
|
512
|
+
```
|
|
513
|
+
|
|
476
514
|
### Non-Strict Mode (Default)
|
|
477
515
|
|
|
478
|
-
Without strict mode:
|
|
516
|
+
Without strict mode, the file precedence feature works as intended:
|
|
479
517
|
- ✅ Unknown keys are allowed and parsed
|
|
480
|
-
- ✅
|
|
518
|
+
- ✅ **Duplicate keys override** - Later files can override keys from earlier files
|
|
481
519
|
- ✅ Invalid lines are skipped
|
|
482
|
-
- ⚠️ Warnings can be logged via `onWarning` callback
|
|
520
|
+
- ⚠️ Warnings can be logged via `onWarning` callback for within-file duplicates
|
|
483
521
|
|
|
484
522
|
**Example with warning handler:**
|
|
485
523
|
|
|
486
524
|
```javascript
|
|
525
|
+
// .menv file with duplicate keys
|
|
526
|
+
// PORT=3000
|
|
527
|
+
// PORT=8080
|
|
528
|
+
|
|
487
529
|
load({
|
|
488
|
-
schema: { PORT: 'number' },
|
|
489
530
|
strict: false,
|
|
490
531
|
onWarning: (info) => {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
532
|
+
if (info.type === 'duplicate') {
|
|
533
|
+
console.warn(`Warning: Duplicate key '${info.key}' in ${info.file}:${info.line}`);
|
|
534
|
+
}
|
|
494
535
|
}
|
|
495
536
|
});
|
|
537
|
+
// Output: Warning: Duplicate key 'PORT' in .menv:2
|
|
538
|
+
// Result: PORT=8080 (last value wins)
|
|
496
539
|
```
|
|
497
540
|
|
|
541
|
+
**Tip:** Use `debug: true` to see cross-file overrides (file precedence), or `onWarning` to catch within-file duplicates.
|
|
542
|
+
|
|
498
543
|
---
|
|
499
544
|
|
|
500
545
|
## Origin Tracking
|
|
@@ -710,7 +755,8 @@ Production: .menv + .menv.prod + .menv.prod.local (secrets)
|
|
|
710
755
|
### Security Tips
|
|
711
756
|
|
|
712
757
|
- ✅ **DO** use `.menv.local` for secrets and add to `.gitignore`
|
|
713
|
-
- ✅ **DO** use `strict: true` in production to catch
|
|
758
|
+
- ✅ **DO** use `strict: true` in production to catch unknown keys and configuration errors
|
|
759
|
+
- ✅ **DO** use `debug: true` during development to understand file precedence
|
|
714
760
|
- ✅ **DO** validate sensitive values (URLs, ports, etc.) after loading
|
|
715
761
|
- ❌ **DON'T** commit production secrets to git
|
|
716
762
|
- ❌ **DON'T** use `exportEnv: true` if you need immutable config
|
|
@@ -786,6 +832,22 @@ METADATA={"key":"value"}
|
|
|
786
832
|
START_DATE=2026-02-02
|
|
787
833
|
```
|
|
788
834
|
|
|
835
|
+
### Understanding which file sets a value
|
|
836
|
+
|
|
837
|
+
**Problem:** Not sure which file is providing a specific config value.
|
|
838
|
+
|
|
839
|
+
**Solution:** Use `debug: true` to see file precedence in action:
|
|
840
|
+
```javascript
|
|
841
|
+
load({ profile: 'prod', debug: true });
|
|
842
|
+
// Shows console output for each override
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
Or check the `origins` object:
|
|
846
|
+
```javascript
|
|
847
|
+
const result = load({ profile: 'prod' });
|
|
848
|
+
console.log(result.origins.PORT); // { file: '.menv.prod', line: 1, raw: '8080' }
|
|
849
|
+
```
|
|
850
|
+
|
|
789
851
|
---
|
|
790
852
|
|
|
791
853
|
## License
|
package/package.json
CHANGED
package/src/lib/apply.js
CHANGED
|
@@ -5,15 +5,15 @@ const { coerceType, autoCast } = require('./cast');
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Apply a parsed entry to the state with schema/type checks.
|
|
8
|
-
* @param {{values: object, origins: object,
|
|
8
|
+
* @param {{values: object, origins: object, seenPerFile: Map<string, Set<string>>}} state
|
|
9
9
|
* @param {{key: string, raw: string, line: number}} entry
|
|
10
|
-
* @param {{schema: object|null, strict: boolean, cast: object, onWarning?: Function}} options
|
|
10
|
+
* @param {{schema: object|null, strict: boolean, cast: object, onWarning?: Function, debug?: boolean}} options
|
|
11
11
|
* @param {string} filePath
|
|
12
12
|
* @returns {void}
|
|
13
13
|
*/
|
|
14
14
|
function applyEntry(state, entry, options, filePath)
|
|
15
15
|
{
|
|
16
|
-
const { schema, strict, cast, onWarning } = options;
|
|
16
|
+
const { schema, strict, cast, onWarning, debug } = options;
|
|
17
17
|
const { key, raw, line } = entry;
|
|
18
18
|
|
|
19
19
|
if (schema && strict && !schema[key])
|
|
@@ -21,7 +21,15 @@ function applyEntry(state, entry, options, filePath)
|
|
|
21
21
|
throw unknownKeyError(key, filePath, line);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if
|
|
24
|
+
// Initialize per-file tracking if needed
|
|
25
|
+
if (!state.seenPerFile.has(filePath))
|
|
26
|
+
{
|
|
27
|
+
state.seenPerFile.set(filePath, new Set());
|
|
28
|
+
}
|
|
29
|
+
const fileKeys = state.seenPerFile.get(filePath);
|
|
30
|
+
|
|
31
|
+
// Check for duplicates ONLY within the same file
|
|
32
|
+
if (fileKeys.has(key))
|
|
25
33
|
{
|
|
26
34
|
if (strict)
|
|
27
35
|
{
|
|
@@ -38,6 +46,15 @@ function applyEntry(state, entry, options, filePath)
|
|
|
38
46
|
}
|
|
39
47
|
}
|
|
40
48
|
|
|
49
|
+
// Debug logging for file precedence
|
|
50
|
+
if (debug && state.values[key] !== undefined)
|
|
51
|
+
{
|
|
52
|
+
const prevOrigin = state.origins[key];
|
|
53
|
+
console.log(`[molex-env] Override: ${key}`);
|
|
54
|
+
console.log(` Previous: ${prevOrigin.file}:${prevOrigin.line} = ${prevOrigin.raw}`);
|
|
55
|
+
console.log(` New: ${filePath}:${line} = ${raw}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
const def = schema ? schema[key] : null;
|
|
42
59
|
let value;
|
|
43
60
|
if (def && def.type)
|
|
@@ -50,7 +67,7 @@ function applyEntry(state, entry, options, filePath)
|
|
|
50
67
|
|
|
51
68
|
state.values[key] = value;
|
|
52
69
|
state.origins[key] = { file: filePath, line, raw };
|
|
53
|
-
|
|
70
|
+
fileKeys.add(key);
|
|
54
71
|
}
|
|
55
72
|
|
|
56
73
|
module.exports = {
|
package/src/lib/core.js
CHANGED
|
@@ -11,14 +11,14 @@ const { deepFreeze } = require('./utils');
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Build a new parsing state container.
|
|
14
|
-
* @returns {{values: object, origins: object,
|
|
14
|
+
* @returns {{values: object, origins: object, seenPerFile: Map<string, Set<string>>}}
|
|
15
15
|
*/
|
|
16
16
|
function buildState()
|
|
17
17
|
{
|
|
18
18
|
return {
|
|
19
19
|
values: {},
|
|
20
20
|
origins: {},
|
|
21
|
-
|
|
21
|
+
seenPerFile: new Map()
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -79,7 +79,8 @@ function load(options = {})
|
|
|
79
79
|
schema: normalizedSchema,
|
|
80
80
|
strict,
|
|
81
81
|
cast,
|
|
82
|
-
onWarning: options.onWarning
|
|
82
|
+
onWarning: options.onWarning,
|
|
83
|
+
debug: options.debug
|
|
83
84
|
}, filePath);
|
|
84
85
|
}
|
|
85
86
|
readFiles.push(filePath);
|