@aimeloic/monkey-tester 3.0.4 → 3.0.5

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 (2) hide show
  1. package/monkey.js +33 -26
  2. package/package.json +1 -1
package/monkey.js CHANGED
@@ -85,20 +85,27 @@ function fallbackFields(path) {
85
85
 
86
86
  // ─── Extract router prefix safely from Express layer ─────────────────────────
87
87
  function extractRouterPrefix(layer) {
88
+ // Prefer explicit path if available
89
+ if (layer.path && typeof layer.path === 'string') {
90
+ return layer.path === '/' ? '' : layer.path;
91
+ }
92
+
88
93
  if (!layer.regexp) return '';
89
- if (layer.path) return layer.path; // If explicit paths exist
90
-
91
- let src = layer.regexp.source;
92
-
93
- // Strip out boilerplate generated patterns from default express mounting structures
94
- src = src
95
- .replace('^\\/', '/')
96
- .replace('\\/?(?=\\/|$)', '')
97
- .replace('(?:\\/(?=$))?(?=\\/|$)', '')
98
- .replace('\\/', '/');
99
-
100
- const cleanPath = src.split('(?')[0].replace(/\\/g, '');
101
- return cleanPath === '/' ? '' : cleanPath;
94
+
95
+ // Convert the regexp back to a path prefix by looking at the regexp source
96
+ // Express generates regexps like: /^\/api\/v1\/?(?=\/|$)/i
97
+ const src = layer.regexp.source;
98
+
99
+ // Extract the literal path segment before any optional/lookahead parts
100
+ // Match from start: ^\/ then literal segments
101
+ const match = src.match(/^\^((?:\\\/[^\\(?[*+{}|$^]+)+)/);
102
+ if (!match) return '';
103
+
104
+ // Unescape the extracted path
105
+ const raw = match[1].replace(/\\\//g, '/');
106
+
107
+ // Remove trailing slash if present
108
+ return raw.replace(/\/$/, '') || '';
102
109
  }
103
110
 
104
111
  // ─── Walk the Express router stack recursively ────────────────────────────────
@@ -114,6 +121,7 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
114
121
 
115
122
  const fullPath = (prefix + rawPath).replace(/\/+/g, '/') || '/';
116
123
 
124
+ // Skip the tester route itself
117
125
  if (fullPath.startsWith('/api/tester')) continue;
118
126
 
119
127
  const methods = Object.keys(layer.route.methods || {});
@@ -122,7 +130,7 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
122
130
  const httpMethod = method.toUpperCase();
123
131
  const key = `${httpMethod}::${fullPath}`;
124
132
 
125
- // ── Path params (:id, :slug …) safely parsed using matchAll ─────────
133
+ // ── Path params (:id, :slug …) ────────────────────────────────────
126
134
  const pathParams = [];
127
135
  const paramRe = /:([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
128
136
  const matches = [...fullPath.matchAll(paramRe)];
@@ -135,13 +143,14 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
135
143
  });
136
144
  }
137
145
 
138
- // ── Body fields ────────────────────────────────────────────────────
146
+ // ── Body fields ──────────────────────────────────────────────────
139
147
  let bodyFields = [];
140
148
  if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
141
149
  const handlers = (layer.route.stack || []).map(sl => sl.handle).filter(Boolean);
142
150
  for (const handler of handlers) {
143
151
  bodyFields.push(...extractBodyFields(handler));
144
152
  }
153
+ // Deduplicate
145
154
  const seen = new Map();
146
155
  bodyFields = bodyFields.filter(f => {
147
156
  if (seen.has(f.name)) return false;
@@ -165,7 +174,7 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
165
174
  }
166
175
 
167
176
  // ── Nested router (app.use('/prefix', router)) ───────────────────────────
168
- else if (layer.name === 'router' && layer.handle && layer.handle.stack) {
177
+ else if (layer.handle && typeof layer.handle === 'function' && layer.handle.stack) {
169
178
  const routerPrefix = extractRouterPrefix(layer);
170
179
  parseStack(layer.handle.stack, detectedEndpoints, prefix + routerPrefix);
171
180
  }
@@ -173,19 +182,19 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
173
182
  }
174
183
 
175
184
  // ─── Middleware ───────────────────────────────────────────────────────────────
176
- // ─── Middleware in monkey.js ──────────────────────────────────────────────────
177
185
  function endtesterExpress() {
178
186
  return function monkeyTesterMiddleware(req, res, next) {
179
- // Standard structural path cleaning
180
- const normalPath = req.path.replace(/\/$/, ''); // removes trailing slash
181
-
182
- if (normalPath !== '/api/tester') {
187
+ // Normalize path: strip trailing slash, handle both req.path and req.url
188
+ const rawPath = (req.path || req.url || '').split('?')[0].replace(/\/+$/, '');
189
+
190
+ if (rawPath !== '/api/tester') {
183
191
  return next();
184
192
  }
185
193
 
186
194
  const app = req.app;
187
-
188
- // CRITICAL FIX: Reset the detected endpoints on each call to prevent stale empty states
195
+
196
+ // Wait a tick to ensure all routes are registered before scanning
197
+ // (handles edge cases where middleware is mounted before some routes)
189
198
  const detectedEndpoints = {};
190
199
 
191
200
  const rootStack =
@@ -193,12 +202,10 @@ function endtesterExpress() {
193
202
  (app.router && app.router.stack) || // Express 5
194
203
  [];
195
204
 
196
- // Scan the live compiled routing stack
197
205
  parseStack(rootStack, detectedEndpoints);
198
206
 
199
- // Render page template
200
207
  const html = getHtmlTemplate(detectedEndpoints);
201
- res.setHeader('Content-Type', 'text/html');
208
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
202
209
  return res.send(html);
203
210
  };
204
211
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aimeloic/monkey-tester",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "description": "Auto route scanning visual runner dashboard.",
5
5
  "main": "index.js",
6
6
  "type": "module",