@aimeloic/monkey-tester 2.0.0 → 2.0.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/index.js CHANGED
@@ -2,83 +2,259 @@ import { getHtmlTemplate } from './htmlTemplate.js';
2
2
 
3
3
  export function endtesterExpress() {
4
4
  return (req, res, next) => {
5
- if (req.path !== '/api/tester' && req.path !== '/api/tester/') {
5
+ if (
6
+ req.path !== '/api/tester' &&
7
+ req.path !== '/api/tester/'
8
+ ) {
6
9
  return next();
7
10
  }
8
11
 
9
12
  const expressApp = req.app;
10
13
  const detectedEndpoints = {};
11
14
 
15
+ // =========================
16
+ // Detect Input Types
17
+ // =========================
18
+ function detectInputType(field) {
19
+ const lower = field.toLowerCase();
20
+
21
+ if (lower.includes('email')) return 'email';
22
+
23
+ if (lower.includes('password')) return 'password';
24
+
25
+ if (lower.includes('date')) return 'date';
26
+
27
+ if (
28
+ lower.includes('age') ||
29
+ lower.includes('price') ||
30
+ lower.includes('salary') ||
31
+ lower.includes('stock') ||
32
+ lower.includes('quantity') ||
33
+ lower.includes('count') ||
34
+ lower.includes('total') ||
35
+ lower.includes('amount') ||
36
+ lower.includes('id')
37
+ ) {
38
+ return 'number';
39
+ }
40
+
41
+ if (
42
+ lower.includes('phone') ||
43
+ lower.includes('tel')
44
+ ) {
45
+ return 'tel';
46
+ }
47
+
48
+ if (
49
+ lower.includes('url') ||
50
+ lower.includes('website')
51
+ ) {
52
+ return 'url';
53
+ }
54
+
55
+ return 'text';
56
+ }
57
+
58
+ // =========================
59
+ // Extract req.body fields
60
+ // =========================
61
+ function extractBodyFields(handler) {
62
+ try {
63
+ const source = handler.toString();
64
+
65
+ const regex =
66
+ /(const|let|var)\s*\{\s*([^}]+)\s*\}\s*=\s*req\.body/g;
67
+
68
+ const matches = [...source.matchAll(regex)];
69
+
70
+ const fields = [];
71
+
72
+ matches.forEach((match) => {
73
+ const variables = match[2]
74
+ .split(',')
75
+ .map(v => v.trim())
76
+ .filter(Boolean);
77
+
78
+ variables.forEach((field) => {
79
+ let realField = field;
80
+
81
+ // support aliases
82
+ // name: username
83
+ if (field.includes(':')) {
84
+ realField = field.split(':')[0].trim();
85
+ }
86
+
87
+ // remove defaults
88
+ // age = 0
89
+ if (realField.includes('=')) {
90
+ realField = realField.split('=')[0].trim();
91
+ }
92
+
93
+ // avoid duplicates
94
+ const alreadyExists = fields.find(
95
+ f => f.name === realField
96
+ );
97
+
98
+ if (!alreadyExists) {
99
+ fields.push({
100
+ name: realField,
101
+ label:
102
+ realField.charAt(0).toUpperCase() +
103
+ realField.slice(1),
104
+
105
+ type: detectInputType(realField),
106
+
107
+ placeholder: `Enter ${realField}`
108
+ });
109
+ }
110
+ });
111
+ });
112
+
113
+ return fields;
114
+ } catch (err) {
115
+ console.error('Field extraction error:', err);
116
+ return [];
117
+ }
118
+ }
119
+
120
+ // =========================
121
+ // Parse Express Stack
122
+ // =========================
12
123
  function parseStack(stack, prefix = '') {
13
124
  if (!stack) return;
14
125
 
15
126
  stack.forEach((layer) => {
127
+ // =========================
128
+ // ROUTES
129
+ // =========================
16
130
  if (layer.route) {
17
- const methods = Object.keys(layer.route.methods);
18
- const path = (prefix + layer.route.path).replace(/\/+/g, '/');
19
-
20
- if (path.includes('/api/tester')) return;
131
+ const methods = Object.keys(
132
+ layer.route.methods
133
+ );
134
+
135
+ const path = (
136
+ prefix + layer.route.path
137
+ ).replace(/\/+/g, '/');
138
+
139
+ if (path.includes('/api/tester')) {
140
+ return;
141
+ }
21
142
 
22
143
  methods.forEach((method) => {
23
144
  const httpMethod = method.toUpperCase();
24
- const key = `${httpMethod.toLowerCase()}-${path.replace(/[^a-zA-Z0-9]/g, '-')}`;
25
-
26
- const pathParams = layer.route.keys ? layer.route.keys.map(k => ({
27
- name: k.name,
28
- label: k.name.toUpperCase(),
29
- placeholder: 'value'
30
- })) : [];
31
-
32
- // Context-aware field mapping for common endpoints
145
+
146
+ const key =
147
+ `${httpMethod.toLowerCase()}-` +
148
+ path.replace(/[^a-zA-Z0-9]/g, '-');
149
+
150
+ // =========================
151
+ // PATH PARAMS
152
+ // =========================
153
+ const pathParams = layer.route.keys
154
+ ? layer.route.keys.map((k) => ({
155
+ name: k.name,
156
+ label: k.name.toUpperCase(),
157
+ placeholder: 'value'
158
+ }))
159
+ : [];
160
+
161
+ // =========================
162
+ // BODY FIELDS
163
+ // =========================
33
164
  let bodyFields = [];
34
- if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
35
- if (path.toLowerCase().includes('login') || path.toLowerCase().includes('auth')) {
36
- bodyFields = [
37
- { name: 'email', label: 'Email Address', type: 'text', placeholder: 'user@example.com' },
38
- { name: 'password', label: 'Password', type: 'text', placeholder: '••••••••' }
39
- ];
40
- } else if (path.toLowerCase().includes('product')) {
41
- bodyFields = [
42
- { name: 'name', label: 'Product Name', type: 'text', placeholder: 'Sourdough Loaf' },
43
- { name: 'price', label: 'Unit Price', type: 'number', placeholder: '5.50' },
44
- { name: 'stock', label: 'Initial Inventory', type: 'number', placeholder: '10' }
45
- ];
46
- } else {
47
- // Fallback default structure
48
- bodyFields = [
49
- { name: 'key', label: 'Property Key', type: 'text', placeholder: 'value' }
50
- ];
51
- }
165
+
166
+ if (
167
+ ['POST', 'PUT', 'PATCH'].includes(
168
+ httpMethod
169
+ )
170
+ ) {
171
+ layer.route.stack.forEach((stackLayer) => {
172
+ if (
173
+ stackLayer.handle &&
174
+ typeof stackLayer.handle === 'function'
175
+ ) {
176
+ const extractedFields =
177
+ extractBodyFields(
178
+ stackLayer.handle
179
+ );
180
+
181
+ bodyFields.push(...extractedFields);
182
+ }
183
+ });
184
+
185
+ // remove duplicates
186
+ bodyFields = bodyFields.filter(
187
+ (field, index, self) =>
188
+ index ===
189
+ self.findIndex(
190
+ f => f.name === field.name
191
+ )
192
+ );
52
193
  }
53
194
 
54
195
  detectedEndpoints[key] = {
55
196
  method: httpMethod,
56
- path: path,
197
+ path,
57
198
  title: `${httpMethod} ${path}`,
58
199
  desc: `Auto-discovered endpoint: ${path}`,
59
200
  params: pathParams,
60
201
  fields: bodyFields
61
202
  };
62
203
  });
63
- } else if (layer.name === 'router' && layer.handle && layer.handle.stack) {
204
+ }
205
+
206
+ // =========================
207
+ // NESTED ROUTERS
208
+ // =========================
209
+ else if (
210
+ layer.name === 'router' &&
211
+ layer.handle &&
212
+ layer.handle.stack
213
+ ) {
64
214
  let routerPath = '';
215
+
65
216
  if (layer.regexp) {
66
- const match = layer.regexp.toString().match(/^\/\^\\(.*?)\\\/\?/);
217
+ const match = layer.regexp
218
+ .toString()
219
+ .match(/^\/\^\\(.*?)\\\/\?/);
220
+
67
221
  if (match && match[1]) {
68
- routerPath = match[1].replace(/\\/g, '');
222
+ routerPath = match[1].replace(
223
+ /\\/g,
224
+ ''
225
+ );
69
226
  }
70
227
  }
71
- parseStack(layer.handle.stack, prefix + '/' + routerPath);
228
+
229
+ parseStack(
230
+ layer.handle.stack,
231
+ prefix + '/' + routerPath
232
+ );
72
233
  }
73
234
  });
74
235
  }
75
236
 
76
- if (expressApp._router && expressApp._router.stack) {
237
+ // =========================
238
+ // START PARSING
239
+ // =========================
240
+ if (
241
+ expressApp._router &&
242
+ expressApp._router.stack
243
+ ) {
77
244
  parseStack(expressApp._router.stack);
78
245
  }
79
246
 
80
- const fullHtml = getHtmlTemplate(detectedEndpoints);
81
- res.setHeader('Content-Type', 'text/html');
247
+ // =========================
248
+ // RENDER HTML
249
+ // =========================
250
+ const fullHtml =
251
+ getHtmlTemplate(detectedEndpoints);
252
+
253
+ res.setHeader(
254
+ 'Content-Type',
255
+ 'text/html'
256
+ );
257
+
82
258
  return res.send(fullHtml);
83
259
  };
84
260
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aimeloic/monkey-tester",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Auto route scanning visual runner dashboard.",
5
5
  "main": "index.js",
6
6
  "type": "module",