free-framework 4.8.11 → 5.0.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/README.md +20 -9
- package/cli/commands/make.js +21 -12
- package/compiler/generator.js +11 -3
- package/package.json +1 -1
- package/runtime/cluster.js +5 -5
- package/runtime/middleware/firewall.js +38 -0
- package/runtime/server.js +6 -2
- package/templates/app-template/.free/app.js +1555 -0
- package/templates/app-template/app/controllers/ProductController.free +31 -0
- package/templates/app-template/app/models/Product.free +12 -0
- package/templates/app-template/public/free-runtime.js +1 -0
- package/templates/app-template/resources/components/ProductCard.free +16 -0
- package/templates/app-template/resources/views/docs.free +13 -13
- package/templates/app-template/resources/views/home.free +80 -55
|
@@ -0,0 +1,1555 @@
|
|
|
1
|
+
require('dotenv').config();
|
|
2
|
+
const { FreeServer, Model, ORM, ClusterManager, AuthPlugin, UploadPlugin, ChatPlugin } = require('free-framework');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const bcrypt = require('bcryptjs');
|
|
5
|
+
const jwt = require('jsonwebtoken');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { minify } = require('html-minifier-terser');
|
|
9
|
+
// Enterprise Services (Global Inject)
|
|
10
|
+
let Validator, Storage;
|
|
11
|
+
try { Validator = require(path.join(process.cwd(), 'app/Services/Validator.js')); } catch(e) {}
|
|
12
|
+
try { Storage = require(path.join(process.cwd(), 'app/Services/Storage.js')); } catch(e) {}
|
|
13
|
+
const server = new FreeServer();
|
|
14
|
+
|
|
15
|
+
// Models
|
|
16
|
+
const modelsRegistry = {};
|
|
17
|
+
class User extends Model {}
|
|
18
|
+
User.fields = [{"name":"name","type":"string"},{"name":"email","type":"string","unique":true},{"name":"password","type":"string"},{"name":"role","type":"string","default":"user"},{"name":"timestamps","type":"directive"}];
|
|
19
|
+
modelsRegistry['User'] = User;
|
|
20
|
+
server.use(ChatPlugin());
|
|
21
|
+
|
|
22
|
+
server.app.post("/_free/action/register", async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const user = (res.context && res.context.user) ? res.context.user : null;
|
|
25
|
+
let body = {};
|
|
26
|
+
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
27
|
+
body = await req.json().catch(() => ({}));
|
|
28
|
+
} else {
|
|
29
|
+
body = await req.urlencoded().catch(() => ({}));
|
|
30
|
+
}
|
|
31
|
+
const result = await (async () => {
|
|
32
|
+
// 1. Validate incoming data
|
|
33
|
+
const { name, email, password } = body;
|
|
34
|
+
if (!name || !email || !password) {
|
|
35
|
+
throw new Error("Missing required fields: name, email, password");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Hash the password using bcrypt dynamically
|
|
39
|
+
// The salt rounds are pulled from config (defaults to 10 for performance/security balance)
|
|
40
|
+
const bcrypt = require('bcryptjs');
|
|
41
|
+
const authConfig = require('../../config/auth');
|
|
42
|
+
const hashedPassword = await bcrypt.hash(password, authConfig.bcrypt_rounds || 10);
|
|
43
|
+
|
|
44
|
+
// 3. Save the new user to the database
|
|
45
|
+
// The ORM's 'create' method automatically sanitizes inputs to prevent SQL Injection
|
|
46
|
+
const user = await User.create( { name, email, password: hashedPassword });
|
|
47
|
+
|
|
48
|
+
// 4. Generate the JWT stateless token
|
|
49
|
+
const jwt = require('jsonwebtoken');
|
|
50
|
+
const payload = { id: user.id, email: user.email, role: user.role };
|
|
51
|
+
const secret = authConfig.jwt_secret;
|
|
52
|
+
const options = { expiresIn: authConfig.jwt_expires_in || '7d' };
|
|
53
|
+
|
|
54
|
+
const token = jwt.sign(payload, secret, options);
|
|
55
|
+
|
|
56
|
+
// 5. Return success. CRITICAL: Never return the password hash in the response!
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
message: "User registered securely.",
|
|
60
|
+
token,
|
|
61
|
+
user: { id: user.id, name: user.name, email: user.email }
|
|
62
|
+
};
|
|
63
|
+
})();
|
|
64
|
+
if (!res.headersSent) res.json({ success: true, result });
|
|
65
|
+
} catch(e) {
|
|
66
|
+
if (!res.headersSent) res.status(500).json({ success: false, error: e.message });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
server.app.post("/_free/action/login", async (req, res) => {
|
|
71
|
+
try {
|
|
72
|
+
const user = (res.context && res.context.user) ? res.context.user : null;
|
|
73
|
+
let body = {};
|
|
74
|
+
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
75
|
+
body = await req.json().catch(() => ({}));
|
|
76
|
+
} else {
|
|
77
|
+
body = await req.urlencoded().catch(() => ({}));
|
|
78
|
+
}
|
|
79
|
+
const result = await (async () => {
|
|
80
|
+
// 1. Validate inputs
|
|
81
|
+
const { email, password } = body;
|
|
82
|
+
if (!email || !password) {
|
|
83
|
+
throw new Error("Missing credentials");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 2. Look up the user by email (SQL Injection Proof via ORM query builder)
|
|
87
|
+
const userQuery = await User.query().where( { email }).get();
|
|
88
|
+
const user = userQuery[0];
|
|
89
|
+
|
|
90
|
+
if (!user) {
|
|
91
|
+
throw new Error("Invalid credentials");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 3. Prevent Timing Attacks & Verify Hash
|
|
95
|
+
// We strictly use bcrypt.compare instead of manual string comparison
|
|
96
|
+
const bcrypt = require('bcryptjs');
|
|
97
|
+
const isMatch = await bcrypt.compare(password, user.password);
|
|
98
|
+
|
|
99
|
+
if (!isMatch) {
|
|
100
|
+
throw new Error("Invalid credentials");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 4. Issue the secure JWT Token
|
|
104
|
+
const authConfig = require('../../config/auth');
|
|
105
|
+
const jwt = require('jsonwebtoken');
|
|
106
|
+
const token = jwt.sign(
|
|
107
|
+
{ id: user.id, email: user.email, role: user.role },
|
|
108
|
+
authConfig.jwt_secret,
|
|
109
|
+
{ expiresIn: authConfig.jwt_expires_in || '7d' }
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// 5. Return the payload for the frontend to store (e.g., in localStorage or cookies)
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
token,
|
|
116
|
+
user: { id: user.id, name: user.name, email: user.email }
|
|
117
|
+
};
|
|
118
|
+
})();
|
|
119
|
+
if (!res.headersSent) res.json({ success: true, result });
|
|
120
|
+
} catch(e) {
|
|
121
|
+
if (!res.headersSent) res.status(500).json({ success: false, error: e.message });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const scopedCSS = ".free-component[data-component=Dashboard]{background:#f3f4f6;min-height:100vh}.card{background:#fff;padding:1.5rem;border-radius:.5rem;box-shadow:0 4px 6px -1px rgba(0,0,0,.1)}.card{background:#fff;padding:1.5rem;border-radius:.5rem;box-shadow:0 4px 6px -1px rgba(0,0,0,.1)}";
|
|
126
|
+
|
|
127
|
+
server.renderRegistry = {
|
|
128
|
+
Counter: renderCounter,
|
|
129
|
+
Dashboard: renderDashboard,
|
|
130
|
+
Docs: renderDocs,
|
|
131
|
+
Header: renderHeader,
|
|
132
|
+
Home: renderHome,
|
|
133
|
+
Login: renderLogin
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
server.middleware((req, res, next) => {
|
|
137
|
+
if (!res.context) res.context = {};
|
|
138
|
+
res.context.csrfToken = crypto.randomBytes(32).toString('hex');
|
|
139
|
+
next();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
function renderCounter(props = { }, helpers = { }) {
|
|
143
|
+
const e = (str) => String(str).replace(/[&<>'"]/g, m => ({'&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[m]));
|
|
144
|
+
const state = {
|
|
145
|
+
"count": "0",
|
|
146
|
+
};
|
|
147
|
+
const { count } = state;
|
|
148
|
+
const _events = [];
|
|
149
|
+
const islandAttr = true ? ' data-component="Counter"' : '';
|
|
150
|
+
let html = "<div class='free-component'" + islandAttr + " data-state='" + JSON.stringify(state).replace(/'/g, "'") + "'";
|
|
151
|
+
html += ">";
|
|
152
|
+
html += "<div";
|
|
153
|
+
html += ">";
|
|
154
|
+
html += "<class";
|
|
155
|
+
html += " " + "\"p-4 bg-gray-800 rounded-lg text-center\"" + "='" + `h2` + "'";
|
|
156
|
+
html += ">";
|
|
157
|
+
html += `Reactive Counter`;
|
|
158
|
+
html += "</class>";
|
|
159
|
+
html += "<p";
|
|
160
|
+
html += ">";
|
|
161
|
+
html += "<class";
|
|
162
|
+
html += " " + "\"text-4xl font-black text-primary my-4\"" + "='" + `text` + "'";
|
|
163
|
+
html += " " + "\"{count}\"" + "='" + `true` + "'";
|
|
164
|
+
html += ">";
|
|
165
|
+
html += "</class>";
|
|
166
|
+
html += "</p>";
|
|
167
|
+
html += "<button";
|
|
168
|
+
html += ">";
|
|
169
|
+
html += "<class";
|
|
170
|
+
html += " " + "\"px-6 py-2 bg-primary text-dark font-bold rounded-full hover:scale-105 transition-transform\"" + "='" + `text` + "'";
|
|
171
|
+
html += " " + "\"Increment\"" + "='" + `on-click` + "'";
|
|
172
|
+
html += ">";
|
|
173
|
+
html += "<state";
|
|
174
|
+
html += ">";
|
|
175
|
+
html += "</state>";
|
|
176
|
+
html += "<count";
|
|
177
|
+
html += ">";
|
|
178
|
+
html += "</count>";
|
|
179
|
+
html += "</class>";
|
|
180
|
+
html += "</button>";
|
|
181
|
+
html += "</div>";
|
|
182
|
+
html += "<script>window.__free_actions = window.__free_actions || {}; window.__free_actions['Counter'] = {};";
|
|
183
|
+
_events.forEach(ev => {
|
|
184
|
+
html += "window.__free_actions['Counter'][ev.id] = " + ev.fn.toString() + ";";
|
|
185
|
+
});
|
|
186
|
+
html += "</script>";
|
|
187
|
+
html += "</div>";
|
|
188
|
+
return html;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function renderDashboard(props = { }, helpers = { }) {
|
|
192
|
+
const e = (str) => String(str).replace(/[&<>'"]/g, m => ({'&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[m]));
|
|
193
|
+
const state = {
|
|
194
|
+
};
|
|
195
|
+
const _events = [];
|
|
196
|
+
const islandAttr = false ? ' data-component="Dashboard"' : '';
|
|
197
|
+
let html = "<div class='free-component'" + islandAttr + " data-state='" + JSON.stringify(state).replace(/'/g, "'") + "'";
|
|
198
|
+
html += ">";
|
|
199
|
+
html += "<div";
|
|
200
|
+
html += " " + "class" + "='" + `dashboard-container` + "'";
|
|
201
|
+
html += ">";
|
|
202
|
+
html += "<div";
|
|
203
|
+
html += " " + "class" + "='" + `max-w-4xl mx-auto` + "'";
|
|
204
|
+
html += ">";
|
|
205
|
+
html += "<h1";
|
|
206
|
+
html += " " + "class" + "='" + `text-3xl font-bold mb-6 text-gray-800` + "'";
|
|
207
|
+
html += ">";
|
|
208
|
+
html += `Secure Dashboard`;
|
|
209
|
+
html += "</h1>";
|
|
210
|
+
html += "<div";
|
|
211
|
+
html += " " + "class" + "='" + `card` + "'";
|
|
212
|
+
html += ">";
|
|
213
|
+
html += "<h2";
|
|
214
|
+
html += " " + "class" + "='" + `text-xl font-semibold mb-2` + "'";
|
|
215
|
+
html += ">";
|
|
216
|
+
html += `System Status`;
|
|
217
|
+
html += "</h2>";
|
|
218
|
+
html += "<p";
|
|
219
|
+
html += " " + "class" + "='" + `text-gray-600` + "'";
|
|
220
|
+
html += ">";
|
|
221
|
+
html += `If you are seeing this, your enterprise framework was successfully generated and routed!`;
|
|
222
|
+
html += "</p>";
|
|
223
|
+
html += "<p";
|
|
224
|
+
html += " " + "class" + "='" + `text-sm mt-4 text-gray-400` + "'";
|
|
225
|
+
html += ">";
|
|
226
|
+
html += `Tip: Check routes/web.free and app/Http/Controllers to see how this page operates.`;
|
|
227
|
+
html += "</p>";
|
|
228
|
+
html += "</div>";
|
|
229
|
+
html += "</div>";
|
|
230
|
+
html += "</div>";
|
|
231
|
+
html += "</div>";
|
|
232
|
+
return html;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function renderDocs(props = { }, helpers = { }) {
|
|
236
|
+
const e = (str) => String(str).replace(/[&<>'"]/g, m => ({'&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[m]));
|
|
237
|
+
const state = {
|
|
238
|
+
};
|
|
239
|
+
const _events = [];
|
|
240
|
+
const islandAttr = false ? ' data-component="Docs"' : '';
|
|
241
|
+
let html = "<div class='free-component'" + islandAttr + " data-state='" + JSON.stringify(state).replace(/'/g, "'") + "'";
|
|
242
|
+
html += ">";
|
|
243
|
+
html += "<div";
|
|
244
|
+
html += " " + "class" + "='" + `min-h-screen bg-black text-zinc-300 font-sans selection:bg-white selection:text-black` + "'";
|
|
245
|
+
html += ">";
|
|
246
|
+
html += "<div";
|
|
247
|
+
html += " " + "class" + "='" + `container-pro pt-32 pb-20` + "'";
|
|
248
|
+
html += ">";
|
|
249
|
+
html += "<span";
|
|
250
|
+
html += " " + "class" + "='" + `badge-pro mb-4 animate-pulse` + "'";
|
|
251
|
+
html += ">";
|
|
252
|
+
html += `v4.8.10 | Cloud Docs`;
|
|
253
|
+
html += "</span>";
|
|
254
|
+
html += "<h1";
|
|
255
|
+
html += " " + "class" + "='" + `text-6xl md:text-8xl font-black text-white tracking-tighter mb-8 leading-none` + "'";
|
|
256
|
+
html += ">";
|
|
257
|
+
html += `Free Ultra`;
|
|
258
|
+
html += "<br";
|
|
259
|
+
html += ">";
|
|
260
|
+
html += "</br>";
|
|
261
|
+
html += "<span";
|
|
262
|
+
html += " " + "class" + "='" + `text-zinc-700` + "'";
|
|
263
|
+
html += ">";
|
|
264
|
+
html += `Documentation`;
|
|
265
|
+
html += "</span>";
|
|
266
|
+
html += "</h1>";
|
|
267
|
+
html += "<p";
|
|
268
|
+
html += " " + "class" + "='" + `text-xl md:text-2xl text-zinc-500 max-w-2xl leading-relaxed mb-12` + "'";
|
|
269
|
+
html += ">";
|
|
270
|
+
html += `The complete 'A to Z' guide to mastering the world's fastest Islands Architecture framework.`;
|
|
271
|
+
html += "</p>";
|
|
272
|
+
html += "<div";
|
|
273
|
+
html += " " + "class" + "='" + `flex flex-wrap gap-4` + "'";
|
|
274
|
+
html += ">";
|
|
275
|
+
html += "<a";
|
|
276
|
+
html += " " + "href" + "='" + `#philosophy` + "'";
|
|
277
|
+
html += " " + "class" + "='" + `btn-pro` + "'";
|
|
278
|
+
html += ">";
|
|
279
|
+
html += `Core Philosophy`;
|
|
280
|
+
html += "</a>";
|
|
281
|
+
html += "<a";
|
|
282
|
+
html += " " + "href" + "='" + `#cli` + "'";
|
|
283
|
+
html += " " + "class" + "='" + `btn-outline` + "'";
|
|
284
|
+
html += ">";
|
|
285
|
+
html += `CLI Commands`;
|
|
286
|
+
html += "</a>";
|
|
287
|
+
html += "<a";
|
|
288
|
+
html += " " + "href" + "='" + `#vscode` + "'";
|
|
289
|
+
html += " " + "class" + "='" + `btn-outline` + "'";
|
|
290
|
+
html += ">";
|
|
291
|
+
html += `VS Code Setup`;
|
|
292
|
+
html += "</a>";
|
|
293
|
+
html += "</div>";
|
|
294
|
+
html += "</div>";
|
|
295
|
+
html += "<div";
|
|
296
|
+
html += " " + "class" + "='" + `container-pro space-y-32 pb-40` + "'";
|
|
297
|
+
html += ">";
|
|
298
|
+
html += "<section";
|
|
299
|
+
html += " " + "id" + "='" + `philosophy` + "'";
|
|
300
|
+
html += ">";
|
|
301
|
+
html += "<div";
|
|
302
|
+
html += " " + "class" + "='" + `grid md:grid-cols-2 gap-16 items-start` + "'";
|
|
303
|
+
html += ">";
|
|
304
|
+
html += "<div";
|
|
305
|
+
html += ">";
|
|
306
|
+
html += "<h2";
|
|
307
|
+
html += " " + "class" + "='" + `text-4xl font-bold text-white mb-6 tracking-tight` + "'";
|
|
308
|
+
html += ">";
|
|
309
|
+
html += `1. Core Philosophy`;
|
|
310
|
+
html += "</h2>";
|
|
311
|
+
html += "<p";
|
|
312
|
+
html += " " + "class" + "='" + `mb-6 leading-relaxed` + "'";
|
|
313
|
+
html += ">";
|
|
314
|
+
html += `Free Ultra isn't just another framework. It's a high-performance engine built on the principle of . We use to ensure your pages load instantly while remaining fully interactive.`;
|
|
315
|
+
html += "<strong";
|
|
316
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
317
|
+
html += ">";
|
|
318
|
+
html += `Zero-JS by Default`;
|
|
319
|
+
html += "</strong>";
|
|
320
|
+
html += "<strong";
|
|
321
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
322
|
+
html += ">";
|
|
323
|
+
html += `Islands Architecture`;
|
|
324
|
+
html += "</strong>";
|
|
325
|
+
html += "</p>";
|
|
326
|
+
html += "<ul";
|
|
327
|
+
html += " " + "class" + "='" + `space-y-4 text-sm` + "'";
|
|
328
|
+
html += ">";
|
|
329
|
+
html += "<li";
|
|
330
|
+
html += " " + "class" + "='" + `flex gap-3` + "'";
|
|
331
|
+
html += ">";
|
|
332
|
+
html += `Blazing-fast SSR powered by HyperExpress.`;
|
|
333
|
+
html += "<span";
|
|
334
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
335
|
+
html += ">";
|
|
336
|
+
html += `•`;
|
|
337
|
+
html += "</span>";
|
|
338
|
+
html += "</li>";
|
|
339
|
+
html += "<li";
|
|
340
|
+
html += " " + "class" + "='" + `flex gap-3` + "'";
|
|
341
|
+
html += ">";
|
|
342
|
+
html += `Selective Hydration: Only JavaScript that's needed enters the browser.`;
|
|
343
|
+
html += "<span";
|
|
344
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
345
|
+
html += ">";
|
|
346
|
+
html += `•`;
|
|
347
|
+
html += "</span>";
|
|
348
|
+
html += "</li>";
|
|
349
|
+
html += "<li";
|
|
350
|
+
html += " " + "class" + "='" + `flex gap-3` + "'";
|
|
351
|
+
html += ">";
|
|
352
|
+
html += `Unified Syntax: The .free language unifies HTML, CSS, and Logic.`;
|
|
353
|
+
html += "<span";
|
|
354
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
355
|
+
html += ">";
|
|
356
|
+
html += `•`;
|
|
357
|
+
html += "</span>";
|
|
358
|
+
html += "</li>";
|
|
359
|
+
html += "</ul>";
|
|
360
|
+
html += "</div>";
|
|
361
|
+
html += "<div";
|
|
362
|
+
html += " " + "class" + "='" + `card-pro bg-zinc-900/20` + "'";
|
|
363
|
+
html += ">";
|
|
364
|
+
html += "<pre";
|
|
365
|
+
html += ">";
|
|
366
|
+
html += "<code";
|
|
367
|
+
html += ">";
|
|
368
|
+
html += `// Pure Performance\ncomponent Fast { \n h1 { text 'Zero Delay' }\n // Static by default\n}\n`;
|
|
369
|
+
html += "</code>";
|
|
370
|
+
html += "</pre>";
|
|
371
|
+
html += "</div>";
|
|
372
|
+
html += "</div>";
|
|
373
|
+
html += "</section>";
|
|
374
|
+
html += "<section";
|
|
375
|
+
html += " " + "id" + "='" + `language` + "'";
|
|
376
|
+
html += ">";
|
|
377
|
+
html += "<h2";
|
|
378
|
+
html += " " + "class" + "='" + `text-4xl font-bold text-white mb-12 tracking-tight` + "'";
|
|
379
|
+
html += ">";
|
|
380
|
+
html += `2. The .free Language`;
|
|
381
|
+
html += "</h2>";
|
|
382
|
+
html += "<div";
|
|
383
|
+
html += " " + "class" + "='" + `grid md:grid-cols-3 gap-8` + "'";
|
|
384
|
+
html += ">";
|
|
385
|
+
html += "<div";
|
|
386
|
+
html += " " + "class" + "='" + `card-pro` + "'";
|
|
387
|
+
html += ">";
|
|
388
|
+
html += "<h3";
|
|
389
|
+
html += " " + "class" + "='" + `text-xl font-bold text-white mb-4` + "'";
|
|
390
|
+
html += ">";
|
|
391
|
+
html += `Components`;
|
|
392
|
+
html += "</h3>";
|
|
393
|
+
html += "<p";
|
|
394
|
+
html += " " + "class" + "='" + `text-sm leading-relaxed` + "'";
|
|
395
|
+
html += ">";
|
|
396
|
+
html += `Components are the heart of Free. Defined with the keyword, they encapsulate structure and scoped styling.`;
|
|
397
|
+
html += "<code";
|
|
398
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
399
|
+
html += ">";
|
|
400
|
+
html += `component`;
|
|
401
|
+
html += "</code>";
|
|
402
|
+
html += "</p>";
|
|
403
|
+
html += "</div>";
|
|
404
|
+
html += "<div";
|
|
405
|
+
html += " " + "class" + "='" + `card-pro` + "'";
|
|
406
|
+
html += ">";
|
|
407
|
+
html += "<h3";
|
|
408
|
+
html += " " + "class" + "='" + `text-xl font-bold text-white mb-4` + "'";
|
|
409
|
+
html += ">";
|
|
410
|
+
html += `State Management`;
|
|
411
|
+
html += "</h3>";
|
|
412
|
+
html += "<p";
|
|
413
|
+
html += " " + "class" + "='" + `text-sm leading-relaxed` + "'";
|
|
414
|
+
html += ">";
|
|
415
|
+
html += `Use and to manage lifecycle. Logic is handled in actions that bridge server and client.`;
|
|
416
|
+
html += "<code";
|
|
417
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
418
|
+
html += ">";
|
|
419
|
+
html += `onMount`;
|
|
420
|
+
html += "</code>";
|
|
421
|
+
html += "<code";
|
|
422
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
423
|
+
html += ">";
|
|
424
|
+
html += `onDestroy`;
|
|
425
|
+
html += "</code>";
|
|
426
|
+
html += "</p>";
|
|
427
|
+
html += "</div>";
|
|
428
|
+
html += "<div";
|
|
429
|
+
html += " " + "class" + "='" + `card-pro` + "'";
|
|
430
|
+
html += ">";
|
|
431
|
+
html += "<h3";
|
|
432
|
+
html += " " + "class" + "='" + `text-xl font-bold text-white mb-4` + "'";
|
|
433
|
+
html += ">";
|
|
434
|
+
html += `Partial Rendering`;
|
|
435
|
+
html += "</h3>";
|
|
436
|
+
html += "<p";
|
|
437
|
+
html += " " + "class" + "='" + `text-sm leading-relaxed` + "'";
|
|
438
|
+
html += ">";
|
|
439
|
+
html += `The SPA engine uses partial rendering (X-Free-Partial) to update only the content area, keeping transitions smooth as silk.`;
|
|
440
|
+
html += "</p>";
|
|
441
|
+
html += "</div>";
|
|
442
|
+
html += "</div>";
|
|
443
|
+
html += "</section>";
|
|
444
|
+
html += "<section";
|
|
445
|
+
html += " " + "id" + "='" + `orm` + "'";
|
|
446
|
+
html += ">";
|
|
447
|
+
html += "<div";
|
|
448
|
+
html += " " + "class" + "='" + `grid md:grid-cols-2 gap-16` + "'";
|
|
449
|
+
html += ">";
|
|
450
|
+
html += "<div";
|
|
451
|
+
html += ">";
|
|
452
|
+
html += "<h2";
|
|
453
|
+
html += " " + "class" + "='" + `text-4xl font-bold text-white mb-6 tracking-tight` + "'";
|
|
454
|
+
html += ">";
|
|
455
|
+
html += `3. Routing & Logic`;
|
|
456
|
+
html += "</h2>";
|
|
457
|
+
html += "<p";
|
|
458
|
+
html += " " + "class" + "='" + `mb-6` + "'";
|
|
459
|
+
html += ">";
|
|
460
|
+
html += `Routes are separated into for HTML pages and for JSON endpoints. All logic is handled via high-speed action handlers.`;
|
|
461
|
+
html += "<code";
|
|
462
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
463
|
+
html += ">";
|
|
464
|
+
html += `web.free`;
|
|
465
|
+
html += "</code>";
|
|
466
|
+
html += "<code";
|
|
467
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
468
|
+
html += ">";
|
|
469
|
+
html += `api.free`;
|
|
470
|
+
html += "</code>";
|
|
471
|
+
html += "</p>";
|
|
472
|
+
html += "<div";
|
|
473
|
+
html += " " + "class" + "='" + `card-pro border-zinc-800` + "'";
|
|
474
|
+
html += ">";
|
|
475
|
+
html += "<h4";
|
|
476
|
+
html += " " + "class" + "='" + `text-white font-bold mb-2` + "'";
|
|
477
|
+
html += ">";
|
|
478
|
+
html += `Built-in ORM`;
|
|
479
|
+
html += "</h4>";
|
|
480
|
+
html += "<p";
|
|
481
|
+
html += " " + "class" + "='" + `text-sm` + "'";
|
|
482
|
+
html += ">";
|
|
483
|
+
html += `Zero-config SQLite integration. Run and your models are ready. No complex drivers or boilerplate needed.`;
|
|
484
|
+
html += "<code";
|
|
485
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
486
|
+
html += ">";
|
|
487
|
+
html += `ORM.migrate()`;
|
|
488
|
+
html += "</code>";
|
|
489
|
+
html += "</p>";
|
|
490
|
+
html += "</div>";
|
|
491
|
+
html += "</div>";
|
|
492
|
+
html += "<div";
|
|
493
|
+
html += " " + "class" + "='" + `card-pro bg-zinc-950` + "'";
|
|
494
|
+
html += ">";
|
|
495
|
+
html += "<pre";
|
|
496
|
+
html += ">";
|
|
497
|
+
html += "<code";
|
|
498
|
+
html += ">";
|
|
499
|
+
html += `get \/dashboard\ -> Dashboard\n\npost \/api/login\ { \n const user = await ORM.find('User', { email: body.email });\n return { success: !!user };\n}`;
|
|
500
|
+
html += "</code>";
|
|
501
|
+
html += "</pre>";
|
|
502
|
+
html += "</div>";
|
|
503
|
+
html += "</div>";
|
|
504
|
+
html += "</section>";
|
|
505
|
+
html += "<section";
|
|
506
|
+
html += " " + "id" + "='" + `cli` + "'";
|
|
507
|
+
html += ">";
|
|
508
|
+
html += "<h2";
|
|
509
|
+
html += " " + "class" + "='" + `text-4xl font-bold text-white mb-12 tracking-tight` + "'";
|
|
510
|
+
html += ">";
|
|
511
|
+
html += `4. CLI Power-user Guide`;
|
|
512
|
+
html += "</h2>";
|
|
513
|
+
html += "<div";
|
|
514
|
+
html += " " + "class" + "='" + `grid grid-cols-1 md:grid-cols-2 gap-4` + "'";
|
|
515
|
+
html += ">";
|
|
516
|
+
html += "<div";
|
|
517
|
+
html += " " + "class" + "='" + `flex justify-between p-4 border border-zinc-800 rounded-lg hover:border-zinc-600 transition-colors` + "'";
|
|
518
|
+
html += ">";
|
|
519
|
+
html += "<code";
|
|
520
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
521
|
+
html += ">";
|
|
522
|
+
html += `free serve`;
|
|
523
|
+
html += "</code>";
|
|
524
|
+
html += "<span";
|
|
525
|
+
html += " " + "class" + "='" + `text-zinc-600 truncate` + "'";
|
|
526
|
+
html += ">";
|
|
527
|
+
html += `Ignites development server with HMR`;
|
|
528
|
+
html += "</span>";
|
|
529
|
+
html += "</div>";
|
|
530
|
+
html += "<div";
|
|
531
|
+
html += " " + "class" + "='" + `flex justify-between p-4 border border-zinc-800 rounded-lg hover:border-zinc-600 transition-colors` + "'";
|
|
532
|
+
html += ">";
|
|
533
|
+
html += "<code";
|
|
534
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
535
|
+
html += ">";
|
|
536
|
+
html += `free make:crud <name>`;
|
|
537
|
+
html += "</code>";
|
|
538
|
+
html += "<span";
|
|
539
|
+
html += " " + "class" + "='" + `text-zinc-600 truncate` + "'";
|
|
540
|
+
html += ">";
|
|
541
|
+
html += `Generates full MVC scaffold`;
|
|
542
|
+
html += "</span>";
|
|
543
|
+
html += "</div>";
|
|
544
|
+
html += "<div";
|
|
545
|
+
html += " " + "class" + "='" + `flex justify-between p-4 border border-zinc-800 rounded-lg hover:border-zinc-600 transition-colors` + "'";
|
|
546
|
+
html += ">";
|
|
547
|
+
html += "<code";
|
|
548
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
549
|
+
html += ">";
|
|
550
|
+
html += `free doctor`;
|
|
551
|
+
html += "</code>";
|
|
552
|
+
html += "<span";
|
|
553
|
+
html += " " + "class" + "='" + `text-zinc-600 truncate` + "'";
|
|
554
|
+
html += ">";
|
|
555
|
+
html += `Audits and repairs environment`;
|
|
556
|
+
html += "</span>";
|
|
557
|
+
html += "</div>";
|
|
558
|
+
html += "<div";
|
|
559
|
+
html += " " + "class" + "='" + `flex justify-between p-4 border border-zinc-800 rounded-lg hover:border-zinc-600 transition-colors` + "'";
|
|
560
|
+
html += ">";
|
|
561
|
+
html += "<code";
|
|
562
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
563
|
+
html += ">";
|
|
564
|
+
html += `free bench`;
|
|
565
|
+
html += "</code>";
|
|
566
|
+
html += "<span";
|
|
567
|
+
html += " " + "class" + "='" + `text-zinc-600 truncate` + "'";
|
|
568
|
+
html += ">";
|
|
569
|
+
html += `Runs load tests on your routes`;
|
|
570
|
+
html += "</span>";
|
|
571
|
+
html += "</div>";
|
|
572
|
+
html += "<div";
|
|
573
|
+
html += " " + "class" + "='" + `flex justify-between p-4 border border-zinc-800 rounded-lg hover:border-zinc-600 transition-colors` + "'";
|
|
574
|
+
html += ">";
|
|
575
|
+
html += "<code";
|
|
576
|
+
html += " " + "class" + "='" + `text-white` + "'";
|
|
577
|
+
html += ">";
|
|
578
|
+
html += `free route:list`;
|
|
579
|
+
html += "</code>";
|
|
580
|
+
html += "<span";
|
|
581
|
+
html += " " + "class" + "='" + `text-zinc-600 truncate` + "'";
|
|
582
|
+
html += ">";
|
|
583
|
+
html += `Displays all registered endpoints`;
|
|
584
|
+
html += "</span>";
|
|
585
|
+
html += "</div>";
|
|
586
|
+
html += "</div>";
|
|
587
|
+
html += "</section>";
|
|
588
|
+
html += "<section";
|
|
589
|
+
html += " " + "id" + "='" + `vscode` + "'";
|
|
590
|
+
html += ">";
|
|
591
|
+
html += "<div";
|
|
592
|
+
html += " " + "class" + "='" + `bg-zinc-900/10 border border-zinc-800 rounded-3xl p-12` + "'";
|
|
593
|
+
html += ">";
|
|
594
|
+
html += "<h2";
|
|
595
|
+
html += " " + "class" + "='" + `text-4xl font-bold text-white mb-8 tracking-tight` + "'";
|
|
596
|
+
html += ">";
|
|
597
|
+
html += `5. IDE Ecosystem`;
|
|
598
|
+
html += "</h2>";
|
|
599
|
+
html += "<div";
|
|
600
|
+
html += " " + "class" + "='" + `grid md:grid-cols-2 gap-12` + "'";
|
|
601
|
+
html += ">";
|
|
602
|
+
html += "<div";
|
|
603
|
+
html += ">";
|
|
604
|
+
html += "<h3";
|
|
605
|
+
html += " " + "class" + "='" + `text-2xl font-bold text-white mb-4` + "'";
|
|
606
|
+
html += ">";
|
|
607
|
+
html += `Visual Studio Code`;
|
|
608
|
+
html += "</h3>";
|
|
609
|
+
html += "<p";
|
|
610
|
+
html += " " + "class" + "='" + `mb-6 text-sm` + "'";
|
|
611
|
+
html += ">";
|
|
612
|
+
html += `Get syntax highlighting, auto-completion, and deep linting for .free files.`;
|
|
613
|
+
html += "</p>";
|
|
614
|
+
html += "<div";
|
|
615
|
+
html += " " + "class" + "='" + `space-y-4` + "'";
|
|
616
|
+
html += ">";
|
|
617
|
+
html += "<a";
|
|
618
|
+
html += " " + "href" + "='" + `https://marketplace.visualstudio.com/items?itemName=dev-omartolba.free-vscode` + "'";
|
|
619
|
+
html += " " + "class" + "='" + `block text-zinc-400 hover:text-white transition-colors flex items-center gap-2` + "'";
|
|
620
|
+
html += ">";
|
|
621
|
+
html += `→ Install from VS Marketplace`;
|
|
622
|
+
html += "</a>";
|
|
623
|
+
html += "<a";
|
|
624
|
+
html += " " + "href" + "='" + `https://open-vsx.org/extension/dev-omartolba/free-vscode` + "'";
|
|
625
|
+
html += " " + "class" + "='" + `block text-zinc-400 hover:text-white transition-colors flex items-center gap-2` + "'";
|
|
626
|
+
html += ">";
|
|
627
|
+
html += `→ Install from Antigravity / Open VSX`;
|
|
628
|
+
html += "</a>";
|
|
629
|
+
html += "</div>";
|
|
630
|
+
html += "</div>";
|
|
631
|
+
html += "<div";
|
|
632
|
+
html += ">";
|
|
633
|
+
html += "<h3";
|
|
634
|
+
html += " " + "class" + "='" + `text-2xl font-bold text-white mb-4` + "'";
|
|
635
|
+
html += ">";
|
|
636
|
+
html += `Manual Installation`;
|
|
637
|
+
html += "</h3>";
|
|
638
|
+
html += "<p";
|
|
639
|
+
html += " " + "class" + "='" + `mb-4 text-xs font-mono uppercase tracking-widest text-zinc-600` + "'";
|
|
640
|
+
html += ">";
|
|
641
|
+
html += `Air-gapped Setup`;
|
|
642
|
+
html += "</p>";
|
|
643
|
+
html += "<div";
|
|
644
|
+
html += " " + "class" + "='" + `card-pro bg-black border-zinc-800 p-6` + "'";
|
|
645
|
+
html += ">";
|
|
646
|
+
html += "<ol";
|
|
647
|
+
html += " " + "class" + "='" + `text-sm space-y-3` + "'";
|
|
648
|
+
html += ">";
|
|
649
|
+
html += "<li";
|
|
650
|
+
html += ">";
|
|
651
|
+
html += `1. Download the .vsix file from GitHub.`;
|
|
652
|
+
html += "</li>";
|
|
653
|
+
html += "<li";
|
|
654
|
+
html += ">";
|
|
655
|
+
html += `2. Open VS Code.`;
|
|
656
|
+
html += "</li>";
|
|
657
|
+
html += "<li";
|
|
658
|
+
html += ">";
|
|
659
|
+
html += `3. Run 'Extensions: Install from VSIX...' command.`;
|
|
660
|
+
html += "</li>";
|
|
661
|
+
html += "</ol>";
|
|
662
|
+
html += "</div>";
|
|
663
|
+
html += "</div>";
|
|
664
|
+
html += "</div>";
|
|
665
|
+
html += "</div>";
|
|
666
|
+
html += "</section>";
|
|
667
|
+
html += "</div>";
|
|
668
|
+
html += "<footer";
|
|
669
|
+
html += " " + "class" + "='" + `border-t border-zinc-900 py-20` + "'";
|
|
670
|
+
html += ">";
|
|
671
|
+
html += "<div";
|
|
672
|
+
html += " " + "class" + "='" + `container-pro text-center` + "'";
|
|
673
|
+
html += ">";
|
|
674
|
+
html += "<p";
|
|
675
|
+
html += " " + "class" + "='" + `text-sm text-zinc-600` + "'";
|
|
676
|
+
html += ">";
|
|
677
|
+
html += `Free Ultra Framework © 2026. Built for professionals. `;
|
|
678
|
+
html += "<a";
|
|
679
|
+
html += " " + "href" + "='" + `https://omar-fathy.xyz/free-docs/guide.php` + "'";
|
|
680
|
+
html += " " + "class" + "='" + `text-zinc-400 hover:text-white` + "'";
|
|
681
|
+
html += ">";
|
|
682
|
+
html += `Official Encyclopedia`;
|
|
683
|
+
html += "</a>";
|
|
684
|
+
html += "</p>";
|
|
685
|
+
html += "<div";
|
|
686
|
+
html += ">";
|
|
687
|
+
html += "</div>";
|
|
688
|
+
html += "</div>";
|
|
689
|
+
html += "</footer>";
|
|
690
|
+
html += "</div>";
|
|
691
|
+
html += "</div>";
|
|
692
|
+
return html;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function renderHeader(props = { }, helpers = { }) {
|
|
696
|
+
const e = (str) => String(str).replace(/[&<>'"]/g, m => ({'&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[m]));
|
|
697
|
+
const state = {
|
|
698
|
+
};
|
|
699
|
+
const _events = [];
|
|
700
|
+
const islandAttr = false ? ' data-component="Header"' : '';
|
|
701
|
+
let html = "<div class='free-component'" + islandAttr + " data-state='" + JSON.stringify(state).replace(/'/g, "'") + "'";
|
|
702
|
+
html += ">";
|
|
703
|
+
html += "<header";
|
|
704
|
+
html += " " + "class" + "='" + `fixed top-0 left-0 w-full z-50 bg-black border-b border-border h-16 flex items-center` + "'";
|
|
705
|
+
html += ">";
|
|
706
|
+
html += "<nav";
|
|
707
|
+
html += " " + "class" + "='" + `container-pro w-full flex items-center justify-between` + "'";
|
|
708
|
+
html += ">";
|
|
709
|
+
html += "<div";
|
|
710
|
+
html += " " + "class" + "='" + `flex items-center gap-8` + "'";
|
|
711
|
+
html += ">";
|
|
712
|
+
html += "<a";
|
|
713
|
+
html += " " + "href" + "='" + `/` + "'";
|
|
714
|
+
html += " " + "class" + "='" + `text-white font-bold tracking-tight text-lg` + "'";
|
|
715
|
+
html += ">";
|
|
716
|
+
html += `FREE ULTRA`;
|
|
717
|
+
html += "</a>";
|
|
718
|
+
html += "<div";
|
|
719
|
+
html += " " + "class" + "='" + `hidden md:flex gap-6` + "'";
|
|
720
|
+
html += ">";
|
|
721
|
+
html += "<a";
|
|
722
|
+
html += " " + "href" + "='" + `/` + "'";
|
|
723
|
+
html += " " + "class" + "='" + `nav-link font-medium` + "'";
|
|
724
|
+
html += ">";
|
|
725
|
+
html += `Home`;
|
|
726
|
+
html += "</a>";
|
|
727
|
+
html += "<a";
|
|
728
|
+
html += " " + "href" + "='" + `/docs` + "'";
|
|
729
|
+
html += " " + "class" + "='" + `nav-link font-medium` + "'";
|
|
730
|
+
html += ">";
|
|
731
|
+
html += `Documentation`;
|
|
732
|
+
html += "</a>";
|
|
733
|
+
if ((props.user)) {
|
|
734
|
+
html += "<a";
|
|
735
|
+
html += " " + "href" + "='" + `/dashboard` + "'";
|
|
736
|
+
html += " " + "class" + "='" + `nav-link font-medium` + "'";
|
|
737
|
+
html += ">";
|
|
738
|
+
html += `Dashboard`;
|
|
739
|
+
html += "</a>";
|
|
740
|
+
}
|
|
741
|
+
html += "</div>";
|
|
742
|
+
html += "</div>";
|
|
743
|
+
html += "<div";
|
|
744
|
+
html += " " + "class" + "='" + `flex items-center gap-4` + "'";
|
|
745
|
+
html += ">";
|
|
746
|
+
html += "<span";
|
|
747
|
+
html += " " + "class" + "='" + `badge-pro` + "'";
|
|
748
|
+
html += ">";
|
|
749
|
+
html += `v4.8.7`;
|
|
750
|
+
html += "</span>";
|
|
751
|
+
if ((props.user)) {
|
|
752
|
+
html += "<a";
|
|
753
|
+
html += " " + "href" + "='" + `/logout` + "'";
|
|
754
|
+
html += " " + "class" + "='" + `text-sm font-medium text-white hover:opacity-80 transition-opacity` + "'";
|
|
755
|
+
html += ">";
|
|
756
|
+
html += `Logout (\$${e(props.user.name)})`;
|
|
757
|
+
html += "</a>";
|
|
758
|
+
}
|
|
759
|
+
html += "<else";
|
|
760
|
+
html += ">";
|
|
761
|
+
html += "<a";
|
|
762
|
+
html += " " + "href" + "='" + `/login` + "'";
|
|
763
|
+
html += " " + "class" + "='" + `text-sm font-medium text-white hover:opacity-80 transition-opacity` + "'";
|
|
764
|
+
html += ">";
|
|
765
|
+
html += `Sign In`;
|
|
766
|
+
html += "</a>";
|
|
767
|
+
html += "</else>";
|
|
768
|
+
html += "</div>";
|
|
769
|
+
html += "</nav>";
|
|
770
|
+
html += "</header>";
|
|
771
|
+
html += "</div>";
|
|
772
|
+
return html;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function renderHome(props = { }, helpers = { }) {
|
|
776
|
+
const e = (str) => String(str).replace(/[&<>'"]/g, m => ({'&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[m]));
|
|
777
|
+
const state = {
|
|
778
|
+
};
|
|
779
|
+
const _events = [];
|
|
780
|
+
const islandAttr = false ? ' data-component="Home"' : '';
|
|
781
|
+
let html = "<div class='free-component'" + islandAttr + " data-state='" + JSON.stringify(state).replace(/'/g, "'") + "'";
|
|
782
|
+
html += ">";
|
|
783
|
+
html += "<main";
|
|
784
|
+
html += " " + "class" + "='" + `pt-32 pb-40 overflow-hidden` + "'";
|
|
785
|
+
html += ">";
|
|
786
|
+
html += "<section";
|
|
787
|
+
html += " " + "class" + "='" + `container-pro text-center pt-24 pb-20` + "'";
|
|
788
|
+
html += ">";
|
|
789
|
+
html += "<div";
|
|
790
|
+
html += " " + "class" + "='" + `inline-flex items-center gap-2 px-3 py-1 rounded-full border border-border bg-zinc-900/50 text-[11px] font-bold text-zinc-500 mb-8` + "'";
|
|
791
|
+
html += ">";
|
|
792
|
+
html += `VERSION 4.8.10 IS NOW LIVE`;
|
|
793
|
+
html += "<span";
|
|
794
|
+
html += " " + "class" + "='" + `w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse` + "'";
|
|
795
|
+
html += ">";
|
|
796
|
+
html += "</span>";
|
|
797
|
+
html += "</div>";
|
|
798
|
+
html += "<h1";
|
|
799
|
+
html += " " + "class" + "='" + `hero-title` + "'";
|
|
800
|
+
html += ">";
|
|
801
|
+
html += `The Engine for`;
|
|
802
|
+
html += "<br";
|
|
803
|
+
html += ">";
|
|
804
|
+
html += "</br>";
|
|
805
|
+
html += "<span";
|
|
806
|
+
html += " " + "class" + "='" + `text-zinc-600` + "'";
|
|
807
|
+
html += ">";
|
|
808
|
+
html += `High-Performance.`;
|
|
809
|
+
html += "</span>";
|
|
810
|
+
html += "</h1>";
|
|
811
|
+
html += "<p";
|
|
812
|
+
html += " " + "class" + "='" + `text-xl md:text-2xl text-zinc-500 max-w-3xl mx-auto mb-12 leading-relaxed font-medium` + "'";
|
|
813
|
+
html += ">";
|
|
814
|
+
html += `Experience the future of SSR with Islands Architecture, built-in Reactive state, and Enterprise-grade security. Small footprint, infinite power.`;
|
|
815
|
+
html += "</p>";
|
|
816
|
+
html += "<div";
|
|
817
|
+
html += " " + "class" + "='" + `flex flex-wrap justify-center gap-5` + "'";
|
|
818
|
+
html += ">";
|
|
819
|
+
html += "<a";
|
|
820
|
+
html += " " + "href" + "='" + `https://omar-fathy.xyz/free-docs/guide.php` + "'";
|
|
821
|
+
html += " " + "class" + "='" + `btn-pro` + "'";
|
|
822
|
+
html += ">";
|
|
823
|
+
html += `Read Encyclopedia`;
|
|
824
|
+
html += "<svg";
|
|
825
|
+
html += " " + "class" + "='" + `w-4 h-4` + "'";
|
|
826
|
+
html += " " + "fill" + "='" + `none` + "'";
|
|
827
|
+
html += " " + "stroke" + "='" + `currentColor` + "'";
|
|
828
|
+
html += " " + "viewBox" + "='" + `0 0 24 24` + "'";
|
|
829
|
+
html += ">";
|
|
830
|
+
html += "<path";
|
|
831
|
+
html += " " + "stroke-linecap" + "='" + `round` + "'";
|
|
832
|
+
html += " " + "stroke-linejoin" + "='" + `round` + "'";
|
|
833
|
+
html += " " + "stroke-width" + "='" + `2` + "'";
|
|
834
|
+
html += " " + "d" + "='" + `M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18 18.247 18.477 16.5 18c-1.746 0-3.332.477-4.5 1.253` + "'";
|
|
835
|
+
html += ">";
|
|
836
|
+
html += "</path>";
|
|
837
|
+
html += "</svg>";
|
|
838
|
+
html += "</a>";
|
|
839
|
+
html += "<a";
|
|
840
|
+
html += " " + "href" + "='" + `https://github.com/dev-omartolba` + "'";
|
|
841
|
+
html += " " + "class" + "='" + `btn-outline` + "'";
|
|
842
|
+
html += ">";
|
|
843
|
+
html += `View Source`;
|
|
844
|
+
html += "</a>";
|
|
845
|
+
html += "</div>";
|
|
846
|
+
html += "<div";
|
|
847
|
+
html += " " + "class" + "='" + `mt-24 max-w-4xl mx-auto relative` + "'";
|
|
848
|
+
html += ">";
|
|
849
|
+
html += "<div";
|
|
850
|
+
html += " " + "class" + "='" + `absolute -inset-1 bg-gradient-to-r from-zinc-800 to-zinc-900 rounded-2xl blur opacity-20` + "'";
|
|
851
|
+
html += ">";
|
|
852
|
+
html += "</div>";
|
|
853
|
+
html += "<pre";
|
|
854
|
+
html += " " + "class" + "='" + `relative` + "'";
|
|
855
|
+
html += ">";
|
|
856
|
+
html += "<code";
|
|
857
|
+
html += ">";
|
|
858
|
+
html += `\$ npx free-framework@latest new my-project`;
|
|
859
|
+
html += "</code>";
|
|
860
|
+
html += "</pre>";
|
|
861
|
+
html += "</div>";
|
|
862
|
+
html += "</section>";
|
|
863
|
+
html += "<section";
|
|
864
|
+
html += " " + "class" + "='" + `container-pro pt-32` + "'";
|
|
865
|
+
html += ">";
|
|
866
|
+
html += "<div";
|
|
867
|
+
html += " " + "class" + "='" + `grid md:grid-cols-3 gap-8` + "'";
|
|
868
|
+
html += ">";
|
|
869
|
+
html += "<div";
|
|
870
|
+
html += " " + "class" + "='" + `card-pro` + "'";
|
|
871
|
+
html += ">";
|
|
872
|
+
html += "<div";
|
|
873
|
+
html += " " + "class" + "='" + `w-10 h-10 rounded-lg bg-zinc-900 border border-border flex items-center justify-center mb-6` + "'";
|
|
874
|
+
html += ">";
|
|
875
|
+
html += `⚡`;
|
|
876
|
+
html += "</div>";
|
|
877
|
+
html += "<h3";
|
|
878
|
+
html += " " + "class" + "='" + `text-xl font-bold mb-3` + "'";
|
|
879
|
+
html += ">";
|
|
880
|
+
html += `Speed`;
|
|
881
|
+
html += "</h3>";
|
|
882
|
+
html += "<p";
|
|
883
|
+
html += " " + "class" + "='" + `text-base text-zinc-500 leading-relaxed` + "'";
|
|
884
|
+
html += ">";
|
|
885
|
+
html += `Zero-overhead runtime powered by HyperExpress for sub-millisecond responses.`;
|
|
886
|
+
html += "</p>";
|
|
887
|
+
html += "</div>";
|
|
888
|
+
html += "<div";
|
|
889
|
+
html += " " + "class" + "='" + `card-pro` + "'";
|
|
890
|
+
html += ">";
|
|
891
|
+
html += "<div";
|
|
892
|
+
html += " " + "class" + "='" + `w-10 h-10 rounded-lg bg-zinc-900 border border-border flex items-center justify-center mb-6` + "'";
|
|
893
|
+
html += ">";
|
|
894
|
+
html += `🏝️`;
|
|
895
|
+
html += "</div>";
|
|
896
|
+
html += "<h3";
|
|
897
|
+
html += " " + "class" + "='" + `text-xl font-bold mb-3` + "'";
|
|
898
|
+
html += ">";
|
|
899
|
+
html += `Islands`;
|
|
900
|
+
html += "</h3>";
|
|
901
|
+
html += "<p";
|
|
902
|
+
html += " " + "class" + "='" + `text-base text-zinc-500 leading-relaxed` + "'";
|
|
903
|
+
html += ">";
|
|
904
|
+
html += `Partial hydration logic that ships minimal JavaScript for maximum SEO benefits.`;
|
|
905
|
+
html += "</p>";
|
|
906
|
+
html += "</div>";
|
|
907
|
+
html += "<div";
|
|
908
|
+
html += " " + "class" + "='" + `card-pro` + "'";
|
|
909
|
+
html += ">";
|
|
910
|
+
html += "<div";
|
|
911
|
+
html += " " + "class" + "='" + `w-10 h-10 rounded-lg bg-zinc-900 border border-border flex items-center justify-center mb-6` + "'";
|
|
912
|
+
html += ">";
|
|
913
|
+
html += `🛡️`;
|
|
914
|
+
html += "</div>";
|
|
915
|
+
html += "<h3";
|
|
916
|
+
html += " " + "class" + "='" + `text-xl font-bold mb-3` + "'";
|
|
917
|
+
html += ">";
|
|
918
|
+
html += `Security`;
|
|
919
|
+
html += "</h3>";
|
|
920
|
+
html += "<p";
|
|
921
|
+
html += " " + "class" + "='" + `text-base text-zinc-500 leading-relaxed` + "'";
|
|
922
|
+
html += ">";
|
|
923
|
+
html += `Built-in DDoS protection, CSRF guards, and secure-by-default environment.`;
|
|
924
|
+
html += "</p>";
|
|
925
|
+
html += "</div>";
|
|
926
|
+
html += "</div>";
|
|
927
|
+
html += "</section>";
|
|
928
|
+
html += "</main>";
|
|
929
|
+
html += "</div>";
|
|
930
|
+
return html;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function renderLogin(props = { }, helpers = { }) {
|
|
934
|
+
const e = (str) => String(str).replace(/[&<>'"]/g, m => ({'&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[m]));
|
|
935
|
+
const state = {
|
|
936
|
+
};
|
|
937
|
+
const _events = [];
|
|
938
|
+
const islandAttr = false ? ' data-component="Login"' : '';
|
|
939
|
+
let html = "<div class='free-component'" + islandAttr + " data-state='" + JSON.stringify(state).replace(/'/g, "'") + "'";
|
|
940
|
+
html += ">";
|
|
941
|
+
html += "<div";
|
|
942
|
+
html += " " + "class" + "='" + `min-h-screen flex items-center justify-center bg-black px-8` + "'";
|
|
943
|
+
html += ">";
|
|
944
|
+
html += "<div";
|
|
945
|
+
html += " " + "class" + "='" + `w-full max-w-md` + "'";
|
|
946
|
+
html += ">";
|
|
947
|
+
html += "<div";
|
|
948
|
+
html += " " + "class" + "='" + `text-center mb-10` + "'";
|
|
949
|
+
html += ">";
|
|
950
|
+
html += "<h2";
|
|
951
|
+
html += " " + "class" + "='" + `text-3xl font-bold tracking-tight text-white` + "'";
|
|
952
|
+
html += ">";
|
|
953
|
+
html += `Welcome Back`;
|
|
954
|
+
html += "</h2>";
|
|
955
|
+
html += "<p";
|
|
956
|
+
html += " " + "class" + "='" + `text-zinc-500 mt-2` + "'";
|
|
957
|
+
html += ">";
|
|
958
|
+
html += `Sign in to your enterprise console`;
|
|
959
|
+
html += "</p>";
|
|
960
|
+
html += "</div>";
|
|
961
|
+
html += "<div";
|
|
962
|
+
html += " " + "class" + "='" + `card-pro` + "'";
|
|
963
|
+
html += ">";
|
|
964
|
+
html += "<form";
|
|
965
|
+
html += " " + "class" + "='" + `space-y-6` + "'";
|
|
966
|
+
html += ">";
|
|
967
|
+
html += "<div";
|
|
968
|
+
html += ">";
|
|
969
|
+
html += "<label";
|
|
970
|
+
html += " " + "class" + "='" + `block text-sm font-medium text-zinc-400 mb-2` + "'";
|
|
971
|
+
html += ">";
|
|
972
|
+
html += `Email Address`;
|
|
973
|
+
html += "</label>";
|
|
974
|
+
html += "<input";
|
|
975
|
+
html += " " + "type" + "='" + `email` + "'";
|
|
976
|
+
html += " " + "class" + "='" + `w-full bg-black border border-zinc-800 rounded-lg px-4 py-3 text-white focus:border-white transition-colors outline-none` + "'";
|
|
977
|
+
html += " " + "placeholder" + "='" + `name@company.com` + "'";
|
|
978
|
+
html += ">";
|
|
979
|
+
html += "</input>";
|
|
980
|
+
html += "</div>";
|
|
981
|
+
html += "<div";
|
|
982
|
+
html += ">";
|
|
983
|
+
html += "<label";
|
|
984
|
+
html += " " + "class" + "='" + `block text-sm font-medium text-zinc-400 mb-2` + "'";
|
|
985
|
+
html += ">";
|
|
986
|
+
html += `Password`;
|
|
987
|
+
html += "</label>";
|
|
988
|
+
html += "<input";
|
|
989
|
+
html += " " + "type" + "='" + `password` + "'";
|
|
990
|
+
html += " " + "class" + "='" + `w-full bg-black border border-zinc-800 rounded-lg px-4 py-3 text-white focus:border-white transition-colors outline-none` + "'";
|
|
991
|
+
html += " " + "placeholder" + "='" + `••••••••` + "'";
|
|
992
|
+
html += ">";
|
|
993
|
+
html += "</input>";
|
|
994
|
+
html += "</div>";
|
|
995
|
+
html += "<button";
|
|
996
|
+
html += " " + "type" + "='" + `submit` + "'";
|
|
997
|
+
html += " " + "class" + "='" + `btn-pro w-full justify-center py-3` + "'";
|
|
998
|
+
html += ">";
|
|
999
|
+
html += `Continue`;
|
|
1000
|
+
html += "</button>";
|
|
1001
|
+
html += "</form>";
|
|
1002
|
+
html += "</div>";
|
|
1003
|
+
html += "<p";
|
|
1004
|
+
html += " " + "class" + "='" + `text-center text-zinc-500 mt-8 text-sm` + "'";
|
|
1005
|
+
html += ">";
|
|
1006
|
+
html += `Don't have an account? `;
|
|
1007
|
+
html += "<a";
|
|
1008
|
+
html += " " + "href" + "='" + `/register` + "'";
|
|
1009
|
+
html += " " + "class" + "='" + `text-white hover:underline` + "'";
|
|
1010
|
+
html += ">";
|
|
1011
|
+
html += `Create one`;
|
|
1012
|
+
html += "</a>";
|
|
1013
|
+
html += "</p>";
|
|
1014
|
+
html += "</div>";
|
|
1015
|
+
html += "</div>";
|
|
1016
|
+
html += "</div>";
|
|
1017
|
+
return html;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const mwDir = path.join(process.cwd(), 'app/middleware');
|
|
1021
|
+
if (fs.existsSync(mwDir)) {
|
|
1022
|
+
fs.readdirSync(mwDir).forEach(file => {
|
|
1023
|
+
if (file.endsWith('.js')) {
|
|
1024
|
+
const name = file.replace('.js', '');
|
|
1025
|
+
server.registerMiddleware(name, require(path.join(mwDir, file)));
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
if (server.namedMiddlewares['auth']) server.namedMiddlewares['AuthGuard'] = server.namedMiddlewares['auth'];
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
server.route("post", "/api/register", [], async (req, res) => {
|
|
1033
|
+
const user = (res.context && res.context.user) ? res.context.user : null;
|
|
1034
|
+
let body = {};
|
|
1035
|
+
try {
|
|
1036
|
+
if (req.headers['content-length'] > 0) {
|
|
1037
|
+
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
1038
|
+
body = await req.json().catch(() => ({}));
|
|
1039
|
+
} else {
|
|
1040
|
+
body = await req.urlencoded().catch(() => ({}));
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
} catch(e) {}
|
|
1044
|
+
|
|
1045
|
+
const result = await (async () => {
|
|
1046
|
+
|
|
1047
|
+
})();
|
|
1048
|
+
|
|
1049
|
+
if (res.headersSent) return;
|
|
1050
|
+
|
|
1051
|
+
// If it's a PAGE route (has a view), render HTML
|
|
1052
|
+
if (true) {
|
|
1053
|
+
const stylesRegistry = {"Counter":"","Dashboard":".dashboard-container { padding: 2rem; background: #f3f4f6; min-height: 100vh; }\n .card { background: white; padding: 1.5rem; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }","Docs":"","Header":"","Home":"","Login":""};
|
|
1054
|
+
const helpers = {
|
|
1055
|
+
renderComponent: (name, props = {}) => {
|
|
1056
|
+
server.renderRegistry = server.renderRegistry || {
|
|
1057
|
+
Counter: renderCounter,
|
|
1058
|
+
Dashboard: renderDashboard,
|
|
1059
|
+
Docs: renderDocs,
|
|
1060
|
+
Header: renderHeader,
|
|
1061
|
+
Home: renderHome,
|
|
1062
|
+
Login: renderLogin
|
|
1063
|
+
};
|
|
1064
|
+
return server.renderRegistry[name] ? server.renderRegistry[name](props, helpers) : '<!-- Component ' + name + ' not found -->';
|
|
1065
|
+
},
|
|
1066
|
+
getScopedCSS: (used = []) => {
|
|
1067
|
+
return used.map(name => stylesRegistry[name] || '').join('\n');
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
const props = result || (res.context && res.context.props ? res.context.props : {});
|
|
1071
|
+
const pageHtml = helpers.renderComponent('Register', props);
|
|
1072
|
+
const scopedCSS = helpers.getScopedCSS(['Register', 'Header']);
|
|
1073
|
+
if (req.headers['x-free-partial']) {
|
|
1074
|
+
return res.json({ title: 'Free Ultra | Register', content: pageHtml, url: req.url, css: scopedCSS });
|
|
1075
|
+
}
|
|
1076
|
+
const headerHtml = helpers.renderComponent('Header', props);
|
|
1077
|
+
const fullHTML = `<!DOCTYPE html>
|
|
1078
|
+
<html lang="en">
|
|
1079
|
+
<head>
|
|
1080
|
+
<meta charset="UTF-8">
|
|
1081
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1082
|
+
<title>Free Ultra | Register</title>
|
|
1083
|
+
<meta name="csrf-token" content="${res.context.csrfToken || ''}">
|
|
1084
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1085
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1086
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&display=swap" rel="stylesheet">
|
|
1087
|
+
<link rel="stylesheet" href="/css/app.css">
|
|
1088
|
+
<style>
|
|
1089
|
+
:root { --primary:#fff; --bg:#000; --border:#27272a; }
|
|
1090
|
+
body { margin:0; font-family:'Inter', system-ui, sans-serif; background:var(--bg); color:#fff; -webkit-font-smoothing:antialiased; }
|
|
1091
|
+
${scopedCSS}
|
|
1092
|
+
</style>
|
|
1093
|
+
<script src="/free-runtime.js" defer></script>
|
|
1094
|
+
</head>
|
|
1095
|
+
<body>
|
|
1096
|
+
${headerHtml}
|
|
1097
|
+
<main id="free-app-root">
|
|
1098
|
+
${pageHtml}
|
|
1099
|
+
</main>
|
|
1100
|
+
${process.env.NODE_ENV !== 'production' ? `
|
|
1101
|
+
<script>
|
|
1102
|
+
(function connectHMR() {
|
|
1103
|
+
const ws = new WebSocket('ws://' + location.host + '/_free_hmr');
|
|
1104
|
+
ws.onclose = () => {
|
|
1105
|
+
setTimeout(() => fetch('/').then(() => window.location.reload()).catch(connectHMR), 500);
|
|
1106
|
+
};
|
|
1107
|
+
console.log('[Free Engine] ⚡ HMR Active');
|
|
1108
|
+
})();
|
|
1109
|
+
</script>` : ''}
|
|
1110
|
+
</body>
|
|
1111
|
+
</html>`;
|
|
1112
|
+
res.header('Content-Type', 'text/html').send(fullHTML);
|
|
1113
|
+
} else {
|
|
1114
|
+
res.json({ success: true, result });
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
server.route("post", "/api/login", [], async (req, res) => {
|
|
1119
|
+
const user = (res.context && res.context.user) ? res.context.user : null;
|
|
1120
|
+
let body = {};
|
|
1121
|
+
try {
|
|
1122
|
+
if (req.headers['content-length'] > 0) {
|
|
1123
|
+
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
1124
|
+
body = await req.json().catch(() => ({}));
|
|
1125
|
+
} else {
|
|
1126
|
+
body = await req.urlencoded().catch(() => ({}));
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
} catch(e) {}
|
|
1130
|
+
|
|
1131
|
+
const result = await (async () => {
|
|
1132
|
+
|
|
1133
|
+
})();
|
|
1134
|
+
|
|
1135
|
+
if (res.headersSent) return;
|
|
1136
|
+
|
|
1137
|
+
// If it's a PAGE route (has a view), render HTML
|
|
1138
|
+
if (true) {
|
|
1139
|
+
const stylesRegistry = {"Counter":"","Dashboard":".dashboard-container { padding: 2rem; background: #f3f4f6; min-height: 100vh; }\n .card { background: white; padding: 1.5rem; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }","Docs":"","Header":"","Home":"","Login":""};
|
|
1140
|
+
const helpers = {
|
|
1141
|
+
renderComponent: (name, props = {}) => {
|
|
1142
|
+
server.renderRegistry = server.renderRegistry || {
|
|
1143
|
+
Counter: renderCounter,
|
|
1144
|
+
Dashboard: renderDashboard,
|
|
1145
|
+
Docs: renderDocs,
|
|
1146
|
+
Header: renderHeader,
|
|
1147
|
+
Home: renderHome,
|
|
1148
|
+
Login: renderLogin
|
|
1149
|
+
};
|
|
1150
|
+
return server.renderRegistry[name] ? server.renderRegistry[name](props, helpers) : '<!-- Component ' + name + ' not found -->';
|
|
1151
|
+
},
|
|
1152
|
+
getScopedCSS: (used = []) => {
|
|
1153
|
+
return used.map(name => stylesRegistry[name] || '').join('\n');
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
const props = result || (res.context && res.context.props ? res.context.props : {});
|
|
1157
|
+
const pageHtml = helpers.renderComponent('Login', props);
|
|
1158
|
+
const scopedCSS = helpers.getScopedCSS(['Login', 'Header']);
|
|
1159
|
+
if (req.headers['x-free-partial']) {
|
|
1160
|
+
return res.json({ title: 'Free Ultra | Login', content: pageHtml, url: req.url, css: scopedCSS });
|
|
1161
|
+
}
|
|
1162
|
+
const headerHtml = helpers.renderComponent('Header', props);
|
|
1163
|
+
const fullHTML = `<!DOCTYPE html>
|
|
1164
|
+
<html lang="en">
|
|
1165
|
+
<head>
|
|
1166
|
+
<meta charset="UTF-8">
|
|
1167
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1168
|
+
<title>Free Ultra | Login</title>
|
|
1169
|
+
<meta name="csrf-token" content="${res.context.csrfToken || ''}">
|
|
1170
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1171
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1172
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&display=swap" rel="stylesheet">
|
|
1173
|
+
<link rel="stylesheet" href="/css/app.css">
|
|
1174
|
+
<style>
|
|
1175
|
+
:root { --primary:#fff; --bg:#000; --border:#27272a; }
|
|
1176
|
+
body { margin:0; font-family:'Inter', system-ui, sans-serif; background:var(--bg); color:#fff; -webkit-font-smoothing:antialiased; }
|
|
1177
|
+
${scopedCSS}
|
|
1178
|
+
</style>
|
|
1179
|
+
<script src="/free-runtime.js" defer></script>
|
|
1180
|
+
</head>
|
|
1181
|
+
<body>
|
|
1182
|
+
${headerHtml}
|
|
1183
|
+
<main id="free-app-root">
|
|
1184
|
+
${pageHtml}
|
|
1185
|
+
</main>
|
|
1186
|
+
${process.env.NODE_ENV !== 'production' ? `
|
|
1187
|
+
<script>
|
|
1188
|
+
(function connectHMR() {
|
|
1189
|
+
const ws = new WebSocket('ws://' + location.host + '/_free_hmr');
|
|
1190
|
+
ws.onclose = () => {
|
|
1191
|
+
setTimeout(() => fetch('/').then(() => window.location.reload()).catch(connectHMR), 500);
|
|
1192
|
+
};
|
|
1193
|
+
console.log('[Free Engine] ⚡ HMR Active');
|
|
1194
|
+
})();
|
|
1195
|
+
</script>` : ''}
|
|
1196
|
+
</body>
|
|
1197
|
+
</html>`;
|
|
1198
|
+
res.header('Content-Type', 'text/html').send(fullHTML);
|
|
1199
|
+
} else {
|
|
1200
|
+
res.json({ success: true, result });
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
server.route("get", "/", [], async (req, res) => {
|
|
1205
|
+
const user = (res.context && res.context.user) ? res.context.user : null;
|
|
1206
|
+
let body = {};
|
|
1207
|
+
try {
|
|
1208
|
+
if (req.headers['content-length'] > 0) {
|
|
1209
|
+
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
1210
|
+
body = await req.json().catch(() => ({}));
|
|
1211
|
+
} else {
|
|
1212
|
+
body = await req.urlencoded().catch(() => ({}));
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
} catch(e) {}
|
|
1216
|
+
|
|
1217
|
+
const result = await (async () => {
|
|
1218
|
+
|
|
1219
|
+
})();
|
|
1220
|
+
|
|
1221
|
+
if (res.headersSent) return;
|
|
1222
|
+
|
|
1223
|
+
// If it's a PAGE route (has a view), render HTML
|
|
1224
|
+
if (true) {
|
|
1225
|
+
const stylesRegistry = {"Counter":"","Dashboard":".dashboard-container { padding: 2rem; background: #f3f4f6; min-height: 100vh; }\n .card { background: white; padding: 1.5rem; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }","Docs":"","Header":"","Home":"","Login":""};
|
|
1226
|
+
const helpers = {
|
|
1227
|
+
renderComponent: (name, props = {}) => {
|
|
1228
|
+
server.renderRegistry = server.renderRegistry || {
|
|
1229
|
+
Counter: renderCounter,
|
|
1230
|
+
Dashboard: renderDashboard,
|
|
1231
|
+
Docs: renderDocs,
|
|
1232
|
+
Header: renderHeader,
|
|
1233
|
+
Home: renderHome,
|
|
1234
|
+
Login: renderLogin
|
|
1235
|
+
};
|
|
1236
|
+
return server.renderRegistry[name] ? server.renderRegistry[name](props, helpers) : '<!-- Component ' + name + ' not found -->';
|
|
1237
|
+
},
|
|
1238
|
+
getScopedCSS: (used = []) => {
|
|
1239
|
+
return used.map(name => stylesRegistry[name] || '').join('\n');
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
const props = result || (res.context && res.context.props ? res.context.props : {});
|
|
1243
|
+
const pageHtml = helpers.renderComponent('Home', props);
|
|
1244
|
+
const scopedCSS = helpers.getScopedCSS(['Home', 'Header']);
|
|
1245
|
+
if (req.headers['x-free-partial']) {
|
|
1246
|
+
return res.json({ title: 'Free Ultra | Home', content: pageHtml, url: req.url, css: scopedCSS });
|
|
1247
|
+
}
|
|
1248
|
+
const headerHtml = helpers.renderComponent('Header', props);
|
|
1249
|
+
const fullHTML = `<!DOCTYPE html>
|
|
1250
|
+
<html lang="en">
|
|
1251
|
+
<head>
|
|
1252
|
+
<meta charset="UTF-8">
|
|
1253
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1254
|
+
<title>Free Ultra | Home</title>
|
|
1255
|
+
<meta name="csrf-token" content="${res.context.csrfToken || ''}">
|
|
1256
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1257
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1258
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&display=swap" rel="stylesheet">
|
|
1259
|
+
<link rel="stylesheet" href="/css/app.css">
|
|
1260
|
+
<style>
|
|
1261
|
+
:root { --primary:#fff; --bg:#000; --border:#27272a; }
|
|
1262
|
+
body { margin:0; font-family:'Inter', system-ui, sans-serif; background:var(--bg); color:#fff; -webkit-font-smoothing:antialiased; }
|
|
1263
|
+
${scopedCSS}
|
|
1264
|
+
</style>
|
|
1265
|
+
<script src="/free-runtime.js" defer></script>
|
|
1266
|
+
</head>
|
|
1267
|
+
<body>
|
|
1268
|
+
${headerHtml}
|
|
1269
|
+
<main id="free-app-root">
|
|
1270
|
+
${pageHtml}
|
|
1271
|
+
</main>
|
|
1272
|
+
${process.env.NODE_ENV !== 'production' ? `
|
|
1273
|
+
<script>
|
|
1274
|
+
(function connectHMR() {
|
|
1275
|
+
const ws = new WebSocket('ws://' + location.host + '/_free_hmr');
|
|
1276
|
+
ws.onclose = () => {
|
|
1277
|
+
setTimeout(() => fetch('/').then(() => window.location.reload()).catch(connectHMR), 500);
|
|
1278
|
+
};
|
|
1279
|
+
console.log('[Free Engine] ⚡ HMR Active');
|
|
1280
|
+
})();
|
|
1281
|
+
</script>` : ''}
|
|
1282
|
+
</body>
|
|
1283
|
+
</html>`;
|
|
1284
|
+
res.header('Content-Type', 'text/html').send(fullHTML);
|
|
1285
|
+
} else {
|
|
1286
|
+
res.json({ success: true, result });
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
server.route("get", "/docs", [], async (req, res) => {
|
|
1291
|
+
const user = (res.context && res.context.user) ? res.context.user : null;
|
|
1292
|
+
let body = {};
|
|
1293
|
+
try {
|
|
1294
|
+
if (req.headers['content-length'] > 0) {
|
|
1295
|
+
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
1296
|
+
body = await req.json().catch(() => ({}));
|
|
1297
|
+
} else {
|
|
1298
|
+
body = await req.urlencoded().catch(() => ({}));
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
} catch(e) {}
|
|
1302
|
+
|
|
1303
|
+
const result = await (async () => {
|
|
1304
|
+
|
|
1305
|
+
})();
|
|
1306
|
+
|
|
1307
|
+
if (res.headersSent) return;
|
|
1308
|
+
|
|
1309
|
+
// If it's a PAGE route (has a view), render HTML
|
|
1310
|
+
if (true) {
|
|
1311
|
+
const stylesRegistry = {"Counter":"","Dashboard":".dashboard-container { padding: 2rem; background: #f3f4f6; min-height: 100vh; }\n .card { background: white; padding: 1.5rem; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }","Docs":"","Header":"","Home":"","Login":""};
|
|
1312
|
+
const helpers = {
|
|
1313
|
+
renderComponent: (name, props = {}) => {
|
|
1314
|
+
server.renderRegistry = server.renderRegistry || {
|
|
1315
|
+
Counter: renderCounter,
|
|
1316
|
+
Dashboard: renderDashboard,
|
|
1317
|
+
Docs: renderDocs,
|
|
1318
|
+
Header: renderHeader,
|
|
1319
|
+
Home: renderHome,
|
|
1320
|
+
Login: renderLogin
|
|
1321
|
+
};
|
|
1322
|
+
return server.renderRegistry[name] ? server.renderRegistry[name](props, helpers) : '<!-- Component ' + name + ' not found -->';
|
|
1323
|
+
},
|
|
1324
|
+
getScopedCSS: (used = []) => {
|
|
1325
|
+
return used.map(name => stylesRegistry[name] || '').join('\n');
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
const props = result || (res.context && res.context.props ? res.context.props : {});
|
|
1329
|
+
const pageHtml = helpers.renderComponent('Docs', props);
|
|
1330
|
+
const scopedCSS = helpers.getScopedCSS(['Docs', 'Header']);
|
|
1331
|
+
if (req.headers['x-free-partial']) {
|
|
1332
|
+
return res.json({ title: 'Free Ultra | Docs', content: pageHtml, url: req.url, css: scopedCSS });
|
|
1333
|
+
}
|
|
1334
|
+
const headerHtml = helpers.renderComponent('Header', props);
|
|
1335
|
+
const fullHTML = `<!DOCTYPE html>
|
|
1336
|
+
<html lang="en">
|
|
1337
|
+
<head>
|
|
1338
|
+
<meta charset="UTF-8">
|
|
1339
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1340
|
+
<title>Free Ultra | Docs</title>
|
|
1341
|
+
<meta name="csrf-token" content="${res.context.csrfToken || ''}">
|
|
1342
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1343
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1344
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&display=swap" rel="stylesheet">
|
|
1345
|
+
<link rel="stylesheet" href="/css/app.css">
|
|
1346
|
+
<style>
|
|
1347
|
+
:root { --primary:#fff; --bg:#000; --border:#27272a; }
|
|
1348
|
+
body { margin:0; font-family:'Inter', system-ui, sans-serif; background:var(--bg); color:#fff; -webkit-font-smoothing:antialiased; }
|
|
1349
|
+
${scopedCSS}
|
|
1350
|
+
</style>
|
|
1351
|
+
<script src="/free-runtime.js" defer></script>
|
|
1352
|
+
</head>
|
|
1353
|
+
<body>
|
|
1354
|
+
${headerHtml}
|
|
1355
|
+
<main id="free-app-root">
|
|
1356
|
+
${pageHtml}
|
|
1357
|
+
</main>
|
|
1358
|
+
${process.env.NODE_ENV !== 'production' ? `
|
|
1359
|
+
<script>
|
|
1360
|
+
(function connectHMR() {
|
|
1361
|
+
const ws = new WebSocket('ws://' + location.host + '/_free_hmr');
|
|
1362
|
+
ws.onclose = () => {
|
|
1363
|
+
setTimeout(() => fetch('/').then(() => window.location.reload()).catch(connectHMR), 500);
|
|
1364
|
+
};
|
|
1365
|
+
console.log('[Free Engine] ⚡ HMR Active');
|
|
1366
|
+
})();
|
|
1367
|
+
</script>` : ''}
|
|
1368
|
+
</body>
|
|
1369
|
+
</html>`;
|
|
1370
|
+
res.header('Content-Type', 'text/html').send(fullHTML);
|
|
1371
|
+
} else {
|
|
1372
|
+
res.json({ success: true, result });
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
server.route("get", "/login", [], async (req, res) => {
|
|
1377
|
+
const user = (res.context && res.context.user) ? res.context.user : null;
|
|
1378
|
+
let body = {};
|
|
1379
|
+
try {
|
|
1380
|
+
if (req.headers['content-length'] > 0) {
|
|
1381
|
+
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
1382
|
+
body = await req.json().catch(() => ({}));
|
|
1383
|
+
} else {
|
|
1384
|
+
body = await req.urlencoded().catch(() => ({}));
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
} catch(e) {}
|
|
1388
|
+
|
|
1389
|
+
const result = await (async () => {
|
|
1390
|
+
|
|
1391
|
+
})();
|
|
1392
|
+
|
|
1393
|
+
if (res.headersSent) return;
|
|
1394
|
+
|
|
1395
|
+
// If it's a PAGE route (has a view), render HTML
|
|
1396
|
+
if (true) {
|
|
1397
|
+
const stylesRegistry = {"Counter":"","Dashboard":".dashboard-container { padding: 2rem; background: #f3f4f6; min-height: 100vh; }\n .card { background: white; padding: 1.5rem; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }","Docs":"","Header":"","Home":"","Login":""};
|
|
1398
|
+
const helpers = {
|
|
1399
|
+
renderComponent: (name, props = {}) => {
|
|
1400
|
+
server.renderRegistry = server.renderRegistry || {
|
|
1401
|
+
Counter: renderCounter,
|
|
1402
|
+
Dashboard: renderDashboard,
|
|
1403
|
+
Docs: renderDocs,
|
|
1404
|
+
Header: renderHeader,
|
|
1405
|
+
Home: renderHome,
|
|
1406
|
+
Login: renderLogin
|
|
1407
|
+
};
|
|
1408
|
+
return server.renderRegistry[name] ? server.renderRegistry[name](props, helpers) : '<!-- Component ' + name + ' not found -->';
|
|
1409
|
+
},
|
|
1410
|
+
getScopedCSS: (used = []) => {
|
|
1411
|
+
return used.map(name => stylesRegistry[name] || '').join('\n');
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
const props = result || (res.context && res.context.props ? res.context.props : {});
|
|
1415
|
+
const pageHtml = helpers.renderComponent('Login', props);
|
|
1416
|
+
const scopedCSS = helpers.getScopedCSS(['Login', 'Header']);
|
|
1417
|
+
if (req.headers['x-free-partial']) {
|
|
1418
|
+
return res.json({ title: 'Free Ultra | Login', content: pageHtml, url: req.url, css: scopedCSS });
|
|
1419
|
+
}
|
|
1420
|
+
const headerHtml = helpers.renderComponent('Header', props);
|
|
1421
|
+
const fullHTML = `<!DOCTYPE html>
|
|
1422
|
+
<html lang="en">
|
|
1423
|
+
<head>
|
|
1424
|
+
<meta charset="UTF-8">
|
|
1425
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1426
|
+
<title>Free Ultra | Login</title>
|
|
1427
|
+
<meta name="csrf-token" content="${res.context.csrfToken || ''}">
|
|
1428
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1429
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1430
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&display=swap" rel="stylesheet">
|
|
1431
|
+
<link rel="stylesheet" href="/css/app.css">
|
|
1432
|
+
<style>
|
|
1433
|
+
:root { --primary:#fff; --bg:#000; --border:#27272a; }
|
|
1434
|
+
body { margin:0; font-family:'Inter', system-ui, sans-serif; background:var(--bg); color:#fff; -webkit-font-smoothing:antialiased; }
|
|
1435
|
+
${scopedCSS}
|
|
1436
|
+
</style>
|
|
1437
|
+
<script src="/free-runtime.js" defer></script>
|
|
1438
|
+
</head>
|
|
1439
|
+
<body>
|
|
1440
|
+
${headerHtml}
|
|
1441
|
+
<main id="free-app-root">
|
|
1442
|
+
${pageHtml}
|
|
1443
|
+
</main>
|
|
1444
|
+
${process.env.NODE_ENV !== 'production' ? `
|
|
1445
|
+
<script>
|
|
1446
|
+
(function connectHMR() {
|
|
1447
|
+
const ws = new WebSocket('ws://' + location.host + '/_free_hmr');
|
|
1448
|
+
ws.onclose = () => {
|
|
1449
|
+
setTimeout(() => fetch('/').then(() => window.location.reload()).catch(connectHMR), 500);
|
|
1450
|
+
};
|
|
1451
|
+
console.log('[Free Engine] ⚡ HMR Active');
|
|
1452
|
+
})();
|
|
1453
|
+
</script>` : ''}
|
|
1454
|
+
</body>
|
|
1455
|
+
</html>`;
|
|
1456
|
+
res.header('Content-Type', 'text/html').send(fullHTML);
|
|
1457
|
+
} else {
|
|
1458
|
+
res.json({ success: true, result });
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
server.route("get", "/dashboard", [], async (req, res) => {
|
|
1463
|
+
const user = (res.context && res.context.user) ? res.context.user : null;
|
|
1464
|
+
let body = {};
|
|
1465
|
+
try {
|
|
1466
|
+
if (req.headers['content-length'] > 0) {
|
|
1467
|
+
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
1468
|
+
body = await req.json().catch(() => ({}));
|
|
1469
|
+
} else {
|
|
1470
|
+
body = await req.urlencoded().catch(() => ({}));
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
} catch(e) {}
|
|
1474
|
+
|
|
1475
|
+
const result = await (async () => {
|
|
1476
|
+
|
|
1477
|
+
})();
|
|
1478
|
+
|
|
1479
|
+
if (res.headersSent) return;
|
|
1480
|
+
|
|
1481
|
+
// If it's a PAGE route (has a view), render HTML
|
|
1482
|
+
if (false) {
|
|
1483
|
+
const stylesRegistry = {"Counter":"","Dashboard":".dashboard-container { padding: 2rem; background: #f3f4f6; min-height: 100vh; }\n .card { background: white; padding: 1.5rem; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }","Docs":"","Header":"","Home":"","Login":""};
|
|
1484
|
+
const helpers = {
|
|
1485
|
+
renderComponent: (name, props = {}) => {
|
|
1486
|
+
server.renderRegistry = server.renderRegistry || {
|
|
1487
|
+
Counter: renderCounter,
|
|
1488
|
+
Dashboard: renderDashboard,
|
|
1489
|
+
Docs: renderDocs,
|
|
1490
|
+
Header: renderHeader,
|
|
1491
|
+
Home: renderHome,
|
|
1492
|
+
Login: renderLogin
|
|
1493
|
+
};
|
|
1494
|
+
return server.renderRegistry[name] ? server.renderRegistry[name](props, helpers) : '<!-- Component ' + name + ' not found -->';
|
|
1495
|
+
},
|
|
1496
|
+
getScopedCSS: (used = []) => {
|
|
1497
|
+
return used.map(name => stylesRegistry[name] || '').join('\n');
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
const props = result || (res.context && res.context.props ? res.context.props : {});
|
|
1501
|
+
const pageHtml = helpers.renderComponent('Home', props);
|
|
1502
|
+
const scopedCSS = helpers.getScopedCSS(['Home', 'Header']);
|
|
1503
|
+
if (req.headers['x-free-partial']) {
|
|
1504
|
+
return res.json({ title: 'Free Ultra | Home', content: pageHtml, url: req.url, css: scopedCSS });
|
|
1505
|
+
}
|
|
1506
|
+
const headerHtml = helpers.renderComponent('Header', props);
|
|
1507
|
+
const fullHTML = `<!DOCTYPE html>
|
|
1508
|
+
<html lang="en">
|
|
1509
|
+
<head>
|
|
1510
|
+
<meta charset="UTF-8">
|
|
1511
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1512
|
+
<title>Free Ultra | Home</title>
|
|
1513
|
+
<meta name="csrf-token" content="${res.context.csrfToken || ''}">
|
|
1514
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1515
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1516
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&display=swap" rel="stylesheet">
|
|
1517
|
+
<link rel="stylesheet" href="/css/app.css">
|
|
1518
|
+
<style>
|
|
1519
|
+
:root { --primary:#fff; --bg:#000; --border:#27272a; }
|
|
1520
|
+
body { margin:0; font-family:'Inter', system-ui, sans-serif; background:var(--bg); color:#fff; -webkit-font-smoothing:antialiased; }
|
|
1521
|
+
${scopedCSS}
|
|
1522
|
+
</style>
|
|
1523
|
+
<script src="/free-runtime.js" defer></script>
|
|
1524
|
+
</head>
|
|
1525
|
+
<body>
|
|
1526
|
+
${headerHtml}
|
|
1527
|
+
<main id="free-app-root">
|
|
1528
|
+
${pageHtml}
|
|
1529
|
+
</main>
|
|
1530
|
+
${process.env.NODE_ENV !== 'production' ? `
|
|
1531
|
+
<script>
|
|
1532
|
+
(function connectHMR() {
|
|
1533
|
+
const ws = new WebSocket('ws://' + location.host + '/_free_hmr');
|
|
1534
|
+
ws.onclose = () => {
|
|
1535
|
+
setTimeout(() => fetch('/').then(() => window.location.reload()).catch(connectHMR), 500);
|
|
1536
|
+
};
|
|
1537
|
+
console.log('[Free Engine] ⚡ HMR Active');
|
|
1538
|
+
})();
|
|
1539
|
+
</script>` : ''}
|
|
1540
|
+
</body>
|
|
1541
|
+
</html>`;
|
|
1542
|
+
res.header('Content-Type', 'text/html').send(fullHTML);
|
|
1543
|
+
} else {
|
|
1544
|
+
res.json({ success: true, result });
|
|
1545
|
+
}
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
if (require.main === module) {
|
|
1549
|
+
ORM.migrate(modelsRegistry).then(() => {
|
|
1550
|
+
server.start(process.env.PORT || 4000);
|
|
1551
|
+
}).catch(err => console.error(err));
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
module.exports = {...modelsRegistry, server, modelsRegistry, ORM};
|
|
1555
|
+
|