@aimeloic/monkey-tester 1.0.10 → 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,62 +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
- })) : [];
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
+ // =========================
164
+ let bodyFields = [];
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
+ );
193
+ }
31
194
 
32
195
  detectedEndpoints[key] = {
33
196
  method: httpMethod,
34
- path: path,
35
- auth: false,
197
+ path,
36
198
  title: `${httpMethod} ${path}`,
37
199
  desc: `Auto-discovered endpoint: ${path}`,
38
200
  params: pathParams,
39
- fields: ['POST', 'PUT', 'PATCH'].includes(httpMethod) ? [{ name: 'payload', label: 'JSON Body', type: 'text' }] : []
201
+ fields: bodyFields
40
202
  };
41
203
  });
42
- } 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
+ ) {
43
214
  let routerPath = '';
215
+
44
216
  if (layer.regexp) {
45
- const match = layer.regexp.toString().match(/^\/\^\\(.*?)\\\/\?/);
217
+ const match = layer.regexp
218
+ .toString()
219
+ .match(/^\/\^\\(.*?)\\\/\?/);
220
+
46
221
  if (match && match[1]) {
47
- routerPath = match[1].replace(/\\/g, '');
222
+ routerPath = match[1].replace(
223
+ /\\/g,
224
+ ''
225
+ );
48
226
  }
49
227
  }
50
- parseStack(layer.handle.stack, prefix + '/' + routerPath);
228
+
229
+ parseStack(
230
+ layer.handle.stack,
231
+ prefix + '/' + routerPath
232
+ );
51
233
  }
52
234
  });
53
235
  }
54
236
 
55
- if (expressApp._router && expressApp._router.stack) {
237
+ // =========================
238
+ // START PARSING
239
+ // =========================
240
+ if (
241
+ expressApp._router &&
242
+ expressApp._router.stack
243
+ ) {
56
244
  parseStack(expressApp._router.stack);
57
245
  }
58
246
 
59
- const fullHtml = getHtmlTemplate(detectedEndpoints);
60
- 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
+
61
258
  return res.send(fullHtml);
62
259
  };
63
260
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aimeloic/monkey-tester",
3
- "version": "1.0.10",
3
+ "version": "2.0.1",
4
4
  "description": "Auto route scanning visual runner dashboard.",
5
5
  "main": "index.js",
6
6
  "type": "module",