molex-env 0.2.4 → 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 +79 -16
- package/package.json +2 -2
- package/src/lib/apply.js +22 -5
- package/src/lib/core.js +4 -3
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# molex-env-npm
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/molex-env)
|
|
4
|
-
[](https://www.npmjs.com/package/molex-env)
|
|
5
|
-
[](https://www.npmjs.com/package/molex-env)
|
|
4
|
+
[](https://www.npmjs.com/package/molex-env)
|
|
5
|
+
[](https://github.com/tonywied17/molex-env-npm)
|
|
6
|
+
[](LICENSE)
|
|
6
7
|
[](https://nodejs.org)
|
|
7
|
-
[](package.json)
|
|
8
9
|
|
|
9
10
|
> **Native .menv environment loader with profile support, typed parsing, origin tracking, and live reload. Zero dependencies.**
|
|
10
11
|
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
- **Type-safe parsing** - Automatic conversion of booleans, numbers, JSON, and dates
|
|
16
17
|
- **Strict validation** - Schema enforcement with required fields and type checking
|
|
17
18
|
- **Origin tracking** - Know exactly which file and line each value came from
|
|
19
|
+
- **Debug mode** - See which files override values during cascading
|
|
18
20
|
- **Immutable config** - Deep-freeze protection prevents accidental modifications
|
|
19
21
|
- **Live reload** - Watch mode automatically reloads on file changes
|
|
20
22
|
- **Deterministic merging** - Predictable cascading from base to profile files
|
|
@@ -138,6 +140,18 @@ Files are loaded and merged in this order (later files override earlier ones):
|
|
|
138
140
|
Final result: PORT=9000, DEBUG=false
|
|
139
141
|
```
|
|
140
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
|
+
|
|
141
155
|
## API Reference
|
|
142
156
|
|
|
143
157
|
### `load(options)` → `Object`
|
|
@@ -152,12 +166,13 @@ Load, merge, parse, and validate .menv files. This is the primary method you'll
|
|
|
152
166
|
| `profile` | `string` | `undefined` | Profile name for `.menv.{profile}` files |
|
|
153
167
|
| `files` | `Array<string>` | Auto-detected | Custom file list (absolute or relative to cwd) |
|
|
154
168
|
| `schema` | `Object` | `{}` | Schema definition for validation and typing |
|
|
155
|
-
| `strict` | `boolean` | `false` | Reject unknown keys, duplicates, and invalid lines |
|
|
169
|
+
| `strict` | `boolean` | `false` | Reject unknown keys, within-file duplicates, and invalid lines |
|
|
156
170
|
| `cast` | `boolean\|Object` | `true` | Enable/disable type casting (see Type Casting) |
|
|
157
171
|
| `exportEnv` | `boolean` | `false` | Write parsed values to `process.env` |
|
|
158
172
|
| `override` | `boolean` | `false` | Override existing `process.env` values |
|
|
159
173
|
| `attach` | `boolean` | `true` | Attach parsed values to `process.menv` |
|
|
160
174
|
| `freeze` | `boolean` | `true` | Deep-freeze the parsed config object |
|
|
175
|
+
| `debug` | `boolean` | `false` | Log file precedence overrides to console |
|
|
161
176
|
| `onWarning` | `Function` | `undefined` | Callback for non-strict warnings |
|
|
162
177
|
|
|
163
178
|
**Returns:**
|
|
@@ -212,10 +227,12 @@ Parse a string of .menv content without loading files. Useful for testing or pro
|
|
|
212
227
|
| Option | Type | Default | Description |
|
|
213
228
|
|--------|------|---------|-------------|
|
|
214
229
|
| `schema` | `Object` | `{}` | Schema definition for validation |
|
|
215
|
-
| `strict` | `boolean` | `false` | Enable strict validation |
|
|
230
|
+
| `strict` | `boolean` | `false` | Enable strict validation (rejects unknown keys, within-file duplicates, invalid lines) |
|
|
216
231
|
| `cast` | `boolean\|Object` | `true` | Enable/disable type casting |
|
|
217
232
|
| `freeze` | `boolean` | `true` | Deep-freeze the result |
|
|
218
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
|
+
|
|
219
236
|
**Returns:**
|
|
220
237
|
|
|
221
238
|
```javascript
|
|
@@ -451,9 +468,10 @@ Strict mode provides rigorous validation to catch configuration errors early.
|
|
|
451
468
|
|
|
452
469
|
When `strict: true`:
|
|
453
470
|
- ❌ **Unknown keys** - Keys not in schema are rejected
|
|
454
|
-
- ❌ **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
|
|
455
473
|
- ❌ **Invalid lines** - Malformed lines throw errors
|
|
456
|
-
-
|
|
474
|
+
- ✅ **Type validation** - When schema is present, type mismatches throw errors (enabled by default with schema)
|
|
457
475
|
|
|
458
476
|
**Example:**
|
|
459
477
|
|
|
@@ -472,28 +490,56 @@ load({
|
|
|
472
490
|
});
|
|
473
491
|
```
|
|
474
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
|
+
|
|
475
514
|
### Non-Strict Mode (Default)
|
|
476
515
|
|
|
477
|
-
Without strict mode:
|
|
516
|
+
Without strict mode, the file precedence feature works as intended:
|
|
478
517
|
- ✅ Unknown keys are allowed and parsed
|
|
479
|
-
- ✅
|
|
518
|
+
- ✅ **Duplicate keys override** - Later files can override keys from earlier files
|
|
480
519
|
- ✅ Invalid lines are skipped
|
|
481
|
-
- ⚠️ Warnings can be logged via `onWarning` callback
|
|
520
|
+
- ⚠️ Warnings can be logged via `onWarning` callback for within-file duplicates
|
|
482
521
|
|
|
483
522
|
**Example with warning handler:**
|
|
484
523
|
|
|
485
524
|
```javascript
|
|
525
|
+
// .menv file with duplicate keys
|
|
526
|
+
// PORT=3000
|
|
527
|
+
// PORT=8080
|
|
528
|
+
|
|
486
529
|
load({
|
|
487
|
-
schema: { PORT: 'number' },
|
|
488
530
|
strict: false,
|
|
489
531
|
onWarning: (info) => {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
532
|
+
if (info.type === 'duplicate') {
|
|
533
|
+
console.warn(`Warning: Duplicate key '${info.key}' in ${info.file}:${info.line}`);
|
|
534
|
+
}
|
|
493
535
|
}
|
|
494
536
|
});
|
|
537
|
+
// Output: Warning: Duplicate key 'PORT' in .menv:2
|
|
538
|
+
// Result: PORT=8080 (last value wins)
|
|
495
539
|
```
|
|
496
540
|
|
|
541
|
+
**Tip:** Use `debug: true` to see cross-file overrides (file precedence), or `onWarning` to catch within-file duplicates.
|
|
542
|
+
|
|
497
543
|
---
|
|
498
544
|
|
|
499
545
|
## Origin Tracking
|
|
@@ -709,7 +755,8 @@ Production: .menv + .menv.prod + .menv.prod.local (secrets)
|
|
|
709
755
|
### Security Tips
|
|
710
756
|
|
|
711
757
|
- ✅ **DO** use `.menv.local` for secrets and add to `.gitignore`
|
|
712
|
-
- ✅ **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
|
|
713
760
|
- ✅ **DO** validate sensitive values (URLs, ports, etc.) after loading
|
|
714
761
|
- ❌ **DON'T** commit production secrets to git
|
|
715
762
|
- ❌ **DON'T** use `exportEnv: true` if you need immutable config
|
|
@@ -785,6 +832,22 @@ METADATA={"key":"value"}
|
|
|
785
832
|
START_DATE=2026-02-02
|
|
786
833
|
```
|
|
787
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
|
+
|
|
788
851
|
---
|
|
789
852
|
|
|
790
853
|
## License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "molex-env",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Native .menv loader with profiles, typing, and origin tracking.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"profile",
|
|
20
20
|
"native"
|
|
21
21
|
],
|
|
22
|
-
"author": "",
|
|
22
|
+
"author": "Anthony Wiedman/Molex",
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
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);
|