@centeredgesoft/runtime-mock-routes 2.11.0 → 2.11.2

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/build/run.js CHANGED
@@ -7,7 +7,9 @@ const instrumentation_http_1 = require("@opentelemetry/instrumentation-http");
7
7
  let logRecordProcessors = undefined;
8
8
  if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
9
9
  // OTLP isn't configured, fallback to console logging
10
- logRecordProcessors = [new sdk_node_1.logs.SimpleLogRecordProcessor(new sdk_node_1.logs.ConsoleLogRecordExporter())];
10
+ logRecordProcessors = [
11
+ new sdk_node_1.logs.SimpleLogRecordProcessor({ exporter: new sdk_node_1.logs.ConsoleLogRecordExporter() })
12
+ ];
11
13
  }
12
14
  const sdk = new sdk_node_1.NodeSDK({
13
15
  logRecordProcessors: logRecordProcessors,
package/build/server.js CHANGED
@@ -66,7 +66,8 @@ const runServer = (sdk) => {
66
66
  .option("--tlsKey <tlsKeyPath>", "Path to the TLS key file", process.env.HTTPS_CERT_KEY_FILE)
67
67
  .option("-s, --seed <filePath>", "File path to seed the application", process.env.RUNTIME_MOCK_ROUTES_FILE_PATH)
68
68
  .option("-a, --appVersion <appVersion>", "App Version to use", AppVersion.v2)
69
- .option("-w, --watch", "Watch the seed file for changes and reload routes without restarting", false);
69
+ .option("-w, --watch", "Watch the seed file for changes and reload routes without restarting", false)
70
+ .option("--watch-poll [interval]", "Use stat polling instead of fs.watch (useful for network drives / containers / bind mounts). Optionally specify poll interval in ms.", false);
70
71
  commander_1.program.parse(process.argv);
71
72
  const seedFilePath = commander_1.program.seed ? (0, path_1.resolve)(commander_1.program.seed) : null;
72
73
  // Tracks all files transitively required by the seed so we can bust & re-watch them
@@ -122,7 +123,8 @@ const runServer = (sdk) => {
122
123
  // Watch seed and all transitive dependencies for changes
123
124
  if (commander_1.program.watch && seedFilePath) {
124
125
  let watchDebounce = null;
125
- let depWatchers = [];
126
+ const usePolling = commander_1.program.watchPoll !== false;
127
+ const pollInterval = typeof commander_1.program.watchPoll === 'string' ? parseInt(commander_1.program.watchPoll, 10) || 300 : 300;
126
128
  function doReload() {
127
129
  watchDebounce = null;
128
130
  try {
@@ -135,37 +137,58 @@ const runServer = (sdk) => {
135
137
  console.error('🔥[watch]: Failed to reload seed:', err);
136
138
  }
137
139
  }
140
+ function scheduleReload() {
141
+ if (watchDebounce)
142
+ clearTimeout(watchDebounce);
143
+ watchDebounce = setTimeout(doReload, 100);
144
+ }
145
+ let watchedFiles = new Set();
146
+ let depWatchers = [];
138
147
  function rewatchDeps() {
139
- for (const w of depWatchers) {
140
- w.close();
141
- }
142
- depWatchers = [];
143
- for (const dep of trackedDeps) {
144
- if (!fs_1.default.existsSync(dep)) {
145
- continue;
148
+ const deps = [...trackedDeps].filter(d => fs_1.default.existsSync(d));
149
+ if (usePolling) {
150
+ // Stop polling files no longer in the dep set
151
+ for (const f of watchedFiles) {
152
+ if (!trackedDeps.has(f)) {
153
+ fs_1.default.unwatchFile(f);
154
+ watchedFiles.delete(f);
155
+ }
146
156
  }
147
- try {
148
- const w = fs_1.default.watch(dep, { persistent: true }, (eventType) => {
149
- if (eventType !== 'change' && eventType !== 'rename') {
150
- return;
151
- }
152
- if (watchDebounce) {
153
- clearTimeout(watchDebounce);
154
- }
155
- watchDebounce = setTimeout(doReload, 100);
156
- });
157
- w.on('error', (err) => {
158
- console.error(`🔥[watch]: Watcher error for ${dep}:`, err);
159
- });
160
- depWatchers.push(w);
157
+ // Start polling new deps
158
+ for (const dep of deps) {
159
+ if (!watchedFiles.has(dep)) {
160
+ fs_1.default.watchFile(dep, { persistent: true, interval: pollInterval }, (curr, prev) => {
161
+ if (curr.mtimeMs !== prev.mtimeMs)
162
+ scheduleReload();
163
+ });
164
+ watchedFiles.add(dep);
165
+ }
161
166
  }
162
- catch (_a) {
163
- // File may have been removed; skip
167
+ }
168
+ else {
169
+ // Native fs.watch path
170
+ for (const w of depWatchers)
171
+ w.close();
172
+ depWatchers = [];
173
+ for (const dep of deps) {
174
+ try {
175
+ const w = fs_1.default.watch(dep, { persistent: true }, (eventType) => {
176
+ if (eventType !== 'change' && eventType !== 'rename')
177
+ return;
178
+ scheduleReload();
179
+ });
180
+ w.on('error', (err) => console.error(`🔥[watch]: Watcher error for ${dep}:`, err));
181
+ depWatchers.push(w);
182
+ }
183
+ catch (_a) {
184
+ // File may have been removed; skip
185
+ }
164
186
  }
165
187
  }
166
188
  }
167
189
  rewatchDeps();
168
- console.log(`🔥[watch]: Watching seed and ${trackedDeps.size} dep(s) — ${seedFilePath}`);
190
+ const watchMode = usePolling ? `stat polling (${pollInterval}ms)` : 'fs.watch';
191
+ console.log(`🔥[watch]: Watching seed and ${trackedDeps.size} dep(s) via ${watchMode} — ${seedFilePath}`);
169
192
  }
170
193
  // Register signal handlers
171
194
  process.on('SIGINT', () => gracefulShutdown('SIGINT'));
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@centeredgesoft/runtime-mock-routes",
3
- "version": "2.11.0",
3
+ "version": "2.11.2",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
7
7
  "build": "tsc --project ./",
8
8
  "start": "nodemon -r dotenv/config index.ts",
9
9
  "test": "jest",
10
- "serve": "ts-node run.ts --watch -s ./example/seed.js"
10
+ "serve": "ts-node run.ts --watch --seed ./example/seed.js",
11
+ "serve.poll": "ts-node run.ts --watch --watch-poll=5000 --seed ./example/seed.js"
11
12
  },
12
13
  "bin": {
13
14
  "runtime-mock-routes": "./build/run.js"
@@ -25,8 +26,8 @@
25
26
  "homepage": "https://github.com/CenterEdge/runtime-mock-routes#readme",
26
27
  "dependencies": {
27
28
  "@opentelemetry/instrumentation-express": "^0.57.0",
28
- "@opentelemetry/instrumentation-http": "^0.208.0",
29
- "@opentelemetry/sdk-node": "^0.208.0",
29
+ "@opentelemetry/instrumentation-http": "^0.220.0",
30
+ "@opentelemetry/sdk-node": "^0.220.0",
30
31
  "chance": "^1.1.7",
31
32
  "commander": "^6.2.1",
32
33
  "cors": "^2.8.5",
package/run.ts CHANGED
@@ -6,7 +6,9 @@ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
6
6
  let logRecordProcessors: logs.LogRecordProcessor[] | undefined = undefined;
7
7
  if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
8
8
  // OTLP isn't configured, fallback to console logging
9
- logRecordProcessors = [new logs.SimpleLogRecordProcessor(new logs.ConsoleLogRecordExporter())];
9
+ logRecordProcessors = [
10
+ new logs.SimpleLogRecordProcessor({ exporter: new logs.ConsoleLogRecordExporter() })
11
+ ];
10
12
  }
11
13
 
12
14
  const sdk = new NodeSDK({
package/server.ts CHANGED
@@ -22,6 +22,7 @@ const runServer = (sdk: NodeSDK) => {
22
22
  .option("-s, --seed <filePath>", "File path to seed the application", process.env.RUNTIME_MOCK_ROUTES_FILE_PATH)
23
23
  .option("-a, --appVersion <appVersion>", "App Version to use", AppVersion.v2)
24
24
  .option("-w, --watch", "Watch the seed file for changes and reload routes without restarting", false)
25
+ .option("--watch-poll [interval]", "Use stat polling instead of fs.watch (useful for network drives / containers / bind mounts). Optionally specify poll interval in ms.", false)
25
26
 
26
27
  program.parse(process.argv);
27
28
 
@@ -85,16 +86,18 @@ const runServer = (sdk: NodeSDK) => {
85
86
  httpServer.listen(program.port, () => {
86
87
  console.log(`⚡️[server]: Server is running at http://localhost:${program.port}`);
87
88
  });
89
+
88
90
  if (httpsServer) {
89
- httpsServer.listen(program.httpsPort, () => {
90
- console.log(`⚡️[server]: Server is running at https://localhost:${program.httpsPort}`);
91
- });
91
+ httpsServer.listen(program.httpsPort, () => {
92
+ console.log(`⚡️[server]: Server is running at https://localhost:${program.httpsPort}`);
93
+ });
92
94
  }
93
95
 
94
96
  // Watch seed and all transitive dependencies for changes
95
97
  if (program.watch && seedFilePath) {
96
98
  let watchDebounce: ReturnType<typeof setTimeout> | null = null;
97
- let depWatchers: fs.FSWatcher[] = [];
99
+ const usePolling = program.watchPoll !== false;
100
+ const pollInterval = typeof program.watchPoll === 'string' ? parseInt(program.watchPoll, 10) || 300 : 300;
98
101
 
99
102
  function doReload() {
100
103
  watchDebounce = null;
@@ -108,42 +111,57 @@ const runServer = (sdk: NodeSDK) => {
108
111
  }
109
112
  }
110
113
 
111
- function rewatchDeps() {
112
- for (const w of depWatchers) {
113
- w.close();
114
- }
114
+ function scheduleReload() {
115
+ if (watchDebounce) clearTimeout(watchDebounce);
116
+ watchDebounce = setTimeout(doReload, 100);
117
+ }
115
118
 
116
- depWatchers = [];
119
+ let watchedFiles = new Set<string>();
120
+ let depWatchers: fs.FSWatcher[] = [];
117
121
 
118
- for (const dep of trackedDeps) {
119
- if (!fs.existsSync(dep)) {
120
- continue;
122
+ function rewatchDeps() {
123
+ const deps = [...trackedDeps].filter(d => fs.existsSync(d));
124
+
125
+ if (usePolling) {
126
+ // Stop polling files no longer in the dep set
127
+ for (const f of watchedFiles) {
128
+ if (!trackedDeps.has(f)) {
129
+ fs.unwatchFile(f);
130
+ watchedFiles.delete(f);
131
+ }
121
132
  }
122
-
123
- try {
124
- const w = fs.watch(dep, { persistent: true }, (eventType) => {
125
- if (eventType !== 'change' && eventType !== 'rename') {
126
- return;
127
- }
128
-
129
- if (watchDebounce) {
130
- clearTimeout(watchDebounce);
131
- }
132
-
133
- watchDebounce = setTimeout(doReload, 100);
134
- });
135
- w.on('error', (err) => {
136
- console.error(`🔥[watch]: Watcher error for ${dep}:`, err);
137
- });
138
- depWatchers.push(w);
139
- } catch {
140
- // File may have been removed; skip
133
+ // Start polling new deps
134
+ for (const dep of deps) {
135
+ if (!watchedFiles.has(dep)) {
136
+ fs.watchFile(dep, { persistent: true, interval: pollInterval }, (curr, prev) => {
137
+ if (curr.mtimeMs !== prev.mtimeMs) scheduleReload();
138
+ });
139
+ watchedFiles.add(dep);
140
+ }
141
+ }
142
+ } else {
143
+ // Native fs.watch path
144
+ for (const w of depWatchers) w.close();
145
+ depWatchers = [];
146
+
147
+ for (const dep of deps) {
148
+ try {
149
+ const w = fs.watch(dep, { persistent: true }, (eventType) => {
150
+ if (eventType !== 'change' && eventType !== 'rename') return;
151
+ scheduleReload();
152
+ });
153
+ w.on('error', (err) => console.error(`🔥[watch]: Watcher error for ${dep}:`, err));
154
+ depWatchers.push(w);
155
+ } catch {
156
+ // File may have been removed; skip
157
+ }
141
158
  }
142
159
  }
143
160
  }
144
161
 
145
162
  rewatchDeps();
146
- console.log(`🔥[watch]: Watching seed and ${trackedDeps.size} dep(s) — ${seedFilePath}`);
163
+ const watchMode = usePolling ? `stat polling (${pollInterval}ms)` : 'fs.watch';
164
+ console.log(`🔥[watch]: Watching seed and ${trackedDeps.size} dep(s) via ${watchMode} — ${seedFilePath}`);
147
165
  }
148
166
 
149
167
  // Register signal handlers