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 CHANGED
@@ -1,10 +1,11 @@
1
1
  # molex-env-npm
2
2
 
3
- [![npm](https://img.shields.io/npm/v/molex-env)](https://www.npmjs.com/package/molex-env)
4
- [![downloads](https://img.shields.io/npm/dm/molex-env)](https://www.npmjs.com/package/molex-env)
5
- [![license](https://img.shields.io/npm/l/molex-env)](LICENSE)
3
+ [![npm version](https://img.shields.io/npm/v/molex-env)](https://www.npmjs.com/package/molex-env)
4
+ [![npm downloads](https://img.shields.io/npm/dm/molex-env.svg)](https://www.npmjs.com/package/molex-env)
5
+ [![GitHub](https://img.shields.io/badge/GitHub-molex--env--npm-blue.svg)](https://github.com/tonywied17/molex-env-npm)
6
+ [![License: MIT](https://img.shields.io/npm/l/molex-env)](LICENSE)
6
7
  [![Node.js](https://img.shields.io/badge/node-%3E%3D14-brightgreen.svg)](https://nodejs.org)
7
- [![deps](https://img.shields.io/badge/dependencies-0-success.svg)](package.json)
8
+ [![Dependencies](https://img.shields.io/badge/dependencies-0-success.svg)](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 multiple files throws error
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
- - **Type mismatches** - Values that can't be parsed as specified type
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
- - ✅ Duplicates override (later files win)
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
- 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}`);
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 misconfigurations
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.4",
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, seen: Set<string>}} state
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 (state.seen.has(key))
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
- state.seen.add(key);
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, seen: Set<string>}}
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
- seen: new Set()
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);