@centeredgesoft/runtime-mock-routes 2.10.3 → 2.11.1

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.
Files changed (3) hide show
  1. package/build/server.js +135 -9
  2. package/package.json +3 -2
  3. package/server.ts +113 -11
package/build/server.js CHANGED
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
36
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
37
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -17,7 +50,7 @@ const fs_1 = __importDefault(require("fs"));
17
50
  const http_1 = __importDefault(require("http"));
18
51
  const https_1 = __importDefault(require("https"));
19
52
  const commander_1 = require("commander");
20
- const path_1 = require("path");
53
+ const path_1 = __importStar(require("path"));
21
54
  const app_1 = require("./src/app");
22
55
  const appV2_1 = require("./src/appV2");
23
56
  var AppVersion;
@@ -32,20 +65,43 @@ const runServer = (sdk) => {
32
65
  .option("--tlsCert <tlsCertPath>", "Path to the TLS certificate file", process.env.HTTPS_CERT_FILE)
33
66
  .option("--tlsKey <tlsKeyPath>", "Path to the TLS key file", process.env.HTTPS_CERT_KEY_FILE)
34
67
  .option("-s, --seed <filePath>", "File path to seed the application", process.env.RUNTIME_MOCK_ROUTES_FILE_PATH)
35
- .option("-a, --appVersion <appVersion>", "App Version to use", AppVersion.v2);
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)
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);
36
71
  commander_1.program.parse(process.argv);
37
- let initialRequests = {};
38
- if (commander_1.program.seed) {
39
- const filePath = (0, path_1.resolve)(commander_1.program.seed);
72
+ const seedFilePath = commander_1.program.seed ? (0, path_1.resolve)(commander_1.program.seed) : null;
73
+ // Tracks all files transitively required by the seed so we can bust & re-watch them
74
+ let trackedDeps = new Set();
75
+ function loadSeed(filePath) {
76
+ // Bust cache for all previously tracked deps so we get fresh modules on reload
77
+ for (const key of trackedDeps) {
78
+ delete require.cache[key];
79
+ }
80
+ // Snapshot before requiring — anything new afterwards is a transitive dep
81
+ const cacheBefore = new Set(Object.keys(require.cache));
40
82
  const funcOrJson = require(filePath);
41
- initialRequests = typeof funcOrJson === 'function' ? funcOrJson() : funcOrJson;
83
+ trackedDeps = new Set();
84
+ for (const key of Object.keys(require.cache)) {
85
+ const normalizedKey = path_1.default.normalize(key);
86
+ const segments = normalizedKey.split(path_1.default.sep);
87
+ if (!cacheBefore.has(key) && !segments.includes('node_modules')) {
88
+ trackedDeps.add(key);
89
+ }
90
+ }
91
+ return typeof funcOrJson === 'function' ? funcOrJson() : funcOrJson;
92
+ }
93
+ let initialRequests = {};
94
+ if (seedFilePath) {
95
+ initialRequests = loadSeed(seedFilePath);
42
96
  }
43
97
  const appVersions = {
44
98
  v1: app_1.appFactory,
45
99
  v2: appV2_1.appFactory
46
100
  };
47
101
  const appFactory = appVersions[commander_1.program.appVersion] || appV2_1.appFactory;
48
- const app = appFactory(initialRequests);
102
+ // Use a mutable handler so the servers don't need to rebind ports on reload
103
+ let currentApp = appFactory(initialRequests);
104
+ const handler = (req, res) => currentApp(req, res);
49
105
  // Setup HTTPS options
50
106
  const httpsOptions = commander_1.program.tlsCert && commander_1.program.tlsKey && fs_1.default.existsSync(commander_1.program.tlsCert) && fs_1.default.existsSync(commander_1.program.tlsKey)
51
107
  ? {
@@ -54,8 +110,8 @@ const runServer = (sdk) => {
54
110
  enabled: true
55
111
  }
56
112
  : { enabled: false };
57
- const httpServer = http_1.default.createServer(app);
58
- const httpsServer = httpsOptions.enabled ? https_1.default.createServer(httpsOptions, app) : null;
113
+ const httpServer = http_1.default.createServer(handler);
114
+ const httpsServer = httpsOptions.enabled ? https_1.default.createServer(httpsOptions, handler) : null;
59
115
  httpServer.listen(commander_1.program.port, () => {
60
116
  console.log(`⚡️[server]: Server is running at http://localhost:${commander_1.program.port}`);
61
117
  });
@@ -64,6 +120,76 @@ const runServer = (sdk) => {
64
120
  console.log(`⚡️[server]: Server is running at https://localhost:${commander_1.program.httpsPort}`);
65
121
  });
66
122
  }
123
+ // Watch seed and all transitive dependencies for changes
124
+ if (commander_1.program.watch && seedFilePath) {
125
+ let watchDebounce = null;
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;
128
+ function doReload() {
129
+ watchDebounce = null;
130
+ try {
131
+ const newRequests = loadSeed(seedFilePath);
132
+ currentApp = appFactory(newRequests);
133
+ console.log(`🔥[watch]: Seed reloaded — watching ${trackedDeps.size} file(s)`);
134
+ rewatchDeps();
135
+ }
136
+ catch (err) {
137
+ console.error('🔥[watch]: Failed to reload seed:', err);
138
+ }
139
+ }
140
+ function scheduleReload() {
141
+ if (watchDebounce)
142
+ clearTimeout(watchDebounce);
143
+ watchDebounce = setTimeout(doReload, 100);
144
+ }
145
+ let watchedFiles = new Set();
146
+ let depWatchers = [];
147
+ function rewatchDeps() {
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
+ }
156
+ }
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
+ }
166
+ }
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
+ }
186
+ }
187
+ }
188
+ }
189
+ rewatchDeps();
190
+ const watchMode = usePolling ? `stat polling (${pollInterval}ms)` : 'fs.watch';
191
+ console.log(`🔥[watch]: Watching seed and ${trackedDeps.size} dep(s) via ${watchMode} — ${seedFilePath}`);
192
+ }
67
193
  // Register signal handlers
68
194
  process.on('SIGINT', () => gracefulShutdown('SIGINT'));
69
195
  process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@centeredgesoft/runtime-mock-routes",
3
- "version": "2.10.3",
3
+ "version": "2.11.1",
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 -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"
package/server.ts CHANGED
@@ -3,7 +3,7 @@ import http from 'http';
3
3
  import https from 'https';
4
4
  import { Express } from 'express'
5
5
  import { program } from 'commander';
6
- import { resolve } from 'path';
6
+ import path, { resolve } from 'path';
7
7
  import { appFactory as appFactoryV1 } from './src/app';
8
8
  import { appFactory as appFactoryV2 } from './src/appV2';
9
9
  import { NodeSDK } from '@opentelemetry/sdk-node';
@@ -21,14 +21,42 @@ const runServer = (sdk: NodeSDK) => {
21
21
  .option("--tlsKey <tlsKeyPath>", "Path to the TLS key file", process.env.HTTPS_CERT_KEY_FILE)
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
+ .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)
24
26
 
25
27
  program.parse(process.argv);
26
28
 
27
- let initialRequests: any = {};
28
- if (program.seed) {
29
- const filePath = resolve(program.seed);
29
+ const seedFilePath = program.seed ? resolve(program.seed) : null;
30
+
31
+ // Tracks all files transitively required by the seed so we can bust & re-watch them
32
+ let trackedDeps = new Set<string>();
33
+
34
+ function loadSeed(filePath: string): any {
35
+
36
+ // Bust cache for all previously tracked deps so we get fresh modules on reload
37
+ for (const key of trackedDeps) {
38
+ delete require.cache[key];
39
+ }
40
+
41
+ // Snapshot before requiring — anything new afterwards is a transitive dep
42
+ const cacheBefore = new Set(Object.keys(require.cache));
30
43
  const funcOrJson = require(filePath);
31
- initialRequests = typeof funcOrJson === 'function' ? funcOrJson() : funcOrJson;
44
+
45
+ trackedDeps = new Set<string>();
46
+ for (const key of Object.keys(require.cache)) {
47
+ const normalizedKey = path.normalize(key);
48
+ const segments = normalizedKey.split(path.sep);
49
+ if (!cacheBefore.has(key) && !segments.includes('node_modules')) {
50
+ trackedDeps.add(key);
51
+ }
52
+ }
53
+
54
+ return typeof funcOrJson === 'function' ? funcOrJson() : funcOrJson;
55
+ }
56
+
57
+ let initialRequests: any = {};
58
+ if (seedFilePath) {
59
+ initialRequests = loadSeed(seedFilePath);
32
60
  }
33
61
 
34
62
  type AppFactory = (...args: any[]) => Express;
@@ -39,7 +67,9 @@ const runServer = (sdk: NodeSDK) => {
39
67
  }
40
68
  const appFactory = appVersions[program.appVersion] || appFactoryV2;
41
69
 
42
- const app = appFactory(initialRequests);
70
+ // Use a mutable handler so the servers don't need to rebind ports on reload
71
+ let currentApp = appFactory(initialRequests);
72
+ const handler: http.RequestListener = (req, res) => currentApp(req, res);
43
73
 
44
74
  // Setup HTTPS options
45
75
  const httpsOptions = program.tlsCert && program.tlsKey && fs.existsSync(program.tlsCert) && fs.existsSync(program.tlsKey)
@@ -50,16 +80,88 @@ const runServer = (sdk: NodeSDK) => {
50
80
  }
51
81
  : { enabled: false };
52
82
 
53
- const httpServer = http.createServer(app);
54
- const httpsServer = httpsOptions.enabled ? https.createServer(httpsOptions, app) : null;
83
+ const httpServer = http.createServer(handler);
84
+ const httpsServer = httpsOptions.enabled ? https.createServer(httpsOptions, handler) : null;
55
85
 
56
86
  httpServer.listen(program.port, () => {
57
87
  console.log(`⚡️[server]: Server is running at http://localhost:${program.port}`);
58
88
  });
89
+
59
90
  if (httpsServer) {
60
- httpsServer.listen(program.httpsPort, () => {
61
- console.log(`⚡️[server]: Server is running at https://localhost:${program.httpsPort}`);
62
- });
91
+ httpsServer.listen(program.httpsPort, () => {
92
+ console.log(`⚡️[server]: Server is running at https://localhost:${program.httpsPort}`);
93
+ });
94
+ }
95
+
96
+ // Watch seed and all transitive dependencies for changes
97
+ if (program.watch && seedFilePath) {
98
+ let watchDebounce: ReturnType<typeof setTimeout> | null = null;
99
+ const usePolling = program.watchPoll !== false;
100
+ const pollInterval = typeof program.watchPoll === 'string' ? parseInt(program.watchPoll, 10) || 300 : 300;
101
+
102
+ function doReload() {
103
+ watchDebounce = null;
104
+ try {
105
+ const newRequests = loadSeed(seedFilePath);
106
+ currentApp = appFactory(newRequests);
107
+ console.log(`🔥[watch]: Seed reloaded — watching ${trackedDeps.size} file(s)`);
108
+ rewatchDeps();
109
+ } catch (err) {
110
+ console.error('🔥[watch]: Failed to reload seed:', err);
111
+ }
112
+ }
113
+
114
+ function scheduleReload() {
115
+ if (watchDebounce) clearTimeout(watchDebounce);
116
+ watchDebounce = setTimeout(doReload, 100);
117
+ }
118
+
119
+ let watchedFiles = new Set<string>();
120
+ let depWatchers: fs.FSWatcher[] = [];
121
+
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
+ }
132
+ }
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
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ rewatchDeps();
163
+ const watchMode = usePolling ? `stat polling (${pollInterval}ms)` : 'fs.watch';
164
+ console.log(`🔥[watch]: Watching seed and ${trackedDeps.size} dep(s) via ${watchMode} — ${seedFilePath}`);
63
165
  }
64
166
 
65
167
  // Register signal handlers