@centeredgesoft/runtime-mock-routes 2.10.2 → 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'));
@@ -103,19 +103,26 @@ const appFactory = (runtimeCollection) => {
103
103
  if (process.env.LOG_MOCK_REQUESTS === 'true') {
104
104
  const logger = api_logs_1.logs.getLogger('mock-requests-logger');
105
105
  app.use((req, res, next) => {
106
- // log the request
106
+ let severity = api_logs_1.SeverityNumber.INFO;
107
+ let severityText = 'INFO';
108
+ // Log root requests as trace
109
+ if (req.path === '/') {
110
+ severity = api_logs_1.SeverityNumber.TRACE;
111
+ severityText = 'TRACE';
112
+ }
113
+ // Log the request
107
114
  logger.emit({
108
- severityNumber: api_logs_1.SeverityNumber.INFO,
109
- severityText: 'INFO',
115
+ severityNumber: severity,
116
+ severityText: severityText,
110
117
  body: `Request ${req.path}`,
111
118
  attributes: {
112
- 'http.headers': req.headers,
113
119
  'http.method': req.method,
114
120
  'http.url': req.url,
115
121
  'http.request_body': req.body,
122
+ 'http.request_headers': req.headers,
116
123
  }
117
124
  });
118
- // log the response
125
+ // Log the response
119
126
  const originalSend = res.send;
120
127
  let firstSend = true;
121
128
  res.send = function (body) {
@@ -123,14 +130,15 @@ const appFactory = (runtimeCollection) => {
123
130
  // For some reason send gets called twice per request, this ensures we only log once
124
131
  firstSend = false;
125
132
  logger.emit({
126
- severityNumber: api_logs_1.SeverityNumber.INFO,
127
- severityText: 'INFO',
133
+ severityNumber: severity,
134
+ severityText: severityText,
128
135
  body: `Response ${req.path}`,
129
136
  attributes: {
130
137
  'http.status_code': res.statusCode,
131
138
  'http.method': req.method,
132
139
  'http.url': req.url,
133
140
  'http.response_body': body,
141
+ 'http.response_headers': res.getHeaders()
134
142
  }
135
143
  });
136
144
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@centeredgesoft/runtime-mock-routes",
3
- "version": "2.10.2",
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'));
package/src/appV2.ts CHANGED
@@ -121,20 +121,30 @@ export const appFactory = (runtimeCollection?: RuntimeRequestCollection) => {
121
121
  const logger = logs.getLogger('mock-requests-logger');
122
122
 
123
123
  app.use((req, res, next) => {
124
- // log the request
124
+
125
+ let severity = SeverityNumber.INFO;
126
+ let severityText = 'INFO';
127
+
128
+ // Log root requests as trace
129
+ if (req.path === '/') {
130
+ severity = SeverityNumber.TRACE;
131
+ severityText = 'TRACE';
132
+ }
133
+
134
+ // Log the request
125
135
  logger.emit({
126
- severityNumber: SeverityNumber.INFO,
127
- severityText: 'INFO',
136
+ severityNumber: severity,
137
+ severityText: severityText,
128
138
  body: `Request ${req.path}`,
129
139
  attributes: {
130
- 'http.headers': req.headers,
131
140
  'http.method': req.method,
132
141
  'http.url': req.url,
133
142
  'http.request_body': req.body,
143
+ 'http.request_headers': req.headers,
134
144
  }
135
145
  });
136
146
 
137
- // log the response
147
+ // Log the response
138
148
  const originalSend = res.send;
139
149
  let firstSend = true;
140
150
  res.send = function (body) {
@@ -143,14 +153,15 @@ export const appFactory = (runtimeCollection?: RuntimeRequestCollection) => {
143
153
  firstSend = false;
144
154
 
145
155
  logger.emit({
146
- severityNumber: SeverityNumber.INFO,
147
- severityText: 'INFO',
156
+ severityNumber: severity,
157
+ severityText: severityText,
148
158
  body: `Response ${req.path}`,
149
159
  attributes: {
150
160
  'http.status_code': res.statusCode,
151
161
  'http.method': req.method,
152
162
  'http.url': req.url,
153
163
  'http.response_body': body,
164
+ 'http.response_headers': res.getHeaders()
154
165
  }
155
166
  });
156
167
  }
@@ -305,7 +316,7 @@ export const appFactory = (runtimeCollection?: RuntimeRequestCollection) => {
305
316
  app.put(/\/.*/, catchAllHandler);
306
317
  app.patch(/\/.*/, catchAllHandler);
307
318
  app.delete(/\/.*/, catchAllHandler);
308
-
319
+
309
320
  Object.defineProperty(app, 'runtimeRequestCollection', {
310
321
  get: () => {
311
322
  return cloneDeep(runtimeRequestCollection);