free-framework 4.5.8 → 4.6.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/package.json +1 -1
- package/runtime/server.js +57 -5
- package/runtime/views/debug.html +273 -0
package/package.json
CHANGED
package/runtime/server.js
CHANGED
|
@@ -21,9 +21,9 @@ class FreeServer {
|
|
|
21
21
|
this.plugins = [];
|
|
22
22
|
this.viewsPath = process.env.VIEWS_PATH || nodePath.join(process.cwd(), 'views');
|
|
23
23
|
|
|
24
|
-
// Static Asset Serving
|
|
24
|
+
// Static Asset Serving Middleware
|
|
25
25
|
const publicPath = nodePath.join(process.cwd(), 'public');
|
|
26
|
-
this.app.
|
|
26
|
+
this.app.use((req, res, next) => {
|
|
27
27
|
const lookupPath = req.path.startsWith('/') ? req.path.substring(1) : req.path;
|
|
28
28
|
if (!lookupPath) return next();
|
|
29
29
|
|
|
@@ -98,8 +98,58 @@ class FreeServer {
|
|
|
98
98
|
|
|
99
99
|
await handler(req, res);
|
|
100
100
|
} catch (e) {
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
if (process.env.NODE_ENV === 'production') {
|
|
102
|
+
console.error(`[Free Engine] Error ${method} ${path}:`, e);
|
|
103
|
+
res.status(e.status || 500).header('Content-Type', 'text/html').send('Internal Error');
|
|
104
|
+
} else {
|
|
105
|
+
// Premium Debug Page (Ignition Style)
|
|
106
|
+
const os = require('os');
|
|
107
|
+
const pkg = require('../package.json');
|
|
108
|
+
const debugTplPath = nodePath.join(__dirname, 'views/debug.html');
|
|
109
|
+
|
|
110
|
+
if (fs.existsSync(debugTplPath)) {
|
|
111
|
+
let html = fs.readFileSync(debugTplPath, 'utf8');
|
|
112
|
+
|
|
113
|
+
// Error Details
|
|
114
|
+
html = html.replace('{{errorName}}', e.name || 'Error');
|
|
115
|
+
html = html.replace('{{errorMessage}}', e.message || 'No message provided');
|
|
116
|
+
|
|
117
|
+
// Stack Trace with highlighting
|
|
118
|
+
const stack = (e.stack || '').split('\n').map(line => {
|
|
119
|
+
const isUserFile = line.includes(process.cwd()) && !line.includes('node_modules');
|
|
120
|
+
return `<span class="stack-line ${isUserFile ? 'highlight' : ''}">${line.replace(/&/g, '&').replace(/</g, '<')}</span>`;
|
|
121
|
+
}).join('');
|
|
122
|
+
html = html.replace('{{stackTrace}}', stack);
|
|
123
|
+
|
|
124
|
+
// Request Details
|
|
125
|
+
let reqHtml = `<tr><th>Method</th><td class="val-text">${req.method}</td></tr>`;
|
|
126
|
+
reqHtml += `<tr><th>URL</th><td class="val-text">${req.url}</td></tr>`;
|
|
127
|
+
Object.keys(req.headers).forEach(k => {
|
|
128
|
+
reqHtml += `<tr><th>Header: ${k}</th><td class="val-text">${req.headers[k]}</td></tr>`;
|
|
129
|
+
});
|
|
130
|
+
html = html.replace('{{requestDetails}}', reqHtml);
|
|
131
|
+
|
|
132
|
+
// Env details (Mask secrets)
|
|
133
|
+
let envHtml = '';
|
|
134
|
+
Object.keys(process.env).forEach(k => {
|
|
135
|
+
const isSecret = k.includes('KEY') || k.includes('SECRET') || k.includes('PASS');
|
|
136
|
+
const val = isSecret ? '********' : process.env[k];
|
|
137
|
+
envHtml += `<tr><th>${k}</th><td class="val-text">${val}</td></tr>`;
|
|
138
|
+
});
|
|
139
|
+
html = html.replace('{{envDetails}}', envHtml);
|
|
140
|
+
|
|
141
|
+
// Engine Stats
|
|
142
|
+
html = html.replace('{{version}}', pkg.version);
|
|
143
|
+
html = html.replace('{{nodeVersion}}', process.version);
|
|
144
|
+
html = html.replace('{{osInfo}}', `${os.type()} ${os.release()} (${os.arch()})`);
|
|
145
|
+
const mem = process.memoryUsage();
|
|
146
|
+
html = html.replace('{{memoryUsage}}', `${Math.round(mem.heapUsed / 1024 / 1024)}MB / ${Math.round(mem.heapTotal / 1024 / 1024)}MB`);
|
|
147
|
+
|
|
148
|
+
res.status(500).header('Content-Type', 'text/html').send(html);
|
|
149
|
+
} else {
|
|
150
|
+
res.status(500).send(`<h1>${e.name || 'Error'}</h1><pre>${e.stack}</pre>`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
103
153
|
}
|
|
104
154
|
};
|
|
105
155
|
|
|
@@ -122,7 +172,9 @@ class FreeServer {
|
|
|
122
172
|
start(port = 3000) {
|
|
123
173
|
this.app.get('/health', (req, res) => res.send('OK'));
|
|
124
174
|
this.app.listen(port).then(() => {
|
|
125
|
-
|
|
175
|
+
const blueUnderline = "\x1b[34m\x1b[4m";
|
|
176
|
+
const reset = "\x1b[0m";
|
|
177
|
+
console.log(`⚡ Free Engine Ignite: ${blueUnderline}http://localhost:${port}${reset}`);
|
|
126
178
|
});
|
|
127
179
|
}
|
|
128
180
|
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Free Engine | Runtime Exception</title>
|
|
8
|
+
<link
|
|
9
|
+
href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;700;900&family=JetBrains+Mono:wght@400;700&display=swap"
|
|
10
|
+
rel="stylesheet">
|
|
11
|
+
<style>
|
|
12
|
+
:root {
|
|
13
|
+
--primary: #ff3e3e;
|
|
14
|
+
--primary-bg: #1a0b0b;
|
|
15
|
+
--bg: #050505;
|
|
16
|
+
--surface: #121212;
|
|
17
|
+
--text: #ffffff;
|
|
18
|
+
--text-dim: #888888;
|
|
19
|
+
--border: rgba(255, 255, 255, 0.1);
|
|
20
|
+
--code: #00ff88;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
* {
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
body {
|
|
28
|
+
margin: 0;
|
|
29
|
+
font-family: 'Outfit', sans-serif;
|
|
30
|
+
background: var(--bg);
|
|
31
|
+
color: var(--text);
|
|
32
|
+
line-height: 1.6;
|
|
33
|
+
overflow-x: hidden;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
header {
|
|
37
|
+
background: var(--primary-bg);
|
|
38
|
+
border-bottom: 2px solid var(--primary);
|
|
39
|
+
padding: 3rem 2rem;
|
|
40
|
+
position: relative;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.glow {
|
|
45
|
+
position: absolute;
|
|
46
|
+
top: -100px;
|
|
47
|
+
right: -100px;
|
|
48
|
+
width: 400px;
|
|
49
|
+
height: 400px;
|
|
50
|
+
background: radial-gradient(circle, rgba(255, 62, 62, 0.2) 0%, transparent 70%);
|
|
51
|
+
z-index: 0;
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.container {
|
|
56
|
+
max-width: 1200px;
|
|
57
|
+
margin: 0 auto;
|
|
58
|
+
position: relative;
|
|
59
|
+
z-index: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.badge {
|
|
63
|
+
display: inline-block;
|
|
64
|
+
background: var(--primary);
|
|
65
|
+
color: white;
|
|
66
|
+
padding: 0.2rem 0.8rem;
|
|
67
|
+
border-radius: 4px;
|
|
68
|
+
font-weight: 900;
|
|
69
|
+
font-size: 0.7rem;
|
|
70
|
+
letter-spacing: 1px;
|
|
71
|
+
text-transform: uppercase;
|
|
72
|
+
margin-bottom: 1rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
h1 {
|
|
76
|
+
margin: 0;
|
|
77
|
+
font-size: 2.5rem;
|
|
78
|
+
font-weight: 900;
|
|
79
|
+
letter-spacing: -1px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.message {
|
|
83
|
+
font-size: 1.3rem;
|
|
84
|
+
color: var(--text-dim);
|
|
85
|
+
margin-top: 0.5rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
nav {
|
|
89
|
+
background: var(--surface);
|
|
90
|
+
border-bottom: 1px solid var(--border);
|
|
91
|
+
padding: 0;
|
|
92
|
+
display: flex;
|
|
93
|
+
position: sticky;
|
|
94
|
+
top: 0;
|
|
95
|
+
z-index: 100;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
nav button {
|
|
99
|
+
background: transparent;
|
|
100
|
+
border: none;
|
|
101
|
+
color: var(--text-dim);
|
|
102
|
+
padding: 1rem 2rem;
|
|
103
|
+
font-family: inherit;
|
|
104
|
+
font-weight: 700;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
transition: 0.3s;
|
|
107
|
+
border-bottom: 2px solid transparent;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
nav button.active {
|
|
111
|
+
color: var(--primary);
|
|
112
|
+
border-bottom-color: var(--primary);
|
|
113
|
+
background: rgba(255, 62, 62, 0.05);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
section {
|
|
117
|
+
padding: 2rem;
|
|
118
|
+
display: none;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
section.active {
|
|
122
|
+
display: block;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.stack-trace {
|
|
126
|
+
background: #000;
|
|
127
|
+
border-radius: 8px;
|
|
128
|
+
padding: 1rem;
|
|
129
|
+
overflow-x: auto;
|
|
130
|
+
border: 1px solid var(--border);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.stack-trace pre {
|
|
134
|
+
margin: 0;
|
|
135
|
+
font-family: 'JetBrains Mono', monospace;
|
|
136
|
+
font-size: 0.9rem;
|
|
137
|
+
color: #ddd;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.stack-line {
|
|
141
|
+
display: block;
|
|
142
|
+
padding: 0.2rem 0;
|
|
143
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.stack-line.highlight {
|
|
147
|
+
background: rgba(255, 62, 62, 0.1);
|
|
148
|
+
color: var(--primary);
|
|
149
|
+
font-weight: 700;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
table {
|
|
153
|
+
width: 100%;
|
|
154
|
+
border-collapse: collapse;
|
|
155
|
+
margin-top: 1rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
th,
|
|
159
|
+
td {
|
|
160
|
+
text-align: left;
|
|
161
|
+
padding: 1rem;
|
|
162
|
+
border-bottom: 1px solid var(--border);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
th {
|
|
166
|
+
color: var(--text-dim);
|
|
167
|
+
width: 200px;
|
|
168
|
+
text-transform: uppercase;
|
|
169
|
+
font-size: 0.75rem;
|
|
170
|
+
letter-spacing: 1px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.val-text {
|
|
174
|
+
font-family: 'JetBrains Mono', monospace;
|
|
175
|
+
color: var(--code);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.card {
|
|
179
|
+
background: var(--surface);
|
|
180
|
+
border: 1px solid var(--border);
|
|
181
|
+
border-radius: 12px;
|
|
182
|
+
padding: 2rem;
|
|
183
|
+
margin-bottom: 2rem;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
h2 {
|
|
187
|
+
font-size: 1.5rem;
|
|
188
|
+
margin-top: 0;
|
|
189
|
+
}
|
|
190
|
+
</style>
|
|
191
|
+
</head>
|
|
192
|
+
|
|
193
|
+
<body>
|
|
194
|
+
<header>
|
|
195
|
+
<div class="glow"></div>
|
|
196
|
+
<div class="container">
|
|
197
|
+
<div class="badge">Runtime Exception</div>
|
|
198
|
+
<h1>{{errorName}}</h1>
|
|
199
|
+
<div class="message">{{errorMessage}}</div>
|
|
200
|
+
</div>
|
|
201
|
+
</header>
|
|
202
|
+
|
|
203
|
+
<nav>
|
|
204
|
+
<button onclick="showTab('stack')" id="btn-stack" class="active">Stack Trace</button>
|
|
205
|
+
<button onclick="showTab('request')" id="btn-request">Request</button>
|
|
206
|
+
<button onclick="showTab('env')" id="btn-env">Environment</button>
|
|
207
|
+
<button onclick="showTab('app')" id="btn-app">Engine</button>
|
|
208
|
+
</nav>
|
|
209
|
+
|
|
210
|
+
<div class="container">
|
|
211
|
+
<section id="tab-stack" class="active">
|
|
212
|
+
<div class="card">
|
|
213
|
+
<h2>Trace Analysis</h2>
|
|
214
|
+
<div class="stack-trace">
|
|
215
|
+
<pre>{{stackTrace}}</pre>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</section>
|
|
219
|
+
|
|
220
|
+
<section id="tab-request">
|
|
221
|
+
<div class="card">
|
|
222
|
+
<h2>HTTP Details</h2>
|
|
223
|
+
<table>
|
|
224
|
+
{{requestDetails}}
|
|
225
|
+
</table>
|
|
226
|
+
</div>
|
|
227
|
+
</section>
|
|
228
|
+
|
|
229
|
+
<section id="tab-env">
|
|
230
|
+
<div class="card">
|
|
231
|
+
<h2>Environment Variables</h2>
|
|
232
|
+
<table>
|
|
233
|
+
{{envDetails}}
|
|
234
|
+
</table>
|
|
235
|
+
</div>
|
|
236
|
+
</section>
|
|
237
|
+
|
|
238
|
+
<section id="tab-app">
|
|
239
|
+
<div class="card">
|
|
240
|
+
<h2>Engine Statistics</h2>
|
|
241
|
+
<table>
|
|
242
|
+
<tr>
|
|
243
|
+
<th>Engine</th>
|
|
244
|
+
<td class="val-text">Free Enterprise v{{version}}</td>
|
|
245
|
+
</tr>
|
|
246
|
+
<tr>
|
|
247
|
+
<th>Node Version</th>
|
|
248
|
+
<td class="val-text">{{nodeVersion}}</td>
|
|
249
|
+
</tr>
|
|
250
|
+
<tr>
|
|
251
|
+
<th>OS</th>
|
|
252
|
+
<td class="val-text">{{osInfo}}</td>
|
|
253
|
+
</tr>
|
|
254
|
+
<tr>
|
|
255
|
+
<th>Memory Usage</th>
|
|
256
|
+
<td class="val-text">{{memoryUsage}}</td>
|
|
257
|
+
</tr>
|
|
258
|
+
</table>
|
|
259
|
+
</div>
|
|
260
|
+
</section>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<script>
|
|
264
|
+
function showTab(id) {
|
|
265
|
+
document.querySelectorAll('section').forEach(s => s.classList.remove('active'));
|
|
266
|
+
document.querySelectorAll('nav button').forEach(b => b.classList.remove('active'));
|
|
267
|
+
document.getElementById('tab-' + id).classList.add('active');
|
|
268
|
+
document.getElementById('btn-' + id).classList.add('active');
|
|
269
|
+
}
|
|
270
|
+
</script>
|
|
271
|
+
</body>
|
|
272
|
+
|
|
273
|
+
</html>
|