free-framework 4.5.7 → 4.6.0
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
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Enterprise Maintenance Mode Guard.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
module.exports = function maintenanceMiddleware(req, res
|
|
6
|
+
module.exports = function maintenanceMiddleware(req, res) {
|
|
7
7
|
const isMaintenance = process.env.MAINTENANCE_MODE === 'true';
|
|
8
8
|
const bypassToken = process.env.MAINTENANCE_BYPASS;
|
|
9
9
|
|
|
10
10
|
// Allow bypassing with a specific query token or if disabled
|
|
11
11
|
if (!isMaintenance || (bypassToken && req.query.bypass === bypassToken)) {
|
|
12
|
-
return
|
|
12
|
+
return;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// Identify if it's an API request or HTML
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Enterprise-grade Security Headers Middleware.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
module.exports = function securityMiddleware(req, res
|
|
6
|
+
module.exports = function securityMiddleware(req, res) {
|
|
7
7
|
// 1. Core Security Headers
|
|
8
8
|
res.setHeader('X-Powered-By', 'Free-Ultra/Enterprise');
|
|
9
9
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
@@ -25,6 +25,4 @@ module.exports = function securityMiddleware(req, res, next) {
|
|
|
25
25
|
|
|
26
26
|
// 4. Permissions Policy (Hardware restrictions)
|
|
27
27
|
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), interest-cohort=()');
|
|
28
|
-
|
|
29
|
-
next();
|
|
30
28
|
};
|
package/runtime/server.js
CHANGED
|
@@ -21,11 +21,11 @@ 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) => {
|
|
27
27
|
const lookupPath = req.path.startsWith('/') ? req.path.substring(1) : req.path;
|
|
28
|
-
if (!lookupPath) return
|
|
28
|
+
if (!lookupPath) return;
|
|
29
29
|
|
|
30
30
|
const fullPath = nodePath.join(publicPath, lookupPath);
|
|
31
31
|
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
@@ -34,7 +34,6 @@ class FreeServer {
|
|
|
34
34
|
const mimes = { '.js': 'application/javascript', '.css': 'text/css', '.png': 'image/png' };
|
|
35
35
|
return res.type(mimes[ext] || 'application/javascript').send(content);
|
|
36
36
|
}
|
|
37
|
-
next();
|
|
38
37
|
});
|
|
39
38
|
|
|
40
39
|
this.app.get('/test-static', (req, res) => res.send('Static serving test'));
|
|
@@ -55,7 +54,7 @@ class FreeServer {
|
|
|
55
54
|
this.app.use(maintenanceMiddleware);
|
|
56
55
|
this.app.use(securityMiddleware);
|
|
57
56
|
|
|
58
|
-
this.app.use((req, res
|
|
57
|
+
this.app.use((req, res) => {
|
|
59
58
|
const ip = req.ip || req.headers['x-forwarded-for'] || 'unknown';
|
|
60
59
|
const reqCount = this.rateLimits.get(ip) || 0;
|
|
61
60
|
|
|
@@ -66,7 +65,6 @@ class FreeServer {
|
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
this.rateLimits.set(ip, reqCount + 1);
|
|
69
|
-
next();
|
|
70
68
|
});
|
|
71
69
|
}
|
|
72
70
|
|
|
@@ -98,8 +96,58 @@ class FreeServer {
|
|
|
98
96
|
|
|
99
97
|
await handler(req, res);
|
|
100
98
|
} catch (e) {
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
if (process.env.NODE_ENV === 'production') {
|
|
100
|
+
console.error(`[Free Engine] Error ${method} ${path}:`, e);
|
|
101
|
+
res.status(e.status || 500).header('Content-Type', 'text/html').send('Internal Error');
|
|
102
|
+
} else {
|
|
103
|
+
// Premium Debug Page (Ignition Style)
|
|
104
|
+
const os = require('os');
|
|
105
|
+
const pkg = require('../package.json');
|
|
106
|
+
const debugTplPath = nodePath.join(__dirname, 'views/debug.html');
|
|
107
|
+
|
|
108
|
+
if (fs.existsSync(debugTplPath)) {
|
|
109
|
+
let html = fs.readFileSync(debugTplPath, 'utf8');
|
|
110
|
+
|
|
111
|
+
// Error Details
|
|
112
|
+
html = html.replace('{{errorName}}', e.name || 'Error');
|
|
113
|
+
html = html.replace('{{errorMessage}}', e.message || 'No message provided');
|
|
114
|
+
|
|
115
|
+
// Stack Trace with highlighting
|
|
116
|
+
const stack = (e.stack || '').split('\n').map(line => {
|
|
117
|
+
const isUserFile = line.includes(process.cwd()) && !line.includes('node_modules');
|
|
118
|
+
return `<span class="stack-line ${isUserFile ? 'highlight' : ''}">${line.replace(/&/g, '&').replace(/</g, '<')}</span>`;
|
|
119
|
+
}).join('');
|
|
120
|
+
html = html.replace('{{stackTrace}}', stack);
|
|
121
|
+
|
|
122
|
+
// Request Details
|
|
123
|
+
let reqHtml = `<tr><th>Method</th><td class="val-text">${req.method}</td></tr>`;
|
|
124
|
+
reqHtml += `<tr><th>URL</th><td class="val-text">${req.url}</td></tr>`;
|
|
125
|
+
Object.keys(req.headers).forEach(k => {
|
|
126
|
+
reqHtml += `<tr><th>Header: ${k}</th><td class="val-text">${req.headers[k]}</td></tr>`;
|
|
127
|
+
});
|
|
128
|
+
html = html.replace('{{requestDetails}}', reqHtml);
|
|
129
|
+
|
|
130
|
+
// Env details (Mask secrets)
|
|
131
|
+
let envHtml = '';
|
|
132
|
+
Object.keys(process.env).forEach(k => {
|
|
133
|
+
const isSecret = k.includes('KEY') || k.includes('SECRET') || k.includes('PASS');
|
|
134
|
+
const val = isSecret ? '********' : process.env[k];
|
|
135
|
+
envHtml += `<tr><th>${k}</th><td class="val-text">${val}</td></tr>`;
|
|
136
|
+
});
|
|
137
|
+
html = html.replace('{{envDetails}}', envHtml);
|
|
138
|
+
|
|
139
|
+
// Engine Stats
|
|
140
|
+
html = html.replace('{{version}}', pkg.version);
|
|
141
|
+
html = html.replace('{{nodeVersion}}', process.version);
|
|
142
|
+
html = html.replace('{{osInfo}}', `${os.type()} ${os.release()} (${os.arch()})`);
|
|
143
|
+
const mem = process.memoryUsage();
|
|
144
|
+
html = html.replace('{{memoryUsage}}', `${Math.round(mem.heapUsed / 1024 / 1024)}MB / ${Math.round(mem.heapTotal / 1024 / 1024)}MB`);
|
|
145
|
+
|
|
146
|
+
res.status(500).header('Content-Type', 'text/html').send(html);
|
|
147
|
+
} else {
|
|
148
|
+
res.status(500).send(`<h1>${e.name || 'Error'}</h1><pre>${e.stack}</pre>`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
103
151
|
}
|
|
104
152
|
};
|
|
105
153
|
|
|
@@ -122,7 +170,9 @@ class FreeServer {
|
|
|
122
170
|
start(port = 3000) {
|
|
123
171
|
this.app.get('/health', (req, res) => res.send('OK'));
|
|
124
172
|
this.app.listen(port).then(() => {
|
|
125
|
-
|
|
173
|
+
const blueUnderline = "\x1b[34m\x1b[4m";
|
|
174
|
+
const reset = "\x1b[0m";
|
|
175
|
+
console.log(`⚡ Free Engine Ignite: ${blueUnderline}http://localhost:${port}${reset}`);
|
|
126
176
|
});
|
|
127
177
|
}
|
|
128
178
|
}
|
|
@@ -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>
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# Free Framework Core Template Configuration
|
|
2
2
|
# Production-ready defaults for Database, Security, and Routing
|
|
3
3
|
|
|
4
|
-
# ── Database (
|
|
5
|
-
DB_CLIENT=
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
# ── Database (SQLite Default - Change as needed) ───────────────────────────
|
|
5
|
+
DB_CLIENT=sqlite3
|
|
6
|
+
DB_PATH=database/database.sqlite
|
|
7
|
+
|
|
8
|
+
# MySQL (Recommended for Production)
|
|
9
|
+
# DB_CLIENT=mysql2
|
|
10
|
+
# DB_HOST=127.0.0.1
|
|
11
|
+
# DB_PORT=3306
|
|
12
|
+
# DB_USER=root
|
|
13
|
+
# DB_PASS=
|
|
14
|
+
# DB_NAME=free_db_default
|
|
11
15
|
|
|
12
16
|
# Connection Pooling Settings
|
|
13
17
|
DB_POOL_MIN=5
|