@aimeloic/monkey-tester 2.0.2 → 2.0.4
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/html.backup.js +140 -52
- package/htmlTemplate.js +15 -22
- package/index.js +52 -66
- package/package.json +1 -1
package/html.backup.js
CHANGED
|
@@ -24,59 +24,150 @@ export function getHtmlTemplate(endpoints) {
|
|
|
24
24
|
--blue: #5a86c0;
|
|
25
25
|
--radius: 8px;
|
|
26
26
|
}
|
|
27
|
+
|
|
27
28
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
29
|
+
|
|
28
30
|
body {
|
|
29
|
-
background: var(--bg);
|
|
31
|
+
background: var(--bg);
|
|
32
|
+
color: var(--text);
|
|
33
|
+
font-family: 'DM Sans', sans-serif;
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
height: 100vh;
|
|
36
|
+
overflow: hidden; /* Prevents whole-page scrolling */
|
|
30
37
|
background-image: radial-gradient(ellipse 80% 60% at 50% -20%, #3a2a0a22 0%, transparent 70%);
|
|
31
38
|
}
|
|
39
|
+
|
|
32
40
|
header {
|
|
33
|
-
border-bottom: 1px solid var(--border);
|
|
34
|
-
|
|
41
|
+
border-bottom: 1px solid var(--border);
|
|
42
|
+
padding: 16px 32px;
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: 20px;
|
|
46
|
+
background: #0e0c09ee;
|
|
47
|
+
backdrop-filter: blur(8px);
|
|
48
|
+
height: 65px;
|
|
35
49
|
}
|
|
36
|
-
|
|
37
|
-
.logo
|
|
38
|
-
.
|
|
50
|
+
|
|
51
|
+
.logo { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--accent); letter-spacing: 0.02em; }
|
|
52
|
+
.logo span { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; display: inline-block; margin-left: 8px; font-weight: 400; }
|
|
53
|
+
.header-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
|
|
39
54
|
.jwt-wrap, .base-url-wrap { display: flex; align-items: center; gap: 8px; }
|
|
40
|
-
.jwt-wrap label, .base-url-wrap label { color: var(--text-dim); font-size:
|
|
55
|
+
.jwt-wrap label, .base-url-wrap label { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; letter-spacing: 0.05em; }
|
|
56
|
+
|
|
41
57
|
#jwt-input, #base-url {
|
|
42
58
|
background: var(--surface2); border: 1px solid var(--border); color: var(--text);
|
|
43
|
-
font-family: 'DM Mono', monospace; font-size:
|
|
59
|
+
font-family: 'DM Mono', monospace; font-size: 12px; padding: 6px 12px; border-radius: var(--radius); width: 220px; outline: none;
|
|
44
60
|
}
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
|
|
62
|
+
/* Fixed view height viewport matrix layout grid rules */
|
|
63
|
+
.layout {
|
|
64
|
+
display: grid;
|
|
65
|
+
grid-template-columns: 280px 1fr 450px;
|
|
66
|
+
height: calc(100vh - 65px);
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
aside {
|
|
71
|
+
border-right: 1px solid var(--border);
|
|
72
|
+
overflow-y: auto;
|
|
73
|
+
padding: 16px 0;
|
|
74
|
+
background: #0b0907;
|
|
75
|
+
}
|
|
76
|
+
|
|
47
77
|
.section-label { font-size: 10px; font-family: 'DM Mono', monospace; color: var(--text-dim); letter-spacing: 0.12em; text-transform: uppercase; padding: 12px 18px 6px; }
|
|
48
|
-
|
|
78
|
+
|
|
79
|
+
.nav-item { display: flex; align-items: center; gap: 10px; padding: 10px 18px; cursor: pointer; border-left: 2px solid transparent; color: var(--text-dim); transition: all 0.2s; }
|
|
49
80
|
.nav-item:hover { background: var(--surface); color: var(--text); }
|
|
50
81
|
.nav-item.active { border-left-color: var(--accent); background: var(--surface); color: var(--accent); }
|
|
51
|
-
|
|
82
|
+
|
|
83
|
+
.method-badge { font-family: 'DM Mono', monospace; font-size: 9px; font-weight: 600; padding: 2px 6px; border-radius: 4px; min-width: 52px; text-align: center; text-transform: uppercase; }
|
|
52
84
|
.GET { background: #1a3a22; color: #6ba05a; }
|
|
53
85
|
.POST { background: #1a2e3a; color: #5a86c0; }
|
|
54
86
|
.PUT, .PATCH { background: #3a2e10; color: #e8a838; }
|
|
55
87
|
.DELETE { background: #3a1a14; color: #d45c3c; }
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
88
|
+
|
|
89
|
+
.nav-label { font-size: 12px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
90
|
+
|
|
91
|
+
main {
|
|
92
|
+
overflow-y: auto;
|
|
93
|
+
padding: 32px;
|
|
94
|
+
background: #0e0c09;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.endpoint-title { font-family: 'Playfair Display', serif; font-size: 24px; color: var(--accent); margin-bottom: 8px; }
|
|
98
|
+
.endpoint-path { font-family: 'DM Mono', monospace; font-size: 13px; color: var(--text-dim); margin-bottom: 24px; display: flex; align-items: center; gap: 8px; }
|
|
60
99
|
.endpoint-desc { color: var(--text-dim); font-size: 13px; line-height: 1.6; margin-bottom: 24px; border-left: 2px solid var(--border); padding-left: 12px; }
|
|
61
|
-
|
|
62
|
-
.form-section
|
|
63
|
-
.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.
|
|
67
|
-
.
|
|
100
|
+
|
|
101
|
+
.form-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 20px; }
|
|
102
|
+
.form-section-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 16px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
|
|
103
|
+
|
|
104
|
+
.field-row { display: grid; grid-template-columns: 150px 1fr; align-items: center; gap: 16px; margin-bottom: 14px; }
|
|
105
|
+
.field-row:last-child { margin-bottom: 0; }
|
|
106
|
+
.field-label { font-family: 'DM Mono', monospace; font-size: 12px; color: var(--text-dim); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
107
|
+
|
|
108
|
+
input[type=text], input[type=password], input[type=number], input[type=date], input[type=tel], input[type=url], select {
|
|
109
|
+
background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-family: 'DM Sans', sans-serif; font-size: 13px; padding: 8px 12px; border-radius: var(--radius); width: 100%; outline: none; transition: border-color 0.2s;
|
|
110
|
+
}
|
|
111
|
+
input:focus { border-color: var(--accent); }
|
|
112
|
+
|
|
113
|
+
.btn-row { margin-top: 24px; display: flex; gap: 12px; }
|
|
114
|
+
.btn { background: var(--accent); color: #0e0c09; border: none; padding: 10px 24px; border-radius: var(--radius); font-size: 13px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; }
|
|
115
|
+
.btn:hover { background: #f0b850; }
|
|
68
116
|
.btn-secondary { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); }
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.
|
|
117
|
+
.btn-secondary:hover { color: var(--text); background: var(--surface); }
|
|
118
|
+
|
|
119
|
+
/* FIXED: Response block scroll logic layout rules */
|
|
120
|
+
.response-panel {
|
|
121
|
+
border-left: 1px solid var(--border);
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
background: #110e0a;
|
|
126
|
+
}
|
|
127
|
+
.response-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; background: var(--surface); height: 50px; }
|
|
128
|
+
.response-header-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
129
|
+
.status-badge { font-family: 'DM Mono', monospace; font-size: 12px; margin-left: auto; padding: 2px 8px; border-radius: 4px; font-weight: 500; }
|
|
73
130
|
.status-ok { background: #1a3a22; color: #6ba05a; }
|
|
74
131
|
.status-err { background: #3a1a14; color: #d45c3c; }
|
|
75
132
|
.status-idle { background: var(--surface2); color: var(--text-dim); }
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
.
|
|
79
|
-
|
|
133
|
+
|
|
134
|
+
/* FIXED: Body panel scrolls y natively, inner token element handles x scrolling */
|
|
135
|
+
.response-body {
|
|
136
|
+
flex: 1;
|
|
137
|
+
overflow-y: auto;
|
|
138
|
+
overflow-x: hidden;
|
|
139
|
+
padding: 0;
|
|
140
|
+
background: #0d0b08;
|
|
141
|
+
}
|
|
142
|
+
.response-body.empty {
|
|
143
|
+
color: var(--text-dim);
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
padding: 20px;
|
|
148
|
+
text-align: center;
|
|
149
|
+
font-size: 13px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* FIXED: Token code output container handles micro horizontal data flows elegantly */
|
|
153
|
+
.json-render-block {
|
|
154
|
+
display: block;
|
|
155
|
+
padding: 20px;
|
|
156
|
+
margin: 0;
|
|
157
|
+
font-family: 'DM Mono', monospace;
|
|
158
|
+
font-size: 12px;
|
|
159
|
+
line-height: 1.5;
|
|
160
|
+
white-space: pre;
|
|
161
|
+
overflow-x: auto;
|
|
162
|
+
word-break: normal;
|
|
163
|
+
word-wrap: normal;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.json-key { color: #e8a838; }
|
|
167
|
+
.json-str { color: #9ab878; }
|
|
168
|
+
.json-num { color: #5a86c0; }
|
|
169
|
+
|
|
170
|
+
#toast { position: fixed; bottom: 24px; right: 24px; background: var(--surface2); border: 1px solid var(--border); padding: 10px 18px; border-radius: var(--radius); opacity: 0; transition: all .25s; z-index: 1000; font-family: 'DM Mono', monospace; font-size: 12px; color: var(--accent); }
|
|
80
171
|
#toast.show { opacity: 1; }
|
|
81
172
|
</style>
|
|
82
173
|
</head>
|
|
@@ -149,10 +240,9 @@ function buildSidebar() {
|
|
|
149
240
|
if (keys.length > 0) renderPanel(keys[0]);
|
|
150
241
|
}
|
|
151
242
|
|
|
152
|
-
function makeInputString(type, id, placeholder
|
|
243
|
+
function makeInputString(type, id, placeholder) {
|
|
153
244
|
const pAttr = placeholder ? ' placeholder="' + placeholder + '"' : '';
|
|
154
|
-
|
|
155
|
-
return '<input type="' + type + '" id="' + id + '"' + pAttr + vAttr + ' />';
|
|
245
|
+
return '<input type="' + type + '" id="' + id + '"' + pAttr + ' />';
|
|
156
246
|
}
|
|
157
247
|
|
|
158
248
|
function renderPanel(epKey) {
|
|
@@ -170,11 +260,10 @@ function renderPanel(epKey) {
|
|
|
170
260
|
<div class="endpoint-desc">\${ep.desc}</div>
|
|
171
261
|
\`;
|
|
172
262
|
|
|
173
|
-
// Path Parameters
|
|
174
263
|
if (ep.params && ep.params.length) {
|
|
175
264
|
html += \`<div class="form-section"><div class="form-section-title">Path Parameters</div>\`;
|
|
176
265
|
ep.params.forEach(function(p) {
|
|
177
|
-
const inputHtml = makeInputString('text', 'param-' + p.name, p.placeholder
|
|
266
|
+
const inputHtml = makeInputString('text', 'param-' + p.name, p.placeholder);
|
|
178
267
|
html += \`
|
|
179
268
|
<div class="field-row">
|
|
180
269
|
<label class="field-label">\${p.label}</label>
|
|
@@ -185,14 +274,13 @@ function renderPanel(epKey) {
|
|
|
185
274
|
html += \`</div>\`;
|
|
186
275
|
}
|
|
187
276
|
|
|
188
|
-
// FIXED: Renders native, individual form fields instead of a single text payload editor block
|
|
189
277
|
if (ep.fields && ep.fields.length) {
|
|
190
278
|
html += \`<div class="form-section"><div class="form-section-title">HTTP JSON Request Payload Parameters</div>\`;
|
|
191
279
|
ep.fields.forEach(function(f) {
|
|
192
|
-
const inputHtml = makeInputString(f.type || 'text', 'field-' + f.name, f.placeholder || ''
|
|
280
|
+
const inputHtml = makeInputString(f.type || 'text', 'field-' + f.name, f.placeholder || '');
|
|
193
281
|
html += \`
|
|
194
282
|
<div class="field-row">
|
|
195
|
-
<label class="field-label"
|
|
283
|
+
<label class="field-label">\${f.label}</label>
|
|
196
284
|
\${inputHtml}
|
|
197
285
|
</div>
|
|
198
286
|
\`;
|
|
@@ -214,7 +302,6 @@ async function sendRequest() {
|
|
|
214
302
|
const ep = ENDPOINTS[currentEp];
|
|
215
303
|
let path = ep.path;
|
|
216
304
|
|
|
217
|
-
// Compile URL path parameter tags
|
|
218
305
|
if (ep.params) {
|
|
219
306
|
for (const p of ep.params) {
|
|
220
307
|
const val = document.getElementById('param-' + p.name)?.value.trim();
|
|
@@ -230,23 +317,20 @@ async function sendRequest() {
|
|
|
230
317
|
const jwt = document.getElementById('jwt-input').value.trim();
|
|
231
318
|
if (jwt) headers['Authorization'] = 'Bearer ' + jwt;
|
|
232
319
|
|
|
233
|
-
// FIXED: Dynamically bundles inputs into a background payload object structure seamlessly
|
|
234
320
|
let body = undefined;
|
|
235
321
|
if (ep.fields && ep.fields.length && ['POST', 'PUT', 'PATCH'].includes(ep.method)) {
|
|
236
|
-
const
|
|
237
|
-
|
|
322
|
+
const jsonPayload = {};
|
|
238
323
|
for (const f of ep.fields) {
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
241
|
-
let
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
value = Number(value);
|
|
324
|
+
const targetEl = document.getElementById('field-' + f.name);
|
|
325
|
+
if (targetEl) {
|
|
326
|
+
let entryVal = targetEl.value.trim();
|
|
327
|
+
if (f.type === 'number' && entryVal !== '') {
|
|
328
|
+
entryVal = Number(entryVal);
|
|
245
329
|
}
|
|
246
|
-
|
|
330
|
+
jsonPayload[f.name] = entryVal;
|
|
247
331
|
}
|
|
248
332
|
}
|
|
249
|
-
body = JSON.stringify(
|
|
333
|
+
body = JSON.stringify(jsonPayload);
|
|
250
334
|
}
|
|
251
335
|
|
|
252
336
|
setResponse(null, 'loading');
|
|
@@ -271,6 +355,7 @@ function setResponse(data, state, status, ms) {
|
|
|
271
355
|
if (state === 'loading') {
|
|
272
356
|
badge.className = 'status-badge status-idle';
|
|
273
357
|
badge.textContent = '...';
|
|
358
|
+
body.className = 'response-body empty';
|
|
274
359
|
body.innerHTML = 'Executing transmission...';
|
|
275
360
|
return;
|
|
276
361
|
}
|
|
@@ -278,7 +363,10 @@ function setResponse(data, state, status, ms) {
|
|
|
278
363
|
badge.className = 'status-badge ' + (state === 'ok' ? 'status-ok' : 'status-err');
|
|
279
364
|
badge.textContent = status + ' · ' + ms + 'ms';
|
|
280
365
|
body.className = 'response-body';
|
|
281
|
-
|
|
366
|
+
|
|
367
|
+
// FIXED: Nested rendering inside dedicated horizontal scrolling code tag block wrapper elements
|
|
368
|
+
const outputString = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
369
|
+
body.innerHTML = '<pre class="json-render-block">' + highlightJson(outputString) + '</pre>';
|
|
282
370
|
}
|
|
283
371
|
|
|
284
372
|
function clearResponse() {
|
package/htmlTemplate.js
CHANGED
|
@@ -33,7 +33,7 @@ export function getHtmlTemplate(endpoints) {
|
|
|
33
33
|
font-family: 'DM Sans', sans-serif;
|
|
34
34
|
font-size: 14px;
|
|
35
35
|
height: 100vh;
|
|
36
|
-
overflow: hidden;
|
|
36
|
+
overflow: hidden;
|
|
37
37
|
background-image: radial-gradient(ellipse 80% 60% at 50% -20%, #3a2a0a22 0%, transparent 70%);
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -59,7 +59,6 @@ export function getHtmlTemplate(endpoints) {
|
|
|
59
59
|
font-family: 'DM Mono', monospace; font-size: 12px; padding: 6px 12px; border-radius: var(--radius); width: 220px; outline: none;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/* Fixed view height viewport matrix layout grid rules */
|
|
63
62
|
.layout {
|
|
64
63
|
display: grid;
|
|
65
64
|
grid-template-columns: 280px 1fr 450px;
|
|
@@ -116,7 +115,6 @@ export function getHtmlTemplate(endpoints) {
|
|
|
116
115
|
.btn-secondary { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); }
|
|
117
116
|
.btn-secondary:hover { color: var(--text); background: var(--surface); }
|
|
118
117
|
|
|
119
|
-
/* FIXED: Response block scroll logic layout rules */
|
|
120
118
|
.response-panel {
|
|
121
119
|
border-left: 1px solid var(--border);
|
|
122
120
|
display: flex;
|
|
@@ -131,7 +129,6 @@ export function getHtmlTemplate(endpoints) {
|
|
|
131
129
|
.status-err { background: #3a1a14; color: #d45c3c; }
|
|
132
130
|
.status-idle { background: var(--surface2); color: var(--text-dim); }
|
|
133
131
|
|
|
134
|
-
/* FIXED: Body panel scrolls y natively, inner token element handles x scrolling */
|
|
135
132
|
.response-body {
|
|
136
133
|
flex: 1;
|
|
137
134
|
overflow-y: auto;
|
|
@@ -149,7 +146,6 @@ export function getHtmlTemplate(endpoints) {
|
|
|
149
146
|
font-size: 13px;
|
|
150
147
|
}
|
|
151
148
|
|
|
152
|
-
/* FIXED: Token code output container handles micro horizontal data flows elegantly */
|
|
153
149
|
.json-render-block {
|
|
154
150
|
display: block;
|
|
155
151
|
padding: 20px;
|
|
@@ -240,34 +236,30 @@ function buildSidebar() {
|
|
|
240
236
|
if (keys.length > 0) renderPanel(keys[0]);
|
|
241
237
|
}
|
|
242
238
|
|
|
243
|
-
function makeInputString(type, id, placeholder) {
|
|
244
|
-
const pAttr = placeholder ? ' placeholder="' + placeholder + '"' : '';
|
|
245
|
-
return '<input type="' + type + '" id="' + id + '"' + pAttr + ' />';
|
|
246
|
-
}
|
|
247
|
-
|
|
248
239
|
function renderPanel(epKey) {
|
|
249
240
|
currentEp = epKey;
|
|
250
241
|
const ep = ENDPOINTS[epKey];
|
|
251
242
|
const main = document.getElementById('main-panel');
|
|
252
243
|
if (!ep) return;
|
|
253
244
|
|
|
245
|
+
// Escaping using triple backslashes ensures client variables evaluate properly at runtime
|
|
254
246
|
let html = \`
|
|
255
|
-
<div class="endpoint-title"
|
|
247
|
+
<div class="endpoint-title">\\\${ep.title}</div>
|
|
256
248
|
<div class="endpoint-path">
|
|
257
|
-
<span class="method-badge
|
|
258
|
-
<span
|
|
249
|
+
<span class="method-badge \\\${ep.method}">\\\${ep.method}</span>
|
|
250
|
+
<span>\\\${ep.path}</span>
|
|
259
251
|
</div>
|
|
260
|
-
<div class="endpoint-desc"
|
|
252
|
+
<div class="endpoint-desc">\\\${ep.desc}</div>
|
|
261
253
|
\`;
|
|
262
254
|
|
|
263
255
|
if (ep.params && ep.params.length) {
|
|
264
256
|
html += \`<div class="form-section"><div class="form-section-title">Path Parameters</div>\`;
|
|
265
257
|
ep.params.forEach(function(p) {
|
|
266
|
-
const
|
|
258
|
+
const ph = p.placeholder ? ' placeholder="' + p.placeholder + '"' : '';
|
|
267
259
|
html += \`
|
|
268
260
|
<div class="field-row">
|
|
269
|
-
<label class="field-label"
|
|
270
|
-
\${
|
|
261
|
+
<label class="field-label">\\\${p.label}</label>
|
|
262
|
+
<input type="text" id="param-\\\${p.name}" \${ph} />
|
|
271
263
|
</div>
|
|
272
264
|
\`;
|
|
273
265
|
});
|
|
@@ -277,11 +269,12 @@ function renderPanel(epKey) {
|
|
|
277
269
|
if (ep.fields && ep.fields.length) {
|
|
278
270
|
html += \`<div class="form-section"><div class="form-section-title">HTTP JSON Request Payload Parameters</div>\`;
|
|
279
271
|
ep.fields.forEach(function(f) {
|
|
280
|
-
const
|
|
272
|
+
const type = f.type || 'text';
|
|
273
|
+
const ph = f.placeholder ? ' placeholder="' + f.placeholder + '"' : '';
|
|
281
274
|
html += \`
|
|
282
275
|
<div class="field-row">
|
|
283
|
-
<label class="field-label"
|
|
284
|
-
\${
|
|
276
|
+
<label class="field-label">\\\${f.label}</label>
|
|
277
|
+
<input type="\${type}" id="field-\\\${f.name}" \${ph} />
|
|
285
278
|
</div>
|
|
286
279
|
\`;
|
|
287
280
|
});
|
|
@@ -364,7 +357,6 @@ function setResponse(data, state, status, ms) {
|
|
|
364
357
|
badge.textContent = status + ' · ' + ms + 'ms';
|
|
365
358
|
body.className = 'response-body';
|
|
366
359
|
|
|
367
|
-
// FIXED: Nested rendering inside dedicated horizontal scrolling code tag block wrapper elements
|
|
368
360
|
const outputString = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
369
361
|
body.innerHTML = '<pre class="json-render-block">' + highlightJson(outputString) + '</pre>';
|
|
370
362
|
}
|
|
@@ -378,10 +370,11 @@ function clearResponse() {
|
|
|
378
370
|
body.textContent = 'Execute a request row to generate feedback data';
|
|
379
371
|
}
|
|
380
372
|
|
|
373
|
+
// Full syntax highlighting for JSON blocks
|
|
381
374
|
function highlightJson(str) {
|
|
382
375
|
return str
|
|
383
376
|
.replace(/&/g, '&').replace(/[<]/g, '<').replace(/[>]/g, '>')
|
|
384
|
-
.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)
|
|
377
|
+
.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g, function(match) {
|
|
385
378
|
if (/^"/.test(match)) {
|
|
386
379
|
if (/:$/.test(match)) return '<span class="json-key">' + match + '</span>';
|
|
387
380
|
return '<span class="json-str">' + match + '</span>';
|
package/index.js
CHANGED
|
@@ -54,23 +54,49 @@ export function endtesterExpress() {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// =========================
|
|
57
|
-
//
|
|
57
|
+
// Context-Aware Static Fallbacks
|
|
58
|
+
// =========================
|
|
59
|
+
function getFallbackFieldsForPath(path) {
|
|
60
|
+
const lowerPath = path.toLowerCase();
|
|
61
|
+
let rawFields = [];
|
|
62
|
+
|
|
63
|
+
if (lowerPath.includes('login') || lowerPath.includes('auth') || lowerPath.includes('signin')) {
|
|
64
|
+
rawFields = ['email', 'password'];
|
|
65
|
+
} else if (lowerPath.includes('user') || lowerPath.includes('register') || lowerPath.includes('signup')) {
|
|
66
|
+
rawFields = ['username', 'email', 'password'];
|
|
67
|
+
} else if (lowerPath.includes('product')) {
|
|
68
|
+
rawFields = ['name', 'price', 'stock'];
|
|
69
|
+
} else {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return rawFields.map(field => ({
|
|
74
|
+
name: field,
|
|
75
|
+
label: field.charAt(0).toUpperCase() + field.slice(1),
|
|
76
|
+
type: detectInputType(field),
|
|
77
|
+
placeholder: `Enter ${field}`
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// =========================
|
|
82
|
+
// Extract req.body fields via Source Inspection
|
|
58
83
|
// =========================
|
|
59
84
|
function extractBodyFields(handler) {
|
|
60
85
|
try {
|
|
61
86
|
const source = handler.toString();
|
|
87
|
+
|
|
88
|
+
// If it's a bound handler or lacks source reference code text
|
|
89
|
+
if (!source || source.includes('[native code]')) return [];
|
|
62
90
|
|
|
63
|
-
// FIXED: The 's' flag allows matching across multiple lines (\n)
|
|
64
91
|
const regex = /(const|let|var)\s*\{\s*([^}]+)\s*\}\s*=\s*req\.body/gs;
|
|
65
92
|
const matches = [...source.matchAll(regex)];
|
|
66
93
|
const fields = [];
|
|
67
94
|
|
|
68
95
|
matches.forEach((match) => {
|
|
69
|
-
// Clean out formatting line-breaks, tabs, or rogue inner code comments
|
|
70
96
|
const cleanedVariablesBlock = match[2]
|
|
71
|
-
.replace(/\/\/.*$/gm, '')
|
|
72
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
73
|
-
.replace(/[\r\n\t]/g, ' ');
|
|
97
|
+
.replace(/\/\/.*$/gm, '')
|
|
98
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
99
|
+
.replace(/[\r\n\t]/g, ' ');
|
|
74
100
|
|
|
75
101
|
const variables = cleanedVariablesBlock
|
|
76
102
|
.split(',')
|
|
@@ -80,29 +106,22 @@ export function endtesterExpress() {
|
|
|
80
106
|
variables.forEach((field) => {
|
|
81
107
|
let realField = field;
|
|
82
108
|
|
|
83
|
-
// Support aliases (e.g., name: username)
|
|
84
109
|
if (field.includes(':')) {
|
|
85
110
|
realField = field.split(':')[0].trim();
|
|
86
111
|
}
|
|
87
112
|
|
|
88
|
-
// Remove defaults values (e.g., age = 0)
|
|
89
113
|
if (realField.includes('=')) {
|
|
90
114
|
realField = realField.split('=')[0].trim();
|
|
91
115
|
}
|
|
92
116
|
|
|
93
|
-
// Clean any trailing whitespace variations
|
|
94
117
|
realField = realField.trim();
|
|
95
118
|
|
|
96
|
-
const alreadyExists = fields.find(
|
|
97
|
-
f => f.name === realField
|
|
98
|
-
);
|
|
119
|
+
const alreadyExists = fields.find(f => f.name === realField);
|
|
99
120
|
|
|
100
121
|
if (!alreadyExists && realField) {
|
|
101
122
|
fields.push({
|
|
102
123
|
name: realField,
|
|
103
|
-
label:
|
|
104
|
-
realField.charAt(0).toUpperCase() +
|
|
105
|
-
realField.slice(1),
|
|
124
|
+
label: realField.charAt(0).toUpperCase() + realField.slice(1),
|
|
106
125
|
type: detectInputType(realField),
|
|
107
126
|
placeholder: `Enter ${realField}`
|
|
108
127
|
});
|
|
@@ -128,13 +147,8 @@ export function endtesterExpress() {
|
|
|
128
147
|
// ROUTES
|
|
129
148
|
// =========================
|
|
130
149
|
if (layer.route) {
|
|
131
|
-
const methods = Object.keys(
|
|
132
|
-
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const path = (
|
|
136
|
-
prefix + layer.route.path
|
|
137
|
-
).replace(/\/+/g, '/');
|
|
150
|
+
const methods = Object.keys(layer.route.methods);
|
|
151
|
+
const path = (prefix + layer.route.path).replace(/\/+/g, '/');
|
|
138
152
|
|
|
139
153
|
if (path.includes('/api/tester')) {
|
|
140
154
|
return;
|
|
@@ -142,14 +156,8 @@ export function endtesterExpress() {
|
|
|
142
156
|
|
|
143
157
|
methods.forEach((method) => {
|
|
144
158
|
const httpMethod = method.toUpperCase();
|
|
159
|
+
const key = `${httpMethod.toLowerCase()}-` + path.replace(/[^a-zA-Z0-9]/g, '-');
|
|
145
160
|
|
|
146
|
-
const key =
|
|
147
|
-
`${httpMethod.toLowerCase()}-` +
|
|
148
|
-
path.replace(/[^a-zA-Z0-9]/g, '-');
|
|
149
|
-
|
|
150
|
-
// =========================
|
|
151
|
-
// PATH PARAMS
|
|
152
|
-
// =========================
|
|
153
161
|
const pathParams = layer.route.keys
|
|
154
162
|
? layer.route.keys.map((k) => ({
|
|
155
163
|
name: k.name,
|
|
@@ -159,37 +167,27 @@ export function endtesterExpress() {
|
|
|
159
167
|
: [];
|
|
160
168
|
|
|
161
169
|
// =========================
|
|
162
|
-
// BODY FIELDS
|
|
170
|
+
// BODY FIELDS COMPILING
|
|
163
171
|
// =========================
|
|
164
172
|
let bodyFields = [];
|
|
165
173
|
|
|
166
|
-
if (
|
|
167
|
-
['POST', 'PUT', 'PATCH'].includes(
|
|
168
|
-
httpMethod
|
|
169
|
-
)
|
|
170
|
-
) {
|
|
174
|
+
if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
|
|
171
175
|
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
|
-
|
|
176
|
+
if (stackLayer.handle && typeof stackLayer.handle === 'function') {
|
|
177
|
+
const extractedFields = extractBodyFields(stackLayer.handle);
|
|
181
178
|
bodyFields.push(...extractedFields);
|
|
182
179
|
}
|
|
183
180
|
});
|
|
184
181
|
|
|
185
|
-
//
|
|
182
|
+
// Deduplicate discovered elements
|
|
186
183
|
bodyFields = bodyFields.filter(
|
|
187
|
-
(field, index, self) =>
|
|
188
|
-
index ===
|
|
189
|
-
self.findIndex(
|
|
190
|
-
f => f.name === field.name
|
|
191
|
-
)
|
|
184
|
+
(field, index, self) => index === self.findIndex(f => f.name === field.name)
|
|
192
185
|
);
|
|
186
|
+
|
|
187
|
+
// CRITICAL FAILSAFE: If code reflection extracted nothing, apply smart path-based fallback fields
|
|
188
|
+
if (bodyFields.length === 0) {
|
|
189
|
+
bodyFields = getFallbackFieldsForPath(path);
|
|
190
|
+
}
|
|
193
191
|
}
|
|
194
192
|
|
|
195
193
|
detectedEndpoints[key] = {
|
|
@@ -219,10 +217,7 @@ export function endtesterExpress() {
|
|
|
219
217
|
.match(/^\/\^\\(.*?)\\\/\?/);
|
|
220
218
|
|
|
221
219
|
if (match && match[1]) {
|
|
222
|
-
routerPath = match[1].replace(
|
|
223
|
-
/\\/g,
|
|
224
|
-
''
|
|
225
|
-
);
|
|
220
|
+
routerPath = match[1].replace(/\\/g, '');
|
|
226
221
|
}
|
|
227
222
|
}
|
|
228
223
|
|
|
@@ -237,24 +232,15 @@ export function endtesterExpress() {
|
|
|
237
232
|
// =========================
|
|
238
233
|
// START PARSING
|
|
239
234
|
// =========================
|
|
240
|
-
if (
|
|
241
|
-
expressApp._router &&
|
|
242
|
-
expressApp._router.stack
|
|
243
|
-
) {
|
|
235
|
+
if (expressApp._router && expressApp._router.stack) {
|
|
244
236
|
parseStack(expressApp._router.stack);
|
|
245
237
|
}
|
|
246
238
|
|
|
247
239
|
// =========================
|
|
248
240
|
// RENDER HTML
|
|
249
241
|
// =========================
|
|
250
|
-
const fullHtml =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
res.setHeader(
|
|
254
|
-
'Content-Type',
|
|
255
|
-
'text/html'
|
|
256
|
-
);
|
|
257
|
-
|
|
242
|
+
const fullHtml = getHtmlTemplate(detectedEndpoints);
|
|
243
|
+
res.setHeader('Content-Type', 'text/html');
|
|
258
244
|
return res.send(fullHtml);
|
|
259
245
|
};
|
|
260
246
|
}
|