molex-env 0.3.1 → 0.3.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 CHANGED
@@ -1,4 +1,10 @@
1
- # molex-env-npm
1
+ <p align="center">
2
+ <img src="docs/images/logo.svg" alt="molex-env logo" width="222" height="222">
3
+ </p>
4
+
5
+ <h1 align="center">molex-env-npm</h1>
6
+
7
+ <p align="center">
2
8
 
3
9
  [![npm version](https://img.shields.io/npm/v/molex-env)](https://www.npmjs.com/package/molex-env)
4
10
  [![npm downloads](https://img.shields.io/npm/dm/molex-env.svg)](https://www.npmjs.com/package/molex-env)
@@ -7,6 +13,8 @@
7
13
  [![Node.js](https://img.shields.io/badge/node-%3E%3D14-brightgreen.svg)](https://nodejs.org)
8
14
  [![Dependencies](https://img.shields.io/badge/dependencies-0-success.svg)](package.json)
9
15
 
16
+ </p>
17
+
10
18
  > **Native .menv environment loader with profile support, typed parsing, origin tracking, and live reload. Zero dependencies.**
11
19
 
12
20
  ## Features
@@ -50,7 +58,7 @@ const result = load({
50
58
 
51
59
  console.log(result.parsed.PORT); // 3000 (number)
52
60
  console.log(result.parsed.DEBUG); // false (boolean)
53
- console.log(result.origins.SERVICE_URL); // { file: '.menv', line: 3 }
61
+ console.log(result.origins.SERVICE_URL); // { file: '.menv', line: 3, raw: 'https://api.example.com' }
54
62
  console.log(process.menv.METADATA.region); // 'us-east-1' (parsed JSON)
55
63
  ```
56
64
 
@@ -180,9 +188,9 @@ Load, merge, parse, and validate .menv files. This is the primary method you'll
180
188
  ```javascript
181
189
  {
182
190
  parsed: Object, // Typed configuration values
183
- raw: Object, // Raw string values before parsing
184
- origins: Object, // Source tracking: { KEY: { file, line } }
185
- files: Array // List of resolved file paths
191
+ raw: Object, // Raw string values before type casting
192
+ origins: Object, // Source tracking: { KEY: { file, line, raw } }
193
+ files: Array // List of resolved file paths that were read
186
194
  }
187
195
  ```
188
196
 
@@ -230,6 +238,8 @@ Parse a string of .menv content without loading files. Useful for testing or pro
230
238
  | `strict` | `boolean` | `false` | Enable strict validation (rejects unknown keys, within-file duplicates, invalid lines) |
231
239
  | `cast` | `boolean\|Object` | `true` | Enable/disable type casting |
232
240
  | `freeze` | `boolean` | `true` | Deep-freeze the result |
241
+ | `filePath` | `string` | `'<inline>'` | Virtual file path used in origin tracking and error messages |
242
+ | `onWarning` | `Function` | `undefined` | Callback for non-strict warnings (e.g., within-string duplicates) |
233
243
 
234
244
  > **Note:** The `parse()` function processes a single string, so the `debug` option for file precedence and cross-file features don't apply here.
235
245
 
@@ -238,8 +248,9 @@ Parse a string of .menv content without loading files. Useful for testing or pro
238
248
  ```javascript
239
249
  {
240
250
  parsed: Object, // Typed values
241
- raw: Object, // Raw string values
242
- origins: Object // Line numbers: { KEY: { line } }
251
+ raw: Object, // Raw string values before type casting
252
+ origins: Object, // Source tracking: { KEY: { file, line, raw } }
253
+ files: Array // Contains [filePath] if filePath option was set, otherwise []
243
254
  }
244
255
  ```
245
256
 
@@ -266,7 +277,7 @@ const result = parse(envContent, {
266
277
  console.log(result.parsed.PORT); // 3000 (number)
267
278
  console.log(result.parsed.DEBUG); // true (boolean)
268
279
  console.log(result.parsed.METADATA); // { env: 'production' } (object)
269
- console.log(result.origins.PORT); // { line: 2 }
280
+ console.log(result.origins.PORT); // { file: '<inline>', line: 2, raw: '3000' }
270
281
  ```
271
282
 
272
283
  ---
@@ -599,9 +610,9 @@ const result = load({ profile: 'prod' });
599
610
 
600
611
  console.log(result.origins);
601
612
  // {
602
- // PORT: { file: '.menv', line: 1 },
603
- // DEBUG: { file: '.menv.local', line: 2 },
604
- // SERVICE_URL: { file: '.menv.prod', line: 3 }
613
+ // PORT: { file: '.menv', line: 1, raw: '3000' },
614
+ // DEBUG: { file: '.menv.local', line: 2, raw: 'false' },
615
+ // SERVICE_URL: { file: '.menv.prod', line: 3, raw: 'https://api.production.com' }
605
616
  // }
606
617
 
607
618
  // Debug where a value came from
@@ -791,10 +802,10 @@ Production: .menv + .menv.prod + .menv.prod.local
791
802
 
792
803
  ## Example Project
793
804
 
794
- A complete example application is included in `examples/basic`.
805
+ A complete example application is included in `examples/full`.
795
806
 
796
807
  ```bash
797
- cd examples/basic
808
+ cd examples/full
798
809
  npm install
799
810
  npm start
800
811
  ```
@@ -886,4 +897,4 @@ console.log(result.origins.PORT); // { file: '.menv.prod', line: 1, raw: '8080'
886
897
 
887
898
  ## License
888
899
 
889
- ISC License
900
+ MIT License
package/index.d.ts ADDED
@@ -0,0 +1,134 @@
1
+ // Type definitions for molex-env
2
+ // Project: https://github.com/tonywied17/molex-env-npm
3
+
4
+ /* ------------------------------------------------------------------ */
5
+ /* Option types */
6
+ /* ------------------------------------------------------------------ */
7
+
8
+ export interface CastOptions
9
+ {
10
+ boolean?: boolean;
11
+ number?: boolean;
12
+ json?: boolean;
13
+ date?: boolean;
14
+ }
15
+
16
+ export interface SchemaDefinition
17
+ {
18
+ type: 'string' | 'boolean' | 'number' | 'json' | 'date';
19
+ required?: boolean;
20
+ default?: any;
21
+ }
22
+
23
+ export interface Warning
24
+ {
25
+ type: string;
26
+ key: string;
27
+ file: string;
28
+ line: number;
29
+ }
30
+
31
+ export interface LoadOptions
32
+ {
33
+ /** Base directory to resolve files from. */
34
+ cwd?: string;
35
+ /** Profile name for `.menv.{profile}` files. */
36
+ profile?: string;
37
+ /** Custom file list (absolute or relative to cwd). */
38
+ files?: string[];
39
+ /** Schema definition for validation and typing. */
40
+ schema?: Record<string, SchemaDefinition | string>;
41
+ /** Reject unknown keys, within-file duplicates, and invalid lines. */
42
+ strict?: boolean;
43
+ /** Enable/disable type casting. */
44
+ cast?: boolean | CastOptions;
45
+ /** Write parsed values to `process.env`. */
46
+ exportEnv?: boolean;
47
+ /** Override existing `process.env` values. */
48
+ override?: boolean;
49
+ /** Attach parsed values to `process.menv`. */
50
+ attach?: boolean;
51
+ /** Deep-freeze the parsed config object. */
52
+ freeze?: boolean;
53
+ /** Log file precedence overrides to console. */
54
+ debug?: boolean;
55
+ /** Callback for non-strict warnings. */
56
+ onWarning?: (warning: Warning) => void;
57
+ }
58
+
59
+ export interface ParseOptions
60
+ {
61
+ /** Schema definition for validation. */
62
+ schema?: Record<string, SchemaDefinition | string>;
63
+ /** Enable strict validation. */
64
+ strict?: boolean;
65
+ /** Enable/disable type casting. */
66
+ cast?: boolean | CastOptions;
67
+ /** Deep-freeze the result. */
68
+ freeze?: boolean;
69
+ /** Virtual file path for origin tracking. */
70
+ filePath?: string;
71
+ /** Callback for non-strict warnings. */
72
+ onWarning?: (warning: Warning) => void;
73
+ }
74
+
75
+ /* ------------------------------------------------------------------ */
76
+ /* Result types */
77
+ /* ------------------------------------------------------------------ */
78
+
79
+ export interface Origin
80
+ {
81
+ file: string;
82
+ line: number;
83
+ raw: string | undefined;
84
+ }
85
+
86
+ export interface LoadResult
87
+ {
88
+ /** Typed configuration values. */
89
+ parsed: Record<string, any>;
90
+ /** Raw string values before type casting. */
91
+ raw: Record<string, string>;
92
+ /** Source tracking: file, line, and raw value per key. */
93
+ origins: Record<string, Origin>;
94
+ /** List of resolved file paths that were read. */
95
+ files: string[];
96
+ }
97
+
98
+ export interface WatchHandle
99
+ {
100
+ /** Stop watching all files. */
101
+ close(): void;
102
+ }
103
+
104
+ export type WatchCallback = (error: Error | null, result?: LoadResult) => void;
105
+
106
+ /* ------------------------------------------------------------------ */
107
+ /* Public API */
108
+ /* ------------------------------------------------------------------ */
109
+
110
+ /**
111
+ * Load .menv files, merge, parse, and validate.
112
+ */
113
+ export function load(options?: LoadOptions): LoadResult;
114
+
115
+ /**
116
+ * Parse a string of .menv content without loading files.
117
+ */
118
+ export function parse(text: string, options?: ParseOptions): LoadResult;
119
+
120
+ /**
121
+ * Watch .menv files and reload automatically on change.
122
+ */
123
+ export function watch(options: LoadOptions, onChange: WatchCallback): WatchHandle;
124
+
125
+ /**
126
+ * Default export — `load` function with named exports attached.
127
+ */
128
+ declare const molexEnv: typeof load & {
129
+ load: typeof load;
130
+ parse: typeof parse;
131
+ watch: typeof watch;
132
+ };
133
+
134
+ export = molexEnv;
package/package.json CHANGED
@@ -1,35 +1,46 @@
1
- {
2
- "name": "molex-env",
3
- "version": "0.3.1",
4
- "description": "Native .menv loader with profiles, typing, and origin tracking.",
5
- "main": "src/index.js",
6
- "files": [
7
- "src",
8
- "LICENSE",
9
- "README.md"
10
- ],
11
- "scripts": {
12
- "test": "node --test"
13
- },
14
- "keywords": [
15
- "env",
16
- "dotenv",
17
- "menv",
18
- "config",
19
- "profile",
20
- "native"
21
- ],
22
- "author": "Anthony Wiedman/Molex",
23
- "license": "MIT",
24
- "repository": {
25
- "type": "git",
26
- "url": "git+https://github.com/tonywied17/molex-env-npm.git"
27
- },
28
- "bugs": {
29
- "url": "https://github.com/tonywied17/molex-env-npm/issues"
30
- },
31
- "homepage": "https://github.com/tonywied17/molex-env-npm#readme",
32
- "publishConfig": {
33
- "access": "public"
34
- }
35
- }
1
+ {
2
+ "name": "molex-env",
3
+ "version": "0.3.3",
4
+ "description": "Native .menv loader with profiles, typing, and origin tracking.",
5
+ "main": "src/index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "src",
9
+ "index.d.ts",
10
+ "LICENSE",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=14.0.0"
15
+ },
16
+ "scripts": {
17
+ "test": "node --test",
18
+ "lint": "node --check src/**/*.js",
19
+ "publish:npm": "npm publish",
20
+ "publish:github": "node -e \"const fs=require('fs');const pkg=JSON.parse(fs.readFileSync('package.json'));pkg.name='@tonywied17/molex-env';fs.writeFileSync('package.json',JSON.stringify(pkg,null,2))\" && npm publish --registry=https://npm.pkg.github.com && git checkout package.json"
21
+ },
22
+ "keywords": [
23
+ "env",
24
+ "dotenv",
25
+ "menv",
26
+ "config",
27
+ "profile",
28
+ "native",
29
+ "schema",
30
+ "typed",
31
+ "zero-dependency"
32
+ ],
33
+ "author": "Anthony Wiedman/Molex",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/tonywied17/molex-env-npm.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/tonywied17/molex-env-npm/issues"
41
+ },
42
+ "homepage": "https://github.com/tonywied17/molex-env-npm#readme",
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }
package/src/index.js CHANGED
@@ -1,26 +1,27 @@
1
- 'use strict';
2
-
3
- const { load, parse } = require('./lib/core');
4
- const { watch } = require('./lib/watch');
5
-
6
- function loadEntry(options)
7
- {
8
- return load(options);
9
- }
10
-
11
- /**
12
- * Watch resolved .menv files and reload on change.
13
- * @param {object} options
14
- * @param {(err: Error|null, result?: {parsed: object, origins: object, files: string[]}) => void} onChange
15
- * @returns {{close: () => void}}
16
- */
17
- function watchEntry(options, onChange)
18
- {
19
- return watch(options, onChange, loadEntry);
20
- }
21
-
22
- module.exports = Object.assign(loadEntry, {
23
- load: loadEntry,
24
- parse,
25
- watch: watchEntry
26
- });
1
+ 'use strict';
2
+
3
+ const { load, parse } = require('./lib/core');
4
+ const { watch } = require('./lib/watch');
5
+
6
+ /**
7
+ * Watch resolved .menv files and reload on change.
8
+ * @param {object} options Same options as load().
9
+ * @param {Function} onChange Callback: (err, result) => void.
10
+ * @returns {{close: () => void}}
11
+ */
12
+ function watchFiles(options, onChange) {
13
+ return watch(options, onChange, load);
14
+ }
15
+
16
+ /*
17
+ * Default export is `load` itself so callers can do:
18
+ * require('molex-env')() — quick load
19
+ * require('molex-env').load() — explicit
20
+ * require('molex-env').parse() — string parsing
21
+ * require('molex-env').watch() — file watching
22
+ */
23
+ module.exports = Object.assign(load, {
24
+ load,
25
+ parse,
26
+ watch: watchFiles,
27
+ });
package/src/lib/apply.js CHANGED
@@ -1,75 +1,61 @@
1
- 'use strict';
2
-
3
- const { unknownKeyError, duplicateKeyError } = require('./errors');
4
- const { coerceType, autoCast } = require('./cast');
5
-
6
- /**
7
- * Apply a parsed entry to the state with schema/type checks.
8
- * @param {{values: object, origins: object, seenPerFile: Map<string, Set<string>>}} state
9
- * @param {{key: string, raw: string, line: number}} entry
10
- * @param {{schema: object|null, strict: boolean, cast: object, onWarning?: Function, debug?: boolean}} options
11
- * @param {string} filePath
12
- * @returns {void}
13
- */
14
- function applyEntry(state, entry, options, filePath)
15
- {
16
- const { schema, strict, cast, onWarning, debug } = options;
17
- const { key, raw, line } = entry;
18
-
19
- if (schema && strict && !schema[key])
20
- {
21
- throw unknownKeyError(key, filePath, line);
22
- }
23
-
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))
33
- {
34
- if (strict)
35
- {
36
- throw duplicateKeyError(key, filePath, line);
37
- }
38
- if (typeof onWarning === 'function')
39
- {
40
- onWarning({
41
- type: 'duplicate',
42
- key,
43
- file: filePath,
44
- line
45
- });
46
- }
47
- }
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
-
58
- const def = schema ? schema[key] : null;
59
- let value;
60
- if (def && def.type)
61
- {
62
- value = coerceType(raw, def.type, filePath, line);
63
- } else
64
- {
65
- value = autoCast(raw, cast);
66
- }
67
-
68
- state.values[key] = value;
69
- state.origins[key] = { file: filePath, line, raw };
70
- fileKeys.add(key);
71
- }
72
-
73
- module.exports = {
74
- applyEntry
75
- };
1
+ 'use strict';
2
+
3
+ const { unknownKeyError, duplicateKeyError } = require('./errors');
4
+ const { coerceType, autoCast } = require('./cast');
5
+
6
+ /**
7
+ * Apply a parsed entry to the state with schema/type checks.
8
+ * @param {{values: object, raw: object, origins: object, seenPerFile: Map<string, Set<string>>}} state
9
+ * @param {{key: string, raw: string, line: number}} entry
10
+ * @param {{schema: object|null, strict: boolean, cast: object, onWarning?: Function, debug?: boolean}} options
11
+ * @param {string} filePath
12
+ */
13
+ function applyEntry(state, entry, options, filePath)
14
+ {
15
+ const { schema, strict, cast, onWarning, debug } = options;
16
+ const { key, raw, line } = entry;
17
+
18
+ // Reject keys not defined in schema when strict
19
+ if (schema && strict && !schema[key])
20
+ {
21
+ throw unknownKeyError(key, filePath, line);
22
+ }
23
+
24
+ // Per-file duplicate tracking
25
+ if (!state.seenPerFile.has(filePath))
26
+ {
27
+ state.seenPerFile.set(filePath, new Set());
28
+ }
29
+ const fileKeys = state.seenPerFile.get(filePath);
30
+
31
+ if (fileKeys.has(key))
32
+ {
33
+ if (strict) throw duplicateKeyError(key, filePath, line);
34
+ if (typeof onWarning === 'function')
35
+ {
36
+ onWarning({ type: 'duplicate', key, file: filePath, line });
37
+ }
38
+ }
39
+
40
+ // Debug logging for cross-file overrides
41
+ if (debug && state.values[key] !== undefined)
42
+ {
43
+ const prev = state.origins[key];
44
+ console.log(`[molex-env] Override: ${key}`);
45
+ console.log(` Previous: ${prev.file}:${prev.line} = ${prev.raw}`);
46
+ console.log(` New: ${filePath}:${line} = ${raw}`);
47
+ }
48
+
49
+ // Type coercion: schema type takes precedence, otherwise auto-cast
50
+ const def = schema ? schema[key] : null;
51
+ const value = (def && def.type)
52
+ ? coerceType(raw, def.type, filePath, line)
53
+ : autoCast(raw, cast);
54
+
55
+ state.values[key] = value;
56
+ state.raw[key] = raw;
57
+ state.origins[key] = { file: filePath, line, raw };
58
+ fileKeys.add(key);
59
+ }
60
+
61
+ module.exports = { applyEntry };