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/src/lib/watch.js CHANGED
@@ -1,110 +1,128 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { resolveFiles } = require('./files');
6
-
7
- /**
8
- * Watch resolved files and reload on changes.
9
- * @param {object} options
10
- * @param {(err: Error|null, result?: {parsed: object, origins: object, files: string[]}) => void} onChange
11
- * @param {(options: object) => {parsed: object, origins: object, files: string[]}} load
12
- * @returns {{close: () => void}}
13
- */
14
- function watch(options, onChange, load)
15
- {
16
- if (typeof onChange !== 'function')
17
- {
18
- throw new Error('onChange callback is required');
19
- }
20
- if (typeof load !== 'function')
21
- {
22
- throw new Error('load function is required');
23
- }
24
-
25
- const files = resolveFiles(options);
26
- const cwd = options.cwd || process.cwd();
27
- const basenames = new Set(files.map((file) => path.basename(file)));
28
- const watchers = [];
29
- let timer = null;
30
- let previousConfig = null;
31
-
32
- const trigger = () =>
33
- {
34
- if (timer) clearTimeout(timer);
35
- timer = setTimeout(() =>
36
- {
37
- try
38
- {
39
- // Disable file precedence debug during reload to avoid conflicts with watch change detection
40
- const reloadOptions = { ...options, debug: false };
41
- const result = load(reloadOptions);
42
-
43
- // Auto-detect changes if debug mode was enabled
44
- if (options.debug && previousConfig)
45
- {
46
- const changes = [];
47
- for (const key in result.parsed)
48
- {
49
- const oldValue = previousConfig[key];
50
- const newValue = result.parsed[key];
51
-
52
- // Compare values (handle dates and objects)
53
- const oldStr = oldValue instanceof Date ? oldValue.toISOString() : JSON.stringify(oldValue);
54
- const newStr = newValue instanceof Date ? newValue.toISOString() : JSON.stringify(newValue);
55
-
56
- if (oldStr !== newStr)
57
- {
58
- changes.push({ key, old: oldValue, new: newValue });
59
- }
60
- }
61
-
62
- if (changes.length > 0)
63
- {
64
- console.log('[molex-env] Config reloaded - changes detected:');
65
- changes.forEach(({ key, old, new: newVal }) =>
66
- {
67
- console.log(` ${key}: ${old} → ${newVal}`);
68
- });
69
- }
70
- }
71
-
72
- previousConfig = { ...result.parsed };
73
- onChange(null, result);
74
- } catch (err)
75
- {
76
- onChange(err);
77
- }
78
- }, 50);
79
- };
80
-
81
- for (const filePath of files)
82
- {
83
- if (!fs.existsSync(filePath)) continue;
84
- watchers.push(fs.watch(filePath, trigger));
85
- }
86
-
87
- if (options.watchMissing !== false)
88
- {
89
- watchers.push(fs.watch(cwd, (event, filename) =>
90
- {
91
- if (!filename) return;
92
- if (basenames.has(filename))
93
- {
94
- trigger();
95
- }
96
- }));
97
- }
98
-
99
- return {
100
- close()
101
- {
102
- watchers.forEach((watcher) => watcher.close());
103
- watchers.length = 0;
104
- }
105
- };
106
- }
107
-
108
- module.exports = {
109
- watch
110
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { resolveFiles } = require('./files');
6
+
7
+ const DEBOUNCE_MS = 50;
8
+
9
+ /* ------------------------------------------------------------------ */
10
+ /* Helpers */
11
+ /* ------------------------------------------------------------------ */
12
+
13
+ /**
14
+ * Serialize a value for comparison (handles Date and objects).
15
+ * @param {any} val
16
+ * @returns {string}
17
+ */
18
+ function serialize(val)
19
+ {
20
+ if (val instanceof Date) return val.toISOString();
21
+ return JSON.stringify(val);
22
+ }
23
+
24
+ /**
25
+ * Detect which keys changed between two config snapshots.
26
+ * @param {object} previous
27
+ * @param {object} current
28
+ * @returns {{key: string, old: any, new: any}[]}
29
+ */
30
+ function detectChanges(previous, current)
31
+ {
32
+ const changes = [];
33
+ for (const key of Object.keys(current))
34
+ {
35
+ if (serialize(previous[key]) !== serialize(current[key]))
36
+ {
37
+ changes.push({ key, old: previous[key], new: current[key] });
38
+ }
39
+ }
40
+ return changes;
41
+ }
42
+
43
+ /**
44
+ * Log detected changes to console.
45
+ * @param {{key: string, old: any, new: any}[]} changes
46
+ */
47
+ function logChanges(changes)
48
+ {
49
+ if (!changes.length) return;
50
+ console.log('[molex-env] Config reloaded - changes detected:');
51
+ for (const { key, old, new: val } of changes)
52
+ {
53
+ console.log(` ${key}: ${old} → ${val}`);
54
+ }
55
+ }
56
+
57
+ /* ------------------------------------------------------------------ */
58
+ /* Public API */
59
+ /* ------------------------------------------------------------------ */
60
+
61
+ /**
62
+ * Watch resolved .menv files and reload on changes.
63
+ * @param {object} options
64
+ * @param {(err: Error|null, result?: object) => void} onChange
65
+ * @param {(options: object) => object} load
66
+ * @returns {{close: () => void}}
67
+ */
68
+ function watch(options, onChange, load)
69
+ {
70
+ if (typeof onChange !== 'function') throw new Error('onChange callback is required');
71
+ if (typeof load !== 'function') throw new Error('load function is required');
72
+
73
+ const files = resolveFiles(options);
74
+ const cwd = options.cwd || process.cwd();
75
+ const basenames = new Set(files.map((f) => path.basename(f)));
76
+ const watchers = [];
77
+ let timer = null;
78
+ let previousConfig = null;
79
+
80
+ const reload = () =>
81
+ {
82
+ if (timer) clearTimeout(timer);
83
+ timer = setTimeout(() =>
84
+ {
85
+ try
86
+ {
87
+ const result = load({ ...options, debug: false });
88
+
89
+ if (options.debug && previousConfig)
90
+ {
91
+ logChanges(detectChanges(previousConfig, result.parsed));
92
+ }
93
+
94
+ previousConfig = { ...result.parsed };
95
+ onChange(null, result);
96
+ } catch (err)
97
+ {
98
+ onChange(err);
99
+ }
100
+ }, DEBOUNCE_MS);
101
+ };
102
+
103
+ // Watch existing files
104
+ for (const filePath of files)
105
+ {
106
+ if (!fs.existsSync(filePath)) continue;
107
+ watchers.push(fs.watch(filePath, reload));
108
+ }
109
+
110
+ // Watch directory for new files matching expected names
111
+ if (options.watchMissing !== false)
112
+ {
113
+ watchers.push(fs.watch(cwd, (_event, filename) =>
114
+ {
115
+ if (filename && basenames.has(filename)) reload();
116
+ }));
117
+ }
118
+
119
+ return {
120
+ close()
121
+ {
122
+ watchers.forEach((w) => w.close());
123
+ watchers.length = 0;
124
+ },
125
+ };
126
+ }
127
+
128
+ module.exports = { watch };