free-framework 4.4.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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +198 -0
  3. package/bin/free.js +118 -0
  4. package/cli/commands/build.js +124 -0
  5. package/cli/commands/deploy.js +143 -0
  6. package/cli/commands/devtools.js +210 -0
  7. package/cli/commands/doctor.js +72 -0
  8. package/cli/commands/install.js +28 -0
  9. package/cli/commands/make.js +74 -0
  10. package/cli/commands/migrate.js +67 -0
  11. package/cli/commands/new.js +54 -0
  12. package/cli/commands/serve.js +73 -0
  13. package/cli/commands/test.js +57 -0
  14. package/compiler/analyzer.js +102 -0
  15. package/compiler/generator.js +386 -0
  16. package/compiler/lexer.js +166 -0
  17. package/compiler/parser.js +410 -0
  18. package/database/model.js +6 -0
  19. package/database/orm.js +379 -0
  20. package/database/query-builder.js +179 -0
  21. package/index.js +51 -0
  22. package/package.json +80 -0
  23. package/plugins/auth.js +212 -0
  24. package/plugins/cache.js +85 -0
  25. package/plugins/chat.js +32 -0
  26. package/plugins/mail.js +53 -0
  27. package/plugins/metrics.js +126 -0
  28. package/plugins/payments.js +59 -0
  29. package/plugins/queue.js +139 -0
  30. package/plugins/search.js +51 -0
  31. package/plugins/storage.js +123 -0
  32. package/plugins/upload.js +62 -0
  33. package/router/router.js +57 -0
  34. package/runtime/app.js +14 -0
  35. package/runtime/client.js +254 -0
  36. package/runtime/cluster.js +35 -0
  37. package/runtime/edge.js +62 -0
  38. package/runtime/middleware/maintenance.js +54 -0
  39. package/runtime/middleware/security.js +30 -0
  40. package/runtime/server.js +130 -0
  41. package/runtime/validator.js +102 -0
  42. package/runtime/views/error.free +104 -0
  43. package/runtime/views/maintenance.free +0 -0
  44. package/template-engine/renderer.js +24 -0
  45. package/templates/app-template/.env +23 -0
  46. package/templates/app-template/app/Exceptions/Handler.js +65 -0
  47. package/templates/app-template/app/Http/Controllers/AuthController.free +91 -0
  48. package/templates/app-template/app/Http/Middleware/AuthGuard.js +46 -0
  49. package/templates/app-template/app/Services/Storage.js +75 -0
  50. package/templates/app-template/app/Services/Validator.js +91 -0
  51. package/templates/app-template/app/controllers/AuthController.free +42 -0
  52. package/templates/app-template/app/middleware/auth.js +25 -0
  53. package/templates/app-template/app/models/User.free +32 -0
  54. package/templates/app-template/app/routes.free +12 -0
  55. package/templates/app-template/app/styles.css +23 -0
  56. package/templates/app-template/app/views/counter.free +23 -0
  57. package/templates/app-template/app/views/header.free +13 -0
  58. package/templates/app-template/config/app.js +32 -0
  59. package/templates/app-template/config/auth.js +39 -0
  60. package/templates/app-template/config/database.js +54 -0
  61. package/templates/app-template/package.json +28 -0
  62. package/templates/app-template/resources/css/app.css +11 -0
  63. package/templates/app-template/resources/views/dashboard.free +25 -0
  64. package/templates/app-template/resources/views/home.free +26 -0
  65. package/templates/app-template/routes/api.free +22 -0
  66. package/templates/app-template/routes/web.free +25 -0
  67. package/templates/app-template/tailwind.config.js +21 -0
  68. package/templates/app-template/views/about.ejs +47 -0
  69. package/templates/app-template/views/home.ejs +52 -0
  70. package/templates/auth/login.html +144 -0
  71. package/templates/auth/register.html +146 -0
  72. package/utils/logger.js +20 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * cli/commands/devtools.js
3
+ * Free DevTools Dashboard โ€“ Enhanced with live system telemetry.
4
+ */
5
+ const path = require("path");
6
+ const fs = require("fs");
7
+ const http = require("http");
8
+ const { tokenize } = require("../../compiler/lexer");
9
+ const { parse } = require("../../compiler/parser");
10
+
11
+ module.exports = function () {
12
+ const appDir = path.join(process.cwd(), "app");
13
+
14
+ if (!fs.existsSync(appDir)) {
15
+ console.error("โŒ Not inside a Free Framework project. 'app' directory missing.");
16
+ process.exit(1);
17
+ }
18
+
19
+ console.log("๐Ÿ” Scanning .free project for DevTools...");
20
+
21
+ const freeFiles = fs.readdirSync(appDir).filter(f => f.endsWith(".free"));
22
+ let combinedAST = [];
23
+
24
+ freeFiles.forEach(file => {
25
+ try {
26
+ const code = fs.readFileSync(path.join(appDir, file), "utf-8");
27
+ const tokens = tokenize(code, file);
28
+ combinedAST = combinedAST.concat(parse(tokens, file));
29
+ } catch (e) {
30
+ console.warn(`โš ๏ธ [DevTools] Skipped ${file}: ${e.message}`);
31
+ }
32
+ });
33
+
34
+ const routes = combinedAST.filter(n => n.type === 'route');
35
+ const components = combinedAST.filter(n => n.type === 'component');
36
+ const models = combinedAST.filter(n => n.type === 'model');
37
+ const actions = combinedAST.filter(n => n.type === 'action');
38
+ const stores = combinedAST.filter(n => n.type === 'store');
39
+
40
+ // Serve the dashboard using Node's built-in http (no HyperExpress needed)
41
+ const server = http.createServer(async (req, res) => {
42
+ if (req.url === '/api/stats') {
43
+ // Attempt to fetch live structural telemetry from the running Free instance
44
+ try {
45
+ const appPort = process.env.PORT || 3000;
46
+ const telemetryRes = await fetch(`http://127.0.0.1:${appPort}/_free/telemetry`);
47
+ if (telemetryRes.ok) {
48
+ const stats = await telemetryRes.json();
49
+ res.writeHead(200, { 'Content-Type': 'application/json' });
50
+ res.end(JSON.stringify({
51
+ status: 'online',
52
+ memory: stats.memory,
53
+ uptime: stats.uptime,
54
+ cache: stats.cache,
55
+ queue: stats.queue,
56
+ freeFiles: freeFiles.length,
57
+ nodeVersion: process.version,
58
+ }));
59
+ return;
60
+ }
61
+ } catch (err) {
62
+ // If the app is offline, fallback
63
+ }
64
+
65
+ const mem = process.memoryUsage();
66
+ res.writeHead(200, { 'Content-Type': 'application/json' });
67
+ res.end(JSON.stringify({
68
+ status: 'offline',
69
+ memory: {
70
+ heapUsed: (mem.heapUsed / 1024 / 1024).toFixed(1) + ' MB',
71
+ heapTotal: (mem.heapTotal / 1024 / 1024).toFixed(1) + ' MB',
72
+ rss: (mem.rss / 1024 / 1024).toFixed(1) + ' MB',
73
+ },
74
+ uptime: process.uptime().toFixed(0) + 's',
75
+ cache: 'Offline',
76
+ queue: 'Offline',
77
+ freeFiles: freeFiles.length,
78
+ nodeVersion: process.version,
79
+ }));
80
+ return;
81
+ }
82
+
83
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
84
+ res.end(`<!DOCTYPE html>
85
+ <html lang="en">
86
+ <head>
87
+ <meta charset="UTF-8">
88
+ <title>Free DevTools</title>
89
+ <style>
90
+ * { box-sizing: border-box; margin: 0; padding: 0; }
91
+ body { font-family: system-ui, sans-serif; background: #0a0a0a; color: #e0e0e0; }
92
+ header { background: #111; padding: 1rem 2rem; border-bottom: 1px solid #222; display: flex; align-items: center; gap: 1rem; }
93
+ header h1 { font-size: 1.4rem; color: #00ffcc; }
94
+ header span { font-size: 0.8rem; color: #666; }
95
+ .container { padding: 2rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 1.5rem; }
96
+ .card { background: #111; border-radius: 10px; padding: 1.5rem; border-left: 4px solid #333; }
97
+ .card h2 { font-size: 1rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
98
+ .badge { background: #00ffcc22; color: #00ffcc; padding: 2px 8px; border-radius: 4px; font-size: 0.75rem; }
99
+ .item { padding: 0.4rem 0; border-bottom: 1px solid #1a1a1a; font-size: 0.85rem; display: flex; gap: 0.5rem; align-items: center; }
100
+ .item:last-child { border-bottom: none; }
101
+ .method { font-weight: bold; color: #00ffcc; font-size: 0.75rem; background: #00ffcc11; padding: 1px 6px; border-radius: 3px; }
102
+ .mw { font-size: 0.72rem; color: #888; background: #222; padding: 1px 5px; border-radius: 3px; }
103
+ code { color: #ff9f0a; font-size: 0.82rem; }
104
+ .stat { font-size: 1.6rem; color: #00ffcc; font-weight: bold; }
105
+ .stat-label { font-size: 0.75rem; color: #666; margin-top: 0.2rem; }
106
+ .stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
107
+ #live { font-size: 0.78rem; color: #555; }
108
+ pre { background: #000; padding: 0.75rem; border-radius: 6px; overflow-x: auto; color: #00ffcc; font-size: 0.75rem; margin-top: 0.5rem; }
109
+ </style>
110
+ </head>
111
+ <body>
112
+ <header>
113
+ <h1>๐Ÿ› ๏ธ Free DevTools</h1>
114
+ <span id="live">Loading live stats...</span>
115
+ </header>
116
+ <div class="container">
117
+
118
+ <!-- Routes -->
119
+ <div class="card" style="border-color: #00ffcc;">
120
+ <h2>๐ŸŒ Routes <span class="badge">${routes.length}</span></h2>
121
+ ${routes.map(r => `<div class="item">
122
+ <span class="method">${r.method}</span>
123
+ <code>${r.path}</code>
124
+ โ†’ ${r.view || '?'}
125
+ ${(r.middlewares || []).map(m => `<span class="mw">๐Ÿ”’ ${m}</span>`).join('')}
126
+ </div>`).join('') || '<div class="item">No routes found</div>'}
127
+ </div>
128
+
129
+ <!-- Server Actions -->
130
+ <div class="card" style="border-color: #ffff00;">
131
+ <h2>โšก Server Actions <span class="badge">${actions.length}</span></h2>
132
+ ${actions.map(a => `<div class="item"><code>POST /_free/action/${a.name}</code></div>`).join('') || '<div class="item">No actions found</div>'}
133
+ </div>
134
+
135
+ <!-- Components -->
136
+ <div class="card" style="border-color: #ff00cc;">
137
+ <h2>๐Ÿงฉ Components <span class="badge">${components.length}</span></h2>
138
+ ${components.map(c => `<div class="item">
139
+ <strong>${c.name}</strong>
140
+ ${(c.states || []).map(s => `<span class="mw">${s.name}=${s.value}</span>`).join('')}
141
+ </div>`).join('') || '<div class="item">No components found</div>'}
142
+ </div>
143
+
144
+ <!-- Models -->
145
+ <div class="card" style="border-color: #00ccff;">
146
+ <h2>๐Ÿ“ฆ Models <span class="badge">${models.length}</span></h2>
147
+ ${models.map(m => `<div class="item">
148
+ <strong>${m.name}</strong>
149
+ <span class="mw">${(m.fields || []).length} fields</span>
150
+ <pre>${(m.fields || []).map(f => `${f.name}: ${f.type}`).join('\n')}</pre>
151
+ </div>`).join('') || '<div class="item">No models found</div>'}
152
+ </div>
153
+
154
+ <!-- Stores -->
155
+ <div class="card" style="border-color: #ff6600;">
156
+ <h2>๐Ÿ—‚ Global Stores <span class="badge">${stores.length}</span></h2>
157
+ ${stores.map(s => `<div class="item"><code>store ${s.name}</code>
158
+ <pre>${JSON.stringify(s.fields, null, 2)}</pre>
159
+ </div>`).join('') || '<div class="item">No stores defined</div>'}
160
+ </div>
161
+
162
+ <!-- Live Telemetry Card -->
163
+ <div class="card" style="border-color: #9945ff;" id="memory-card">
164
+ <h2>๐Ÿง  Live Engine Telemetry <span id="engine-status" class="badge">Checking...</span></h2>
165
+ <div class="stats-grid" id="mem-stats">
166
+ <div><div class="stat">โ€”</div><div class="stat-label">Heap Used</div></div>
167
+ <div><div class="stat">โ€”</div><div class="stat-label">RSS Memory</div></div>
168
+ <div><div class="stat">โ€”</div><div class="stat-label">Cache System</div></div>
169
+ <div><div class="stat">โ€”</div><div class="stat-label">Job Queue</div></div>
170
+ </div>
171
+ </div>
172
+
173
+ </div>
174
+ <script>
175
+ async function fetchStats() {
176
+ try {
177
+ const r = await fetch('/api/stats');
178
+ const d = await r.json();
179
+ document.getElementById('live').textContent = 'Last sync ยท ' + new Date().toLocaleTimeString();
180
+ document.getElementById('engine-status').textContent = d.status === 'online' ? 'ONLINE (Port 3000)' : 'APP OFFLINE';
181
+ document.getElementById('engine-status').style.color = d.status === 'online' ? '#00ffcc' : '#ff3366';
182
+ document.getElementById('engine-status').style.background = d.status === 'online' ? '#00ffcc22' : '#ff336622';
183
+
184
+ // Format cache and queue safely
185
+ const cacheStat = typeof d.cache === 'object' ? \`Hits: \${d.cache.hits} | Miss: \${d.cache.misses}\` : d.cache;
186
+ const queueStat = typeof d.queue === 'object' ? \`\${d.queue.active} active | \${d.queue.pending} pending\` : d.queue;
187
+
188
+ const grid = document.getElementById('mem-stats');
189
+ grid.innerHTML = [
190
+ [d.memory.heapUsed, 'Heap Used'],
191
+ [d.memory.rss, 'RSS Memory'],
192
+ [cacheStat, 'Cache System'],
193
+ [queueStat, 'Job Queue'],
194
+ ].map(([v,l]) => '<div><div class="stat" style="font-size: 1.2rem;">' + v + '</div><div class="stat-label">' + l + '</div></div>').join('');
195
+ } catch(e) {}
196
+ }
197
+
198
+ fetchStats();
199
+ setInterval(fetchStats, 2000);
200
+ </script>
201
+ </body>
202
+ </html>`);
203
+ });
204
+
205
+ const port = 4000;
206
+ server.listen(port, () => {
207
+ console.log(`\n๐Ÿš€ Free DevTools running at http://localhost:${port}`);
208
+ console.log(` Live memory stats auto-refresh every 2 seconds.\n`);
209
+ });
210
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * cli/commands/doctor.js
3
+ * Implementation of 'free doctor' command.
4
+ */
5
+
6
+ const fs = require("fs-extra");
7
+ const path = require("path");
8
+ const { execSync } = require("child_process");
9
+
10
+ module.exports = function () {
11
+ console.log("\n๐Ÿฉบ Free Framework Doctor - Diagnostic Report\n");
12
+
13
+ const checks = [
14
+ { name: "Node.js Version", fn: checkNodeVersion },
15
+ { name: "Environment File (.env)", fn: checkEnvFile },
16
+ { name: "Database Connectivity", fn: checkDatabase },
17
+ { name: "Project Structure", fn: checkStructure },
18
+ { name: "Dependencies", fn: checkDependencies }
19
+ ];
20
+
21
+ let passed = 0;
22
+ checks.forEach(check => {
23
+ try {
24
+ const result = check.fn();
25
+ if (result) {
26
+ console.log(`โœ… ${check.name}: OK`);
27
+ passed++;
28
+ } else {
29
+ console.log(`โŒ ${check.name}: Failed`);
30
+ }
31
+ } catch (err) {
32
+ console.log(`โŒ ${check.name}: Error - ${err.message}`);
33
+ }
34
+ });
35
+
36
+ console.log(`\n๐Ÿ“Š Status: ${passed}/${checks.length} checks passed.\n`);
37
+ };
38
+
39
+ function checkNodeVersion() {
40
+ const version = process.version;
41
+ const major = parseInt(version.slice(1).split('.')[0]);
42
+ if (major < 16) {
43
+ throw new Error(`Node.js version must be 16 or higher. Current: ${version}`);
44
+ }
45
+ return true;
46
+ }
47
+
48
+ function checkEnvFile() {
49
+ return fs.existsSync(path.join(process.cwd(), ".env"));
50
+ }
51
+
52
+ function checkDatabase() {
53
+ // This is a shallow check, ideally would attempt a knex connection
54
+ const envPath = path.join(process.cwd(), ".env");
55
+ if (!fs.existsSync(envPath)) return false;
56
+ const env = fs.readFileSync(envPath, 'utf8');
57
+ return env.includes("DB_CLIENT");
58
+ }
59
+
60
+ function checkStructure() {
61
+ const essentialDirs = ["app", "resources/views", "routes"];
62
+ essentialDirs.forEach(dir => {
63
+ if (!fs.existsSync(path.join(process.cwd(), dir))) {
64
+ throw new Error(`Missing essential directory: ${dir}`);
65
+ }
66
+ });
67
+ return true;
68
+ }
69
+
70
+ function checkDependencies() {
71
+ return fs.existsSync(path.join(process.cwd(), "node_modules"));
72
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * cli/commands/install.js
3
+ * Wrapper around 'npm install' to auto-register Free Framework plugins.
4
+ */
5
+ const { execSync } = require("child_process");
6
+ const fs = require("fs-extra");
7
+ const path = require("path");
8
+
9
+ module.exports = function (packages) {
10
+ if (!packages || packages.length === 0) {
11
+ console.error("โŒ Please specify a package to install. Example: free install free-auth");
12
+ process.exit(1);
13
+ }
14
+
15
+ const packageNames = packages.join(" ");
16
+ console.log(`\n๐Ÿ“ฆ Installing NPM packages: ${packageNames}...\n`);
17
+
18
+ try {
19
+ execSync(`npm install ${packageNames}`, { stdio: "inherit", cwd: process.cwd() });
20
+ console.log(`\nโœ… Packages installed successfully.`);
21
+
22
+ // Potential auto-config logic can go here (e.g., adding to config.free)
23
+ console.log(`๐Ÿ’ก Remember to add any required .free directive block to app/config.free if applicable.`);
24
+ } catch (e) {
25
+ console.error("\nโŒ Installation failed.");
26
+ process.exit(1);
27
+ }
28
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * cli/commands/make.js
3
+ * Implementation of 'free make:*' scaffold commands.
4
+ */
5
+
6
+ const fs = require("fs-extra");
7
+ const path = require("path");
8
+
9
+ function guard(filePath, label) {
10
+ if (fs.existsSync(filePath)) {
11
+ console.error(`โŒ ${label} already exists: ${filePath}`);
12
+ process.exit(1);
13
+ }
14
+ fs.ensureFileSync(filePath);
15
+ }
16
+
17
+ const make = {
18
+ model: (name) => {
19
+ const dir = path.join(process.cwd(), "app/models");
20
+ fs.ensureDirSync(dir);
21
+ const filePath = path.join(dir, `${name}.free`);
22
+ guard(filePath, `Model ${name}`);
23
+ fs.writeFileSync(filePath, `model ${name} {\n name string\n created_at string\n}\n`);
24
+ console.log(`โœ… Created model: app/models/${name}.free`);
25
+ },
26
+
27
+ controller: (name) => {
28
+ const dir = path.join(process.cwd(), "app/controllers");
29
+ fs.ensureDirSync(dir);
30
+ const filePath = path.join(dir, `${name}Controller.free`);
31
+ guard(filePath, `Controller ${name}`);
32
+ fs.writeFileSync(filePath, `/**\n * ${name} Controller\n */\n\naction get${name} {\n return { success: true };\n}\n`);
33
+ console.log(`โœ… Created controller: app/controllers/${name}Controller.free`);
34
+ },
35
+
36
+ middleware: (name) => {
37
+ const dir = path.join(process.cwd(), "app/middleware");
38
+ fs.ensureDirSync(dir);
39
+ const filePath = path.join(dir, `${name}.js`);
40
+ guard(filePath, `Middleware ${name}`);
41
+ fs.writeFileSync(filePath, `/**\n * app/middleware/${name}.js\n */\nmodule.exports = async function ${name}Middleware(req, res, next) {\n // Security/Auth logic here\n next();\n};\n`);
42
+ console.log(`โœ… Created middleware: app/middleware/${name}.js`);
43
+ },
44
+
45
+ migration: (name) => {
46
+ const dir = path.join(process.cwd(), "database/migrations");
47
+ fs.ensureDirSync(dir);
48
+ const timestamp = Date.now();
49
+ const filePath = path.join(dir, `${timestamp}_${name}.js`);
50
+ guard(filePath, `Migration ${name}`);
51
+ fs.writeFileSync(filePath, `exports.up = function(knex) {\n return knex.schema.createTable('${name}', table => {\n table.increments('id');\n table.timestamps(true, true);\n });\n};\n\nexports.down = function(knex) {\n return knex.schema.dropTable('${name}');\n};\n`);
52
+ console.log(`โœ… Created migration: database/migrations/${timestamp}_${name}.js`);
53
+ },
54
+
55
+ page: (name) => {
56
+ const dir = path.join(process.cwd(), "app/views");
57
+ fs.ensureDirSync(dir);
58
+ const filePath = path.join(dir, `${name}.free`);
59
+ guard(filePath, `Page ${name}`);
60
+ fs.writeFileSync(filePath, `component ${name} {\n div class="container mx-auto p-4" {\n h1 class="text-2xl font-bold" { text "${name} View" }\n }\n}\n`);
61
+ console.log(`โœ… Created view: app/views/${name}.free`);
62
+ },
63
+
64
+ service: (name) => {
65
+ const dir = path.join(process.cwd(), "app/services");
66
+ fs.ensureDirSync(dir);
67
+ const filePath = path.join(dir, `${name}.js`);
68
+ guard(filePath, `Service ${name}`);
69
+ fs.writeFileSync(filePath, `/**\n * app/services/${name}.js\n * Enterprise service layer logic.\n */\nclass ${name} {\n static async perform() {\n // Your complex logic here\n }\n}\n\nmodule.exports = ${name};\n`);
70
+ console.log(`โœ… Created service: app/services/${name}.js`);
71
+ },
72
+ };
73
+
74
+ module.exports = make;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * cli/commands/migrate.js
3
+ * Implementation of 'free migrate' command.
4
+ */
5
+
6
+ const fs = require("fs-extra");
7
+ const path = require("path");
8
+ const knex = require("knex");
9
+
10
+ module.exports = async function () {
11
+ console.log("๐Ÿš€ Starting Database Migrations...");
12
+
13
+ // Create connection config dynamically just like ORM
14
+ const client = process.env.DB_CLIENT || 'mysql2';
15
+ let connection = {};
16
+
17
+ if (client === 'mysql2' || client === 'pg') {
18
+ connection = {
19
+ host: process.env.DB_HOST || '127.0.0.1',
20
+ port: process.env.DB_PORT || (client === 'pg' ? 5432 : 3306),
21
+ user: process.env.DB_USER || (client === 'pg' ? 'postgres' : 'root'),
22
+ password: process.env.DB_PASS || '',
23
+ database: process.env.DB_NAME || 'free_db',
24
+ };
25
+ if (client === 'pg' && process.env.DB_SSL === 'true') {
26
+ connection.ssl = { rejectUnauthorized: false };
27
+ }
28
+ } else {
29
+ const dbPath = process.env.DB_PATH || 'database.sqlite';
30
+ const fullPath = path.isAbsolute(dbPath) ? dbPath : path.join(process.cwd(), dbPath);
31
+ connection = { filename: fullPath };
32
+ const dbDir = path.dirname(fullPath);
33
+ if (!fs.existsSync(dbDir)) {
34
+ fs.mkdirSync(dbDir, { recursive: true });
35
+ }
36
+ }
37
+
38
+ const migrationsDir = path.join(process.cwd(), "database", "migrations");
39
+ if (!fs.existsSync(migrationsDir)) {
40
+ console.warn("โš ๏ธ No migrations directory found. Run 'free make:migration create_users_table' first.");
41
+ process.exit(0);
42
+ }
43
+
44
+ const db = knex({
45
+ client,
46
+ connection,
47
+ useNullAsDefault: client === 'sqlite3',
48
+ migrations: {
49
+ directory: migrationsDir
50
+ }
51
+ });
52
+
53
+ try {
54
+ const [batchNo, log] = await db.migrate.latest();
55
+ if (log.length === 0) {
56
+ console.log("โœ… Database is already up to date.");
57
+ } else {
58
+ console.log(`โœ… Batch ${batchNo} run: ${log.length} migrations`);
59
+ log.forEach(m => console.log(` ๐Ÿ‘‰ ${m}`));
60
+ }
61
+ } catch (err) {
62
+ console.error("โŒ Migration failed:", err.message);
63
+ process.exit(1);
64
+ } finally {
65
+ await db.destroy();
66
+ }
67
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * cli/commands/new.js
3
+ * Implementation of 'free new <name>' command.
4
+ */
5
+
6
+ const fs = require("fs-extra");
7
+ const path = require("path");
8
+ const { execSync } = require("child_process");
9
+
10
+ module.exports = function (name) {
11
+ const targetDir = path.join(process.cwd(), name);
12
+ const templateDir = path.join(__dirname, "../../templates/app-template");
13
+
14
+ console.log(`\nโœจ Creating a new Free project in: ${targetDir}`);
15
+
16
+ if (fs.existsSync(targetDir)) {
17
+ console.error("Error: Target directory already exists.");
18
+ process.exit(1);
19
+ }
20
+
21
+ // Copy template
22
+ fs.copySync(templateDir, targetDir);
23
+
24
+ // Initialize the massive enterprise directory scaffolding dynamically
25
+ const dirsToCreate = [
26
+ "app/Http/Controllers",
27
+ "app/Http/Middleware",
28
+ "app/Models",
29
+ "app/Services",
30
+ "config",
31
+ "database/migrations",
32
+ "database/seeders",
33
+ "public/assets",
34
+ "public/css",
35
+ "public/js",
36
+ "public/images",
37
+ "resources/views",
38
+ "resources/css",
39
+ "routes",
40
+ "storage/logs",
41
+ "storage/framework"
42
+ ];
43
+
44
+ dirsToCreate.forEach(dir => {
45
+ fs.ensureDirSync(path.join(targetDir, dir));
46
+ // Add a .gitkeep to ensure the folder is trackable in version control
47
+ fs.writeFileSync(path.join(targetDir, dir, ".gitkeep"), "");
48
+ });
49
+
50
+ // Wrap up
51
+ console.log("\nโœ… Enterprise Project Scaffolded Successfully!");
52
+ console.log(`\nTo get started, run:\n cd ${name}\n npm install\n free serve\n`);
53
+
54
+ };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * cli/commands/serve.js
3
+ * Implementation of 'free serve' command.
4
+ */
5
+
6
+ const { spawn } = require("child_process");
7
+ const path = require("path");
8
+ const chokidar = require("chokidar");
9
+ const build = require("./build");
10
+
11
+ module.exports = function (options) {
12
+ const port = options.port || 3000;
13
+ let serverProcess = null;
14
+
15
+ const startServer = () => {
16
+ if (serverProcess) {
17
+ serverProcess.kill();
18
+ }
19
+
20
+ console.log("๐Ÿš€ Starting server...");
21
+
22
+ // Set view directory to the current project's views
23
+ process.env.VIEWS_PATH = path.join(process.cwd(), "views");
24
+ process.env.PORT = port;
25
+ process.env.FREE_PATH = path.join(__dirname, "../../");
26
+
27
+ const args = [path.join(process.cwd(), ".free/app.js")];
28
+ if (options.cluster) args.push("--cluster");
29
+
30
+ serverProcess = spawn("node", args, {
31
+ stdio: "inherit",
32
+ env: process.env
33
+ });
34
+
35
+ serverProcess.on("close", (code) => {
36
+ if (code && code !== 0) {
37
+ console.error(`Server process exited with code ${code}`);
38
+ }
39
+ });
40
+ };
41
+
42
+ startServer();
43
+
44
+ const appDir = path.join(process.cwd(), "app");
45
+ let isRebuilding = false;
46
+
47
+ // The Fast Refresh / Auto-Rebuild logic
48
+ chokidar.watch(appDir, { ignoreInitial: true }).on("all", (event, file) => {
49
+ if (isRebuilding) return;
50
+ isRebuilding = true;
51
+
52
+ console.log(`\n๐Ÿ”„ ${path.basename(file)} changed. Fast-refreshing...`);
53
+
54
+ try {
55
+ build(); // Rebuild AST and Generators
56
+
57
+ // Graceful restart: Only restart if build succeeds
58
+ setTimeout(() => {
59
+ startServer();
60
+ console.log(`โœ… Ready.`);
61
+ isRebuilding = false;
62
+ }, 100);
63
+ } catch (err) {
64
+ console.error(`โŒ Fast-refresh failed:`, err.message);
65
+ isRebuilding = false;
66
+ }
67
+ });
68
+
69
+ process.on("SIGINT", () => {
70
+ if (serverProcess) serverProcess.kill();
71
+ process.exit();
72
+ });
73
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * cli/commands/test.js
3
+ * Implementation of 'free test' command wrapped around Vitest.
4
+ */
5
+ const { execSync } = require("child_process");
6
+ const fs = require("fs-extra");
7
+ const path = require("path");
8
+
9
+ module.exports = function () {
10
+ const projectRoot = process.cwd();
11
+
12
+ // Check if Vitest is installed in the project
13
+ const packageJsonPath = path.join(projectRoot, "package.json");
14
+ if (!fs.existsSync(packageJsonPath)) {
15
+ console.error("โŒ No package.json found. Make sure you are in a valid Free project.");
16
+ process.exit(1);
17
+ }
18
+
19
+ let packageJson;
20
+ try {
21
+ packageJson = require(packageJsonPath);
22
+ } catch (e) {
23
+ console.error("โŒ Error reading package.json.");
24
+ process.exit(1);
25
+ }
26
+
27
+ const hasVitest = (packageJson.devDependencies && packageJson.devDependencies.vitest) ||
28
+ (packageJson.dependencies && packageJson.dependencies.vitest);
29
+
30
+ if (!hasVitest) {
31
+ console.log("๐Ÿ“ฆ Vitest not detected. Installing Vitest specifically for Free Testing Suite...");
32
+ try {
33
+ execSync("npm install vitest --save-dev", { stdio: "inherit", cwd: projectRoot });
34
+ console.log("โœ… Vitest installed successfully.");
35
+ } catch (e) {
36
+ console.error("โŒ Failed to install Vitest.");
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ // Zero-Config: Create vitest.config.js if missing
42
+ const configPath = path.join(projectRoot, "vitest.config.js");
43
+ if (!fs.existsSync(configPath)) {
44
+ console.log("๐Ÿ“ Generating default vitest.config.js...");
45
+ const configContent = `import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n test: {\n environment: 'node',\n globals: true,\n include: ['**/*.{test,spec}.{js,ts}'],\n },\n});\n`;
46
+ fs.writeFileSync(configPath, configContent);
47
+ }
48
+
49
+ console.log("\n๐Ÿงช Running Free Testing Suite (Vitest)...\n");
50
+ try {
51
+ // Execute Vitest on all files containing '.test.' or '.spec.'
52
+ execSync("npx vitest run", { stdio: "inherit", cwd: projectRoot });
53
+ } catch (error) {
54
+ console.error("\nโŒ Tests failed. Please review the output above.");
55
+ process.exit(1);
56
+ }
57
+ };