@centeredgesoft/runtime-mock-routes 2.10.3 → 2.11.0

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/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,42 @@ 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);
36
70
  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);
71
+ const seedFilePath = commander_1.program.seed ? (0, path_1.resolve)(commander_1.program.seed) : null;
72
+ // Tracks all files transitively required by the seed so we can bust & re-watch them
73
+ let trackedDeps = new Set();
74
+ function loadSeed(filePath) {
75
+ // Bust cache for all previously tracked deps so we get fresh modules on reload
76
+ for (const key of trackedDeps) {
77
+ delete require.cache[key];
78
+ }
79
+ // Snapshot before requiring — anything new afterwards is a transitive dep
80
+ const cacheBefore = new Set(Object.keys(require.cache));
40
81
  const funcOrJson = require(filePath);
41
- initialRequests = typeof funcOrJson === 'function' ? funcOrJson() : funcOrJson;
82
+ trackedDeps = new Set();
83
+ for (const key of Object.keys(require.cache)) {
84
+ const normalizedKey = path_1.default.normalize(key);
85
+ const segments = normalizedKey.split(path_1.default.sep);
86
+ if (!cacheBefore.has(key) && !segments.includes('node_modules')) {
87
+ trackedDeps.add(key);
88
+ }
89
+ }
90
+ return typeof funcOrJson === 'function' ? funcOrJson() : funcOrJson;
91
+ }
92
+ let initialRequests = {};
93
+ if (seedFilePath) {
94
+ initialRequests = loadSeed(seedFilePath);
42
95
  }
43
96
  const appVersions = {
44
97
  v1: app_1.appFactory,
45
98
  v2: appV2_1.appFactory
46
99
  };
47
100
  const appFactory = appVersions[commander_1.program.appVersion] || appV2_1.appFactory;
48
- const app = appFactory(initialRequests);
101
+ // Use a mutable handler so the servers don't need to rebind ports on reload
102
+ let currentApp = appFactory(initialRequests);
103
+ const handler = (req, res) => currentApp(req, res);
49
104
  // Setup HTTPS options
50
105
  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
106
  ? {
@@ -54,8 +109,8 @@ const runServer = (sdk) => {
54
109
  enabled: true
55
110
  }
56
111
  : { enabled: false };
57
- const httpServer = http_1.default.createServer(app);
58
- const httpsServer = httpsOptions.enabled ? https_1.default.createServer(httpsOptions, app) : null;
112
+ const httpServer = http_1.default.createServer(handler);
113
+ const httpsServer = httpsOptions.enabled ? https_1.default.createServer(httpsOptions, handler) : null;
59
114
  httpServer.listen(commander_1.program.port, () => {
60
115
  console.log(`⚡️[server]: Server is running at http://localhost:${commander_1.program.port}`);
61
116
  });
@@ -64,6 +119,54 @@ const runServer = (sdk) => {
64
119
  console.log(`⚡️[server]: Server is running at https://localhost:${commander_1.program.httpsPort}`);
65
120
  });
66
121
  }
122
+ // Watch seed and all transitive dependencies for changes
123
+ if (commander_1.program.watch && seedFilePath) {
124
+ let watchDebounce = null;
125
+ let depWatchers = [];
126
+ function doReload() {
127
+ watchDebounce = null;
128
+ try {
129
+ const newRequests = loadSeed(seedFilePath);
130
+ currentApp = appFactory(newRequests);
131
+ console.log(`🔥[watch]: Seed reloaded — watching ${trackedDeps.size} file(s)`);
132
+ rewatchDeps();
133
+ }
134
+ catch (err) {
135
+ console.error('🔥[watch]: Failed to reload seed:', err);
136
+ }
137
+ }
138
+ 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;
146
+ }
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);
161
+ }
162
+ catch (_a) {
163
+ // File may have been removed; skip
164
+ }
165
+ }
166
+ }
167
+ rewatchDeps();
168
+ console.log(`🔥[watch]: Watching seed and ${trackedDeps.size} dep(s) — ${seedFilePath}`);
169
+ }
67
170
  // Register signal handlers
68
171
  process.on('SIGINT', () => gracefulShutdown('SIGINT'));
69
172
  process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@centeredgesoft/runtime-mock-routes",
3
- "version": "2.10.3",
3
+ "version": "2.11.0",
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 -s ./example/seed.js"
11
11
  },
12
12
  "bin": {
13
13
  "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,41 @@ 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)
24
25
 
25
26
  program.parse(process.argv);
26
27
 
27
- let initialRequests: any = {};
28
- if (program.seed) {
29
- const filePath = resolve(program.seed);
28
+ const seedFilePath = program.seed ? resolve(program.seed) : null;
29
+
30
+ // Tracks all files transitively required by the seed so we can bust & re-watch them
31
+ let trackedDeps = new Set<string>();
32
+
33
+ function loadSeed(filePath: string): any {
34
+
35
+ // Bust cache for all previously tracked deps so we get fresh modules on reload
36
+ for (const key of trackedDeps) {
37
+ delete require.cache[key];
38
+ }
39
+
40
+ // Snapshot before requiring — anything new afterwards is a transitive dep
41
+ const cacheBefore = new Set(Object.keys(require.cache));
30
42
  const funcOrJson = require(filePath);
31
- initialRequests = typeof funcOrJson === 'function' ? funcOrJson() : funcOrJson;
43
+
44
+ trackedDeps = new Set<string>();
45
+ for (const key of Object.keys(require.cache)) {
46
+ const normalizedKey = path.normalize(key);
47
+ const segments = normalizedKey.split(path.sep);
48
+ if (!cacheBefore.has(key) && !segments.includes('node_modules')) {
49
+ trackedDeps.add(key);
50
+ }
51
+ }
52
+
53
+ return typeof funcOrJson === 'function' ? funcOrJson() : funcOrJson;
54
+ }
55
+
56
+ let initialRequests: any = {};
57
+ if (seedFilePath) {
58
+ initialRequests = loadSeed(seedFilePath);
32
59
  }
33
60
 
34
61
  type AppFactory = (...args: any[]) => Express;
@@ -39,7 +66,9 @@ const runServer = (sdk: NodeSDK) => {
39
66
  }
40
67
  const appFactory = appVersions[program.appVersion] || appFactoryV2;
41
68
 
42
- const app = appFactory(initialRequests);
69
+ // Use a mutable handler so the servers don't need to rebind ports on reload
70
+ let currentApp = appFactory(initialRequests);
71
+ const handler: http.RequestListener = (req, res) => currentApp(req, res);
43
72
 
44
73
  // Setup HTTPS options
45
74
  const httpsOptions = program.tlsCert && program.tlsKey && fs.existsSync(program.tlsCert) && fs.existsSync(program.tlsKey)
@@ -50,8 +79,8 @@ const runServer = (sdk: NodeSDK) => {
50
79
  }
51
80
  : { enabled: false };
52
81
 
53
- const httpServer = http.createServer(app);
54
- const httpsServer = httpsOptions.enabled ? https.createServer(httpsOptions, app) : null;
82
+ const httpServer = http.createServer(handler);
83
+ const httpsServer = httpsOptions.enabled ? https.createServer(httpsOptions, handler) : null;
55
84
 
56
85
  httpServer.listen(program.port, () => {
57
86
  console.log(`⚡️[server]: Server is running at http://localhost:${program.port}`);
@@ -62,6 +91,61 @@ const runServer = (sdk: NodeSDK) => {
62
91
  });
63
92
  }
64
93
 
94
+ // Watch seed and all transitive dependencies for changes
95
+ if (program.watch && seedFilePath) {
96
+ let watchDebounce: ReturnType<typeof setTimeout> | null = null;
97
+ let depWatchers: fs.FSWatcher[] = [];
98
+
99
+ function doReload() {
100
+ watchDebounce = null;
101
+ try {
102
+ const newRequests = loadSeed(seedFilePath);
103
+ currentApp = appFactory(newRequests);
104
+ console.log(`🔥[watch]: Seed reloaded — watching ${trackedDeps.size} file(s)`);
105
+ rewatchDeps();
106
+ } catch (err) {
107
+ console.error('🔥[watch]: Failed to reload seed:', err);
108
+ }
109
+ }
110
+
111
+ function rewatchDeps() {
112
+ for (const w of depWatchers) {
113
+ w.close();
114
+ }
115
+
116
+ depWatchers = [];
117
+
118
+ for (const dep of trackedDeps) {
119
+ if (!fs.existsSync(dep)) {
120
+ continue;
121
+ }
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
141
+ }
142
+ }
143
+ }
144
+
145
+ rewatchDeps();
146
+ console.log(`🔥[watch]: Watching seed and ${trackedDeps.size} dep(s) — ${seedFilePath}`);
147
+ }
148
+
65
149
  // Register signal handlers
66
150
  process.on('SIGINT', () => gracefulShutdown('SIGINT'));
67
151
  process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));