@aimeloic/monkey-tester 2.0.0 → 2.0.2
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 +317 -0
- package/htmlTemplate.js +140 -52
- package/index.js +216 -40
- package/package.json +1 -1
package/html.backup.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
export function getHtmlTemplate(endpoints) {
|
|
2
|
+
const safeJsonString = Buffer.from(JSON.stringify(endpoints)).toString('base64');
|
|
3
|
+
|
|
4
|
+
return `
|
|
5
|
+
<!DOCTYPE html>
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="UTF-8">
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
10
|
+
<title>Endtester — API Environment</title>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet">
|
|
12
|
+
<style>
|
|
13
|
+
:root {
|
|
14
|
+
--bg: #0e0c09;
|
|
15
|
+
--surface: #181510;
|
|
16
|
+
--surface2: #221d14;
|
|
17
|
+
--border: #3a3020;
|
|
18
|
+
--accent: #e8a838;
|
|
19
|
+
--accent2: #c47a1e;
|
|
20
|
+
--text: #f0e8d8;
|
|
21
|
+
--text-dim: #9a8c78;
|
|
22
|
+
--red: #d45c3c;
|
|
23
|
+
--green: #6ba05a;
|
|
24
|
+
--blue: #5a86c0;
|
|
25
|
+
--radius: 8px;
|
|
26
|
+
}
|
|
27
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
28
|
+
body {
|
|
29
|
+
background: var(--bg); color: var(--text); font-family: 'DM Sans', sans-serif; font-size: 14px; min-height: 100vh;
|
|
30
|
+
background-image: radial-gradient(ellipse 80% 60% at 50% -20%, #3a2a0a22 0%, transparent 70%);
|
|
31
|
+
}
|
|
32
|
+
header {
|
|
33
|
+
border-bottom: 1px solid var(--border); padding: 20px 32px; display: flex; align-items: center; gap: 20px;
|
|
34
|
+
background: #0e0c09ee; backdrop-filter: blur(8px); position: sticky; top: 0; z-index: 100;
|
|
35
|
+
}
|
|
36
|
+
.logo { font-family: 'Playfair Display', serif; font-size: 22px; color: var(--accent); letter-spacing: 0.02em; }
|
|
37
|
+
.logo span { color: var(--text-dim); font-size: 13px; font-family: 'DM Mono', monospace; display: block; font-weight: 400; }
|
|
38
|
+
.header-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
|
|
39
|
+
.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: 12px; font-family: 'DM Mono', monospace; }
|
|
41
|
+
#jwt-input, #base-url {
|
|
42
|
+
background: var(--surface2); border: 1px solid var(--border); color: var(--text);
|
|
43
|
+
font-family: 'DM Mono', monospace; font-size: 11px; padding: 6px 10px; border-radius: var(--radius); width: 220px; outline: none;
|
|
44
|
+
}
|
|
45
|
+
.layout { display: grid; grid-template-columns: 260px 1fr 400px; height: calc(100vh - 65px); }
|
|
46
|
+
aside { border-right: 1px solid var(--border); overflow-y: auto; padding: 16px 0; }
|
|
47
|
+
.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
|
+
.nav-item { display: flex; align-items: center; gap: 10px; padding: 8px 18px; cursor: pointer; border-left: 2px solid transparent; color: var(--text-dim); }
|
|
49
|
+
.nav-item:hover { background: var(--surface); color: var(--text); }
|
|
50
|
+
.nav-item.active { border-left-color: var(--accent); background: var(--surface); color: var(--accent); }
|
|
51
|
+
.method-badge { font-family: 'DM Mono', monospace; font-size: 9px; font-weight: 500; padding: 2px 5px; border-radius: 3px; min-width: 45px; text-align: center; }
|
|
52
|
+
.GET { background: #1a3a22; color: #6ba05a; }
|
|
53
|
+
.POST { background: #1a2e3a; color: #5a86c0; }
|
|
54
|
+
.PUT, .PATCH { background: #3a2e10; color: #e8a838; }
|
|
55
|
+
.DELETE { background: #3a1a14; color: #d45c3c; }
|
|
56
|
+
.nav-label { font-size: 11px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
57
|
+
main { overflow-y: auto; padding: 24px; }
|
|
58
|
+
.endpoint-title { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--accent); margin-bottom: 6px; }
|
|
59
|
+
.endpoint-path { font-family: 'DM Mono', monospace; font-size: 12px; color: var(--text-dim); margin-bottom: 20px; display: flex; align-items: center; gap: 8px; }
|
|
60
|
+
.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
|
+
.form-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; margin-bottom: 16px; }
|
|
62
|
+
.form-section-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 14px; }
|
|
63
|
+
.field-row { display: grid; grid-template-columns: 140px 1fr; align-items: center; gap: 10px; margin-bottom: 10px; }
|
|
64
|
+
.field-label { font-family: 'DM Mono', monospace; font-size: 11px; color: var(--text-dim); text-align: right; }
|
|
65
|
+
input[type=text], input[type=number], select { background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-family: 'DM Mono', monospace; font-size: 12px; padding: 7px 10px; border-radius: var(--radius); width: 100%; outline: none; }
|
|
66
|
+
.btn { background: var(--accent); color: #0e0c09; border: none; padding: 10px 22px; border-radius: var(--radius); font-size: 13px; font-weight: 500; cursor: pointer; }
|
|
67
|
+
.btn-row { margin-top: 20px; display: flex; gap: 10px; }
|
|
68
|
+
.btn-secondary { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); }
|
|
69
|
+
.response-panel { border-left: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; }
|
|
70
|
+
.response-header { padding: 14px 18px; border-bottom: 1px solid var(--border); display: flex; align-items: center; background: var(--surface); }
|
|
71
|
+
.response-header-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; }
|
|
72
|
+
.status-badge { font-family: 'DM Mono', monospace; font-size: 12px; margin-left: auto; padding: 2px 8px; border-radius: 4px; }
|
|
73
|
+
.status-ok { background: #1a3a22; color: #6ba05a; }
|
|
74
|
+
.status-err { background: #3a1a14; color: #d45c3c; }
|
|
75
|
+
.status-idle { background: var(--surface2); color: var(--text-dim); }
|
|
76
|
+
.response-body { flex: 1; overflow-y: auto; padding: 16px; font-family: 'DM Mono', monospace; font-size: 12px; white-space: pre-wrap; word-break: break-all; }
|
|
77
|
+
.response-body.empty { color: var(--text-dim); display: flex; align-items: center; justify-content: center; }
|
|
78
|
+
.json-key { color: #e8a838; } .json-str { color: #9ab878; } .json-num { color: #5a86c0; }
|
|
79
|
+
#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; }
|
|
80
|
+
#toast.show { opacity: 1; }
|
|
81
|
+
</style>
|
|
82
|
+
</head>
|
|
83
|
+
<body>
|
|
84
|
+
|
|
85
|
+
<div id="endtester-data-vault" data-payload="${safeJsonString}" style="display: none;"></div>
|
|
86
|
+
|
|
87
|
+
<header>
|
|
88
|
+
<div class="logo">Endtester <span>Application Runtime Sandbox</span></div>
|
|
89
|
+
<div class="header-right">
|
|
90
|
+
<div class="base-url-wrap">
|
|
91
|
+
<label>TARGET HOST</label>
|
|
92
|
+
<input id="base-url" type="text" value="">
|
|
93
|
+
</div>
|
|
94
|
+
<div class="jwt-wrap">
|
|
95
|
+
<label>BEARER AUTH</label>
|
|
96
|
+
<input id="jwt-input" type="text" placeholder="Token value...">
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</header>
|
|
100
|
+
|
|
101
|
+
<div class="layout">
|
|
102
|
+
<aside id="sidebar-nav">
|
|
103
|
+
<div class="section-label">Discovered Endpoints</div>
|
|
104
|
+
</aside>
|
|
105
|
+
<main id="main-panel"></main>
|
|
106
|
+
<div class="response-panel">
|
|
107
|
+
<div class="response-header">
|
|
108
|
+
<span class="response-header-title">Response Output</span>
|
|
109
|
+
<span id="status-badge" class="status-badge status-idle">—</span>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="response-body empty" id="response-body">Execute a request row to generate feedback data</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div id="toast"></div>
|
|
116
|
+
|
|
117
|
+
<script>
|
|
118
|
+
const rawPayload = document.getElementById('endtester-data-vault').getAttribute('data-payload');
|
|
119
|
+
const ENDPOINTS = JSON.parse(atob(rawPayload));
|
|
120
|
+
|
|
121
|
+
let currentEp = '';
|
|
122
|
+
|
|
123
|
+
document.getElementById('base-url').value = window.location.origin;
|
|
124
|
+
|
|
125
|
+
function buildSidebar() {
|
|
126
|
+
const sidebar = document.getElementById('sidebar-nav');
|
|
127
|
+
const keys = Object.keys(ENDPOINTS);
|
|
128
|
+
|
|
129
|
+
if (keys.length === 0) {
|
|
130
|
+
sidebar.innerHTML += '<div style="padding:15px; color:var(--text-dim)">No active application endpoints discovered.</div>';
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
keys.forEach((key, index) => {
|
|
135
|
+
const ep = ENDPOINTS[key];
|
|
136
|
+
const div = document.createElement('div');
|
|
137
|
+
div.className = index === 0 ? 'nav-item active' : 'nav-item';
|
|
138
|
+
div.setAttribute('data-ep', key);
|
|
139
|
+
div.innerHTML = '<span class="method-badge ' + ep.method + '">' + ep.method + '</span><span class="nav-label">' + ep.path + '</span>';
|
|
140
|
+
div.addEventListener('click', () => {
|
|
141
|
+
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
142
|
+
div.classList.add('active');
|
|
143
|
+
clearResponse();
|
|
144
|
+
renderPanel(key);
|
|
145
|
+
});
|
|
146
|
+
sidebar.appendChild(div);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (keys.length > 0) renderPanel(keys[0]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function makeInputString(type, id, placeholder, defaultValue) {
|
|
153
|
+
const pAttr = placeholder ? ' placeholder="' + placeholder + '"' : '';
|
|
154
|
+
const vAttr = defaultValue !== undefined ? ' value="' + defaultValue + '"' : '';
|
|
155
|
+
return '<input type="' + type + '" id="' + id + '"' + pAttr + vAttr + ' />';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function renderPanel(epKey) {
|
|
159
|
+
currentEp = epKey;
|
|
160
|
+
const ep = ENDPOINTS[epKey];
|
|
161
|
+
const main = document.getElementById('main-panel');
|
|
162
|
+
if (!ep) return;
|
|
163
|
+
|
|
164
|
+
let html = \`
|
|
165
|
+
<div class="endpoint-title">\${ep.title}</div>
|
|
166
|
+
<div class="endpoint-path">
|
|
167
|
+
<span class="method-badge \${ep.method}">\${ep.method}</span>
|
|
168
|
+
<span>\${ep.path}</span>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="endpoint-desc">\${ep.desc}</div>
|
|
171
|
+
\`;
|
|
172
|
+
|
|
173
|
+
// Path Parameters
|
|
174
|
+
if (ep.params && ep.params.length) {
|
|
175
|
+
html += \`<div class="form-section"><div class="form-section-title">Path Parameters</div>\`;
|
|
176
|
+
ep.params.forEach(function(p) {
|
|
177
|
+
const inputHtml = makeInputString('text', 'param-' + p.name, p.placeholder, '');
|
|
178
|
+
html += \`
|
|
179
|
+
<div class="field-row">
|
|
180
|
+
<label class="field-label">\${p.label}</label>
|
|
181
|
+
\${inputHtml}
|
|
182
|
+
</div>
|
|
183
|
+
\`;
|
|
184
|
+
});
|
|
185
|
+
html += \`</div>\`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// FIXED: Renders native, individual form fields instead of a single text payload editor block
|
|
189
|
+
if (ep.fields && ep.fields.length) {
|
|
190
|
+
html += \`<div class="form-section"><div class="form-section-title">HTTP JSON Request Payload Parameters</div>\`;
|
|
191
|
+
ep.fields.forEach(function(f) {
|
|
192
|
+
const inputHtml = makeInputString(f.type || 'text', 'field-' + f.name, f.placeholder || '', '');
|
|
193
|
+
html += \`
|
|
194
|
+
<div class="field-row">
|
|
195
|
+
<label class="field-label">\--\${f.label}</label>
|
|
196
|
+
\${inputHtml}
|
|
197
|
+
</div>
|
|
198
|
+
\`;
|
|
199
|
+
});
|
|
200
|
+
html += \`</div>\`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
html += \`
|
|
204
|
+
<div class="btn-row">
|
|
205
|
+
<button class="btn" onclick="sendRequest()">Execute Route</button>
|
|
206
|
+
<button class="btn btn-secondary" onclick="clearResponse()">Clear Context</button>
|
|
207
|
+
</div>
|
|
208
|
+
\`;
|
|
209
|
+
|
|
210
|
+
main.innerHTML = html;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function sendRequest() {
|
|
214
|
+
const ep = ENDPOINTS[currentEp];
|
|
215
|
+
let path = ep.path;
|
|
216
|
+
|
|
217
|
+
// Compile URL path parameter tags
|
|
218
|
+
if (ep.params) {
|
|
219
|
+
for (const p of ep.params) {
|
|
220
|
+
const val = document.getElementById('param-' + p.name)?.value.trim();
|
|
221
|
+
if (!val) { showToast('Warning: Parameter ' + p.label + ' is required'); return; }
|
|
222
|
+
path = path.replace(':' + p.name, encodeURIComponent(val));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const baseUrl = document.getElementById('base-url').value.replace(/[/]+$/, '');
|
|
227
|
+
const url = baseUrl + path;
|
|
228
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
229
|
+
|
|
230
|
+
const jwt = document.getElementById('jwt-input').value.trim();
|
|
231
|
+
if (jwt) headers['Authorization'] = 'Bearer ' + jwt;
|
|
232
|
+
|
|
233
|
+
// FIXED: Dynamically bundles inputs into a background payload object structure seamlessly
|
|
234
|
+
let body = undefined;
|
|
235
|
+
if (ep.fields && ep.fields.length && ['POST', 'PUT', 'PATCH'].includes(ep.method)) {
|
|
236
|
+
const payloadObject = {};
|
|
237
|
+
|
|
238
|
+
for (const f of ep.fields) {
|
|
239
|
+
const inputEl = document.getElementById('field-' + f.name);
|
|
240
|
+
if (inputEl) {
|
|
241
|
+
let value = inputEl.value.trim();
|
|
242
|
+
// Cast numerical parameters to prevent type validation parsing failures
|
|
243
|
+
if (f.type === 'number' && value !== '') {
|
|
244
|
+
value = Number(value);
|
|
245
|
+
}
|
|
246
|
+
payloadObject[f.name] = value;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
body = JSON.stringify(payloadObject);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
setResponse(null, 'loading');
|
|
253
|
+
const start = Date.now();
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const res = await fetch(url, { method: ep.method, headers, body });
|
|
257
|
+
const ms = Date.now() - start;
|
|
258
|
+
const text = await res.text();
|
|
259
|
+
let json;
|
|
260
|
+
try { json = JSON.parse(text); } catch { json = text; }
|
|
261
|
+
setResponse(json, res.ok ? 'ok' : 'err', res.status, ms);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
setResponse({ error: err.message }, 'err', 'FAIL', 0);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function setResponse(data, state, status, ms) {
|
|
268
|
+
const badge = document.getElementById('status-badge');
|
|
269
|
+
const body = document.getElementById('response-body');
|
|
270
|
+
|
|
271
|
+
if (state === 'loading') {
|
|
272
|
+
badge.className = 'status-badge status-idle';
|
|
273
|
+
badge.textContent = '...';
|
|
274
|
+
body.innerHTML = 'Executing transmission...';
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
badge.className = 'status-badge ' + (state === 'ok' ? 'status-ok' : 'status-err');
|
|
279
|
+
badge.textContent = status + ' · ' + ms + 'ms';
|
|
280
|
+
body.className = 'response-body';
|
|
281
|
+
body.innerHTML = highlightJson(typeof data === 'string' ? data : JSON.stringify(data, null, 2));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function clearResponse() {
|
|
285
|
+
const badge = document.getElementById('status-badge');
|
|
286
|
+
const body = document.getElementById('response-body');
|
|
287
|
+
badge.className = 'status-badge status-idle';
|
|
288
|
+
badge.textContent = '—';
|
|
289
|
+
body.className = 'response-body empty';
|
|
290
|
+
body.textContent = 'Execute a request row to generate feedback data';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function highlightJson(str) {
|
|
294
|
+
return str
|
|
295
|
+
.replace(/&/g, '&').replace(/[<]/g, '<').replace(/[>]/g, '>')
|
|
296
|
+
.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function(match) {
|
|
297
|
+
if (/^"/.test(match)) {
|
|
298
|
+
if (/:$/.test(match)) return '<span class="json-key">' + match + '</span>';
|
|
299
|
+
return '<span class="json-str">' + match + '</span>';
|
|
300
|
+
}
|
|
301
|
+
return '<span class="json-num">' + match + '</span>';
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function showToast(msg) {
|
|
306
|
+
const t = document.getElementById('toast');
|
|
307
|
+
t.textContent = msg;
|
|
308
|
+
t.classList.add('show');
|
|
309
|
+
setTimeout(() => t.classList.remove('show'), 2500);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
buildSidebar();
|
|
313
|
+
</script>
|
|
314
|
+
</body>
|
|
315
|
+
</html>
|
|
316
|
+
`;
|
|
317
|
+
}
|
package/htmlTemplate.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/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 (
|
|
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
|
+
if (lower.includes('password')) return 'password';
|
|
23
|
+
if (lower.includes('date')) return 'date';
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
lower.includes('age') ||
|
|
27
|
+
lower.includes('price') ||
|
|
28
|
+
lower.includes('salary') ||
|
|
29
|
+
lower.includes('stock') ||
|
|
30
|
+
lower.includes('quantity') ||
|
|
31
|
+
lower.includes('count') ||
|
|
32
|
+
lower.includes('total') ||
|
|
33
|
+
lower.includes('amount') ||
|
|
34
|
+
lower.includes('id')
|
|
35
|
+
) {
|
|
36
|
+
return 'number';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
lower.includes('phone') ||
|
|
41
|
+
lower.includes('tel')
|
|
42
|
+
) {
|
|
43
|
+
return 'tel';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (
|
|
47
|
+
lower.includes('url') ||
|
|
48
|
+
lower.includes('website')
|
|
49
|
+
) {
|
|
50
|
+
return 'url';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return 'text';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// =========================
|
|
57
|
+
// Extract req.body fields (FIXED MULTI-LINE)
|
|
58
|
+
// =========================
|
|
59
|
+
function extractBodyFields(handler) {
|
|
60
|
+
try {
|
|
61
|
+
const source = handler.toString();
|
|
62
|
+
|
|
63
|
+
// FIXED: The 's' flag allows matching across multiple lines (\n)
|
|
64
|
+
const regex = /(const|let|var)\s*\{\s*([^}]+)\s*\}\s*=\s*req\.body/gs;
|
|
65
|
+
const matches = [...source.matchAll(regex)];
|
|
66
|
+
const fields = [];
|
|
67
|
+
|
|
68
|
+
matches.forEach((match) => {
|
|
69
|
+
// Clean out formatting line-breaks, tabs, or rogue inner code comments
|
|
70
|
+
const cleanedVariablesBlock = match[2]
|
|
71
|
+
.replace(/\/\/.*$/gm, '') // strip inline comments
|
|
72
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // strip block comments
|
|
73
|
+
.replace(/[\r\n\t]/g, ' '); // normalize lines to spaces
|
|
74
|
+
|
|
75
|
+
const variables = cleanedVariablesBlock
|
|
76
|
+
.split(',')
|
|
77
|
+
.map(v => v.trim())
|
|
78
|
+
.filter(Boolean);
|
|
79
|
+
|
|
80
|
+
variables.forEach((field) => {
|
|
81
|
+
let realField = field;
|
|
82
|
+
|
|
83
|
+
// Support aliases (e.g., name: username)
|
|
84
|
+
if (field.includes(':')) {
|
|
85
|
+
realField = field.split(':')[0].trim();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Remove defaults values (e.g., age = 0)
|
|
89
|
+
if (realField.includes('=')) {
|
|
90
|
+
realField = realField.split('=')[0].trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Clean any trailing whitespace variations
|
|
94
|
+
realField = realField.trim();
|
|
95
|
+
|
|
96
|
+
const alreadyExists = fields.find(
|
|
97
|
+
f => f.name === realField
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (!alreadyExists && realField) {
|
|
101
|
+
fields.push({
|
|
102
|
+
name: realField,
|
|
103
|
+
label:
|
|
104
|
+
realField.charAt(0).toUpperCase() +
|
|
105
|
+
realField.slice(1),
|
|
106
|
+
type: detectInputType(realField),
|
|
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(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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
|
-
}
|
|
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
|
|
217
|
+
const match = layer.regexp
|
|
218
|
+
.toString()
|
|
219
|
+
.match(/^\/\^\\(.*?)\\\/\?/);
|
|
220
|
+
|
|
67
221
|
if (match && match[1]) {
|
|
68
|
-
routerPath = match[1].replace(
|
|
222
|
+
routerPath = match[1].replace(
|
|
223
|
+
/\\/g,
|
|
224
|
+
''
|
|
225
|
+
);
|
|
69
226
|
}
|
|
70
227
|
}
|
|
71
|
-
|
|
228
|
+
|
|
229
|
+
parseStack(
|
|
230
|
+
layer.handle.stack,
|
|
231
|
+
prefix + '/' + routerPath
|
|
232
|
+
);
|
|
72
233
|
}
|
|
73
234
|
});
|
|
74
235
|
}
|
|
75
236
|
|
|
76
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
}
|