@aimeloic/monkey-tester 4.0.3 → 4.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.
package/monkey.backup.js CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { getHtmlTemplate } from './htmlTemplate.js';
4
4
 
5
- // ─── Field type inference ─────────────────────────────────────────────────────
6
5
  function inferType(name) {
7
6
  const n = name.toLowerCase();
8
7
  if (n.includes('email')) return 'email';
@@ -28,15 +27,12 @@ function buildField(name) {
28
27
  };
29
28
  }
30
29
 
31
- // ─── Extract req.body fields from handler source ──────────────────────────────
32
30
  function extractBodyFields(handler) {
33
31
  try {
34
32
  const source = handler.toString();
35
33
  if (!source || source.includes('[native code]')) return [];
36
34
 
37
35
  const seen = new Map();
38
-
39
- // Pattern 1 — destructuring: const { email, password } = req.body
40
36
  const destructRe = /(?:const|let|var)\s*\{\s*([^}]+)\s*\}\s*=\s*req\.body/g;
41
37
  let m;
42
38
  while ((m = destructRe.exec(source)) !== null) {
@@ -48,7 +44,6 @@ function extractBodyFields(handler) {
48
44
  });
49
45
  }
50
46
 
51
- // Pattern 2 — property access: req.body.email / req.body['email']
52
47
  const accessRe = /req\.body\.([a-zA-Z_$][a-zA-Z0-9_$]*)|req\.body\[['"]([a-zA-Z_$][a-zA-Z0-9_$]*)['"]]/g;
53
48
  while ((m = accessRe.exec(source)) !== null) {
54
49
  const name = m[1] || m[2];
@@ -61,67 +56,35 @@ function extractBodyFields(handler) {
61
56
  }
62
57
  }
63
58
 
64
- // ─── Path-based fallback fields ───────────────────────────────────────────────
65
59
  function fallbackFields(path) {
66
60
  const p = path.toLowerCase();
67
-
68
- if (p.includes('login') || p.includes('signin') || p.includes('auth/login')) {
69
- return ['email', 'password'].map(buildField);
70
- }
71
- if (p.includes('register') || p.includes('signup') || p.includes('auth/register')) {
72
- return ['username', 'email', 'password'].map(buildField);
73
- }
74
- if (p.includes('user')) {
75
- return ['username', 'email', 'password'].map(buildField);
76
- }
77
- if (p.includes('product')) {
78
- return ['name', 'price', 'stock'].map(buildField);
79
- }
80
- if (p.includes('order')) {
81
- return ['productId', 'quantity', 'address'].map(buildField);
82
- }
61
+ if (p.includes('login') || p.includes('signin') || p.includes('auth/login')) return ['email', 'password'].map(buildField);
62
+ if (p.includes('register') || p.includes('signup') || p.includes('auth/register')) return ['username', 'email', 'password'].map(buildField);
63
+ if (p.includes('user')) return ['username', 'email', 'password'].map(buildField);
64
+ if (p.includes('product')) return ['name', 'price', 'stock'].map(buildField);
65
+ if (p.includes('order')) return ['productId', 'quantity', 'address'].map(buildField);
83
66
  return [];
84
67
  }
85
68
 
86
- // ─── Extract router prefix safely from Express layer ─────────────────────────
87
69
  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
-
93
70
  if (!layer.regexp) return '';
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
71
  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(/\/$/, '') || '';
72
+ const patterns = [/^\^\\\/([^\\?$]+)/, /^\^\\\/([a-zA-Z0-9_/-]+)/];
73
+ for (const re of patterns) {
74
+ const m = re.exec(src);
75
+ if (m && m[1]) return '/' + m[1].replace(/\\\//g, '/').replace(/\\/g, '');
76
+ }
77
+ return '';
109
78
  }
110
79
 
111
- // ─── Walk the Express router stack recursively ────────────────────────────────
112
80
  function parseStack(stack, detectedEndpoints, prefix = '') {
113
81
  if (!Array.isArray(stack)) return;
114
82
 
115
83
  for (const layer of stack) {
116
- // ── Named route (app.get / app.post …) ──────────────────────────────────
117
84
  if (layer.route) {
118
- const rawPath = typeof layer.route.path === 'string'
119
- ? layer.route.path
120
- : (layer.route.path ? String(layer.route.path) : '');
121
-
85
+ const rawPath = typeof layer.route.path === 'string' ? layer.route.path : (layer.route.path ? String(layer.route.path) : '');
122
86
  const fullPath = (prefix + rawPath).replace(/\/+/g, '/') || '/';
123
87
 
124
- // Skip the tester route itself
125
88
  if (fullPath.startsWith('/api/tester')) continue;
126
89
 
127
90
  const methods = Object.keys(layer.route.methods || {});
@@ -130,76 +93,66 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
130
93
  const httpMethod = method.toUpperCase();
131
94
  const key = `${httpMethod}::${fullPath}`;
132
95
 
133
- // ── Path params (:id, :slug …) ────────────────────────────────────
134
96
  const pathParams = [];
135
97
  const paramRe = /:([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
136
- const matches = [...fullPath.matchAll(paramRe)];
137
-
138
- for (const pm of matches) {
139
- pathParams.push({
140
- name: pm[1],
141
- label: pm[1].charAt(0).toUpperCase() + pm[1].slice(1),
142
- placeholder: 'value'
143
- });
98
+ let pm;
99
+ while ((pm = paramRe.exec(fullPath)) !== null) {
100
+ pathParams.push({ name: pm[1], label: pm[1].charAt(0).toUpperCase() + pm[1].slice(1), placeholder: 'value' });
144
101
  }
145
102
 
146
- // ── Body fields ──────────────────────────────────────────────────
147
103
  let bodyFields = [];
148
- if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
149
- const handlers = (layer.route.stack || []).map(sl => sl.handle).filter(Boolean);
150
- for (const handler of handlers) {
151
- bodyFields.push(...extractBodyFields(handler));
152
- }
153
- // Deduplicate
154
- const seen = new Map();
155
- bodyFields = bodyFields.filter(f => {
156
- if (seen.has(f.name)) return false;
157
- seen.set(f.name, true);
158
- return true;
159
- });
160
- if (bodyFields.length === 0) {
161
- bodyFields = fallbackFields(fullPath);
162
- }
163
- }
104
+ // Inside your parseStack function in monkey.js, update this block:
105
+ if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
106
+ const handlers = (layer.route.stack || []).map(sl => sl.handle).filter(Boolean);
107
+ for (const handler of handlers) {
108
+ bodyFields.push(...extractBodyFields(handler));
109
+ }
110
+
111
+ const seen = new Map();
112
+ bodyFields = bodyFields.filter(f => {
113
+ if (seen.has(f.name)) return false;
114
+ seen.set(f.name, true);
115
+ return true;
116
+ });
117
+
118
+ // CHANGE THIS: Only apply generic fallbacks if there are no explicit path parameters
119
+ if (bodyFields.length === 0 && pathParams.length === 0) {
120
+ bodyFields = fallbackFields(fullPath);
121
+ }
122
+ }
164
123
 
165
124
  detectedEndpoints[key] = {
166
- method: httpMethod,
167
- path: fullPath,
168
- title: `${httpMethod} ${fullPath}`,
169
- desc: `Auto-discovered endpoint ${fullPath}`,
170
- params: pathParams,
171
- fields: bodyFields,
125
+ method: httpMethod,
126
+ path: fullPath,
127
+ title: `${httpMethod} ${fullPath}`,
128
+ desc: `Auto-discovered endpoint - ${fullPath}`, // Safe ASCII character
129
+ params: pathParams,
130
+ fields: bodyFields,
172
131
  };
173
132
  }
174
- }
175
-
176
- // ── Nested router (app.use('/prefix', router)) ───────────────────────────
177
- else if (layer.handle && typeof layer.handle === 'function' && layer.handle.stack) {
133
+ } else if (layer.name === 'router' && layer.handle && layer.handle.stack) {
178
134
  const routerPrefix = extractRouterPrefix(layer);
179
135
  parseStack(layer.handle.stack, detectedEndpoints, prefix + routerPrefix);
180
136
  }
181
137
  }
182
138
  }
183
139
 
184
- // ─── Middleware ───────────────────────────────────────────────────────────────
185
- function endtesterExpress() {
186
- return function monkeyTesterMiddleware(req, res, next) {
187
- // Normalize path: strip trailing slash, handle both req.path and req.url
188
- const rawPath = (req.path || req.url || '').split('?')[0].replace(/\/+$/, '');
140
+ // function endtesterExpress() { ... }
189
141
 
190
- if (rawPath !== '/api/tester') {
142
+
143
+ // Change it to this instead:
144
+ export function endtesterExpress() {
145
+ return function monkeyTesterMiddleware(req, res, next) {
146
+ if (req.path !== '/api/tester' && req.path !== '/api/tester/') {
191
147
  return next();
192
148
  }
193
149
 
194
150
  const app = req.app;
195
-
196
- // Wait a tick to ensure all routes are registered before scanning
197
- // (handles edge cases where middleware is mounted before some routes)
198
151
  const detectedEndpoints = {};
199
152
 
200
153
  const rootStack =
201
- (app._router && app._router.stack) || // Express 4
202
- (app.router && app.router.stack) || // Express 5
154
+ (app._router && app._router.stack) ||
155
+ (app.router && app.router.stack) ||
203
156
  [];
204
157
 
205
158
  parseStack(rootStack, detectedEndpoints);
@@ -210,4 +163,4 @@ function endtesterExpress() {
210
163
  };
211
164
  }
212
165
 
213
- export { endtesterExpress };
166
+ // export { endtesterExpress };
package/monkey.js CHANGED
@@ -1,98 +1,59 @@
1
1
  'use strict';
2
2
 
3
- import { getHtmlTemplate } from './htmlTemplate.js';
3
+ import { UI } from './htmlTemplate.js';
4
4
 
5
5
  function inferType(name) {
6
6
  const n = name.toLowerCase();
7
- if (n.includes('email')) return 'email';
8
- if (n.includes('password') || n.includes('pass')) return 'password';
9
- if (n.includes('date') || n.includes('birth')) return 'date';
10
- if (n.includes('phone') || n.includes('tel')) return 'tel';
11
- if (n.includes('url') || n.includes('website') || n.includes('link')) return 'url';
12
- if (
13
- n.includes('age') || n.includes('price') || n.includes('amount') ||
14
- n.includes('count') || n.includes('qty') || n.includes('quantity') ||
15
- n.includes('stock') || n.includes('salary') || n.includes('total') ||
16
- (n === 'id') || n.endsWith('_id') || n.endsWith('Id')
17
- ) return 'number';
7
+ if (n.includes('email')) return 'email';
8
+ if (n.includes('password') || n.includes('pass')) return 'password';
9
+ if (n.includes('date')) return 'date';
10
+ if (n.includes('age') || n.includes('price') || n.includes('quantity') || n.includes('stock') || n === 'id' || n.endsWith('id')) return 'number';
18
11
  return 'text';
19
12
  }
20
13
 
21
14
  function buildField(name) {
22
- return {
23
- name,
24
- label: name.charAt(0).toUpperCase() + name.slice(1).replace(/([A-Z])/g, ' $1'),
25
- type: inferType(name),
26
- placeholder: `Enter ${name}`
27
- };
15
+ return { name, label: name.charAt(0).toUpperCase() + name.slice(1).replace(/([A-Z])/g, ' $1'), type: inferType(name), placeholder: `Enter ${name}` };
28
16
  }
29
17
 
30
18
  function extractBodyFields(handler) {
31
19
  try {
32
20
  const source = handler.toString();
33
21
  if (!source || source.includes('[native code]')) return [];
34
-
35
22
  const seen = new Map();
36
23
  const destructRe = /(?:const|let|var)\s*\{\s*([^}]+)\s*\}\s*=\s*req\.body/g;
37
24
  let m;
38
25
  while ((m = destructRe.exec(source)) !== null) {
39
26
  m[1].split(',').forEach(part => {
40
27
  const name = part.split(':')[0].split('=')[0].trim();
41
- if (name && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) && !seen.has(name)) {
42
- seen.set(name, buildField(name));
43
- }
28
+ if (name && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) && !seen.has(name)) seen.set(name, buildField(name));
44
29
  });
45
30
  }
46
-
47
- const accessRe = /req\.body\.([a-zA-Z_$][a-zA-Z0-9_$]*)|req\.body\[['"]([a-zA-Z_$][a-zA-Z0-9_$]*)['"]]/g;
31
+ const accessRe = /req\.body\.([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
48
32
  while ((m = accessRe.exec(source)) !== null) {
49
- const name = m[1] || m[2];
50
- if (name && !seen.has(name)) seen.set(name, buildField(name));
33
+ const name = m[1]; if (name && !seen.has(name)) seen.set(name, buildField(name));
51
34
  }
52
-
53
35
  return Array.from(seen.values());
54
- } catch {
55
- return [];
56
- }
57
- }
58
-
59
- function fallbackFields(path) {
60
- const p = path.toLowerCase();
61
- if (p.includes('login') || p.includes('signin') || p.includes('auth/login')) return ['email', 'password'].map(buildField);
62
- if (p.includes('register') || p.includes('signup') || p.includes('auth/register')) return ['username', 'email', 'password'].map(buildField);
63
- if (p.includes('user')) return ['username', 'email', 'password'].map(buildField);
64
- if (p.includes('product')) return ['name', 'price', 'stock'].map(buildField);
65
- if (p.includes('order')) return ['productId', 'quantity', 'address'].map(buildField);
66
- return [];
36
+ } catch { return []; }
67
37
  }
68
38
 
69
39
  function extractRouterPrefix(layer) {
70
40
  if (!layer.regexp) return '';
71
- const src = layer.regexp.source;
72
- const patterns = [/^\^\\\/([^\\?$]+)/, /^\^\\\/([a-zA-Z0-9_/-]+)/];
73
- for (const re of patterns) {
74
- const m = re.exec(src);
75
- if (m && m[1]) return '/' + m[1].replace(/\\\//g, '/').replace(/\\/g, '');
76
- }
77
- return '';
41
+ const m = [/^\^\\\/([^\\?$]+)/, /^\^\\\/([a-zA-Z0-9_/-]+)/].reduce((acc, re) => acc || re.exec(layer.regexp.source), null);
42
+ return m && m[1] ? '/' + m[1].replace(/\\\//g, '/').replace(/\\/g, '') : '';
78
43
  }
79
44
 
80
45
  function parseStack(stack, detectedEndpoints, prefix = '') {
81
46
  if (!Array.isArray(stack)) return;
82
-
83
47
  for (const layer of stack) {
84
48
  if (layer.route) {
85
49
  const rawPath = typeof layer.route.path === 'string' ? layer.route.path : (layer.route.path ? String(layer.route.path) : '');
86
50
  const fullPath = (prefix + rawPath).replace(/\/+/g, '/') || '/';
87
-
88
51
  if (fullPath.startsWith('/api/tester')) continue;
89
52
 
90
53
  const methods = Object.keys(layer.route.methods || {});
91
-
92
54
  for (const method of methods) {
93
55
  const httpMethod = method.toUpperCase();
94
56
  const key = `${httpMethod}::${fullPath}`;
95
-
96
57
  const pathParams = [];
97
58
  const paramRe = /:([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
98
59
  let pm;
@@ -101,66 +62,43 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
101
62
  }
102
63
 
103
64
  let bodyFields = [];
104
- // Inside your parseStack function in monkey.js, update this block:
105
- if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
106
- const handlers = (layer.route.stack || []).map(sl => sl.handle).filter(Boolean);
107
- for (const handler of handlers) {
108
- bodyFields.push(...extractBodyFields(handler));
109
- }
110
-
111
- const seen = new Map();
112
- bodyFields = bodyFields.filter(f => {
113
- if (seen.has(f.name)) return false;
114
- seen.set(f.name, true);
115
- return true;
116
- });
117
-
118
- // CHANGE THIS: Only apply generic fallbacks if there are no explicit path parameters
119
- if (bodyFields.length === 0 && pathParams.length === 0) {
120
- bodyFields = fallbackFields(fullPath);
121
- }
122
- }
65
+ if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
66
+ const handlers = (layer.route.stack || []).map(sl => sl.handle).filter(Boolean);
67
+ for (const handler of handlers) bodyFields.push(...extractBodyFields(handler));
68
+ const seen = new Map();
69
+ bodyFields = bodyFields.filter(f => !seen.has(f.name) && seen.set(f.name, true));
70
+ }
123
71
 
124
- detectedEndpoints[key] = {
125
- method: httpMethod,
126
- path: fullPath,
127
- title: `${httpMethod} ${fullPath}`,
128
- desc: `Auto-discovered endpoint - ${fullPath}`, // Safe ASCII character
129
- params: pathParams,
130
- fields: bodyFields,
131
- };
72
+ detectedEndpoints[key] = { method: httpMethod, path: fullPath, title: `${httpMethod} ${fullPath}`, desc: `Auto-discovered endpoint - ${fullPath}`, params: pathParams, fields: bodyFields };
132
73
  }
133
74
  } else if (layer.name === 'router' && layer.handle && layer.handle.stack) {
134
- const routerPrefix = extractRouterPrefix(layer);
135
- parseStack(layer.handle.stack, detectedEndpoints, prefix + routerPrefix);
75
+ parseStack(layer.handle.stack, detectedEndpoints, prefix + extractRouterPrefix(layer));
136
76
  }
137
77
  }
138
78
  }
139
79
 
140
- // function endtesterExpress() { ... }
141
-
142
-
143
- // Change it to this instead:
144
80
  export function endtesterExpress() {
145
81
  return function monkeyTesterMiddleware(req, res, next) {
146
- if (req.path !== '/api/tester' && req.path !== '/api/tester/') {
147
- return next();
82
+ const route = req.path.toLowerCase().replace(/\/$/, '');
83
+
84
+ // 1. Serve Standalone Frontend Templates Interceptions
85
+ if (route === '/login') return res.send(UI.login());
86
+ if (route === '/signup') return res.send(UI.signup());
87
+ if (route === '/dashboard') return res.send(UI.dashboard());
88
+
89
+ // 2. Serve the main Tester UI environment
90
+ if (route === '/api/tester') {
91
+ const app = req.app;
92
+ const detectedEndpoints = {};
93
+ const rootStack = (app._router && app._router.stack) || (app.router && app.router.stack) || [];
94
+ parseStack(rootStack, detectedEndpoints);
95
+ const b64 = Buffer.from(JSON.stringify(detectedEndpoints)).toString('base64');
96
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
97
+ return res.send(UI.tester(b64));
148
98
  }
149
99
 
150
- const app = req.app;
151
- const detectedEndpoints = {};
152
-
153
- const rootStack =
154
- (app._router && app._router.stack) ||
155
- (app.router && app.router.stack) ||
156
- [];
157
-
158
- parseStack(rootStack, detectedEndpoints);
159
-
160
- const html = getHtmlTemplate(detectedEndpoints);
161
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
162
- return res.send(html);
100
+ next();
163
101
  };
164
102
  }
165
103
 
166
- // export { endtesterExpress };
104
+ export default { endtesterExpress };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aimeloic/monkey-tester",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "description": "Auto route scanning visual runner dashboard.",
5
5
  "main": "index.js",
6
6
  "type": "module",