@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.
- package/build/server.js +135 -9
- package/package.json +3 -2
- 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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
58
|
-
const httpsServer = httpsOptions.enabled ? https_1.default.createServer(httpsOptions,
|
|
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.
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
54
|
-
const httpsServer = httpsOptions.enabled ? https.createServer(httpsOptions,
|
|
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
|
-
|
|
61
|
-
|
|
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
|