lumpiajs 1.0.7 → 1.0.9
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 +24 -128
- package/bin/lumpia.js +2 -7
- package/index.js +3 -1
- package/lib/bridge/api.php +59 -0
- package/lib/commands/build.js +380 -0
- package/lib/commands/create.js +146 -99
- package/lib/commands/serve.js +109 -65
- package/lib/core/Config.js +20 -0
- package/lib/core/DB.js +139 -0
- package/lib/core/DBClient.js +54 -0
- package/lib/core/View.js +21 -8
- package/package.json +4 -2
- package/templates/gitignore.txt +3 -0
package/lib/commands/create.js
CHANGED
|
@@ -1,156 +1,203 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import prompts from 'prompts';
|
|
3
4
|
|
|
4
|
-
// --- TEMPLATES ---
|
|
5
5
|
const routesTemplate = `import { Jalan } from 'lumpiajs';
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
Jalan
|
|
9
|
-
Jalan
|
|
10
|
-
Jalan
|
|
7
|
+
// Pakai gaya Laravel (->) enak to?
|
|
8
|
+
Jalan->get('/', 'HomeController@index');
|
|
9
|
+
Jalan->get('/db-test', 'HomeController@testDb');
|
|
10
|
+
Jalan->get('/profile', 'HomeController@profile');
|
|
11
|
+
Jalan->get('/api/products', 'ProductController@index');
|
|
11
12
|
`;
|
|
12
13
|
|
|
13
|
-
const controllerTemplate = `import { Controller } from 'lumpiajs';
|
|
14
|
+
const controllerTemplate = `import { Controller, DB } from 'lumpiajs';
|
|
14
15
|
|
|
15
16
|
export default class HomeController extends Controller {
|
|
16
17
|
index() {
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
return this
|
|
18
|
+
// "this.tampil" juga bisa ditulis "this->tampil"
|
|
19
|
+
// Transpiler LumpiaJS sing ngatur, Bos!
|
|
20
|
+
return this->tampil('home', {
|
|
20
21
|
message: 'Welcome to LumpiaJS MVC!',
|
|
21
22
|
author: 'Pakdhe Koding',
|
|
22
23
|
env: this.env.APP_ENV
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
async testDb() {
|
|
28
|
+
try {
|
|
29
|
+
// CONTOH QUERY ALA LARAVEL
|
|
30
|
+
// Pake tanda panah -> biar mantap
|
|
31
|
+
const result = await DB.table('users')
|
|
32
|
+
->limit(1)
|
|
33
|
+
->get();
|
|
34
|
+
|
|
35
|
+
// Raw Query
|
|
36
|
+
const raw = await DB->query('SELECT 1 + 1 AS solution');
|
|
37
|
+
|
|
38
|
+
return this->json({
|
|
39
|
+
status: 'Connected!',
|
|
40
|
+
sample_user: result,
|
|
41
|
+
math_check: raw[0].solution
|
|
42
|
+
});
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return this->json({ error: e.message });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
profile() {
|
|
27
|
-
return this
|
|
49
|
+
return this->tampil('profile', { name: 'Loyal User' });
|
|
28
50
|
}
|
|
29
51
|
}
|
|
30
52
|
`;
|
|
31
53
|
|
|
32
|
-
const modelTemplate = `// Example dummy data
|
|
54
|
+
const modelTemplate = `// Example dummy data (Static)
|
|
33
55
|
const productData = [
|
|
34
56
|
{ id: 1, name: 'Lumpia Basah', price: 5000 },
|
|
35
|
-
{ id: 2, name: 'Lumpia Goreng', price: 6000 }
|
|
36
|
-
{ id: 3, name: 'Tahu Gimbal', price: 12000 }
|
|
57
|
+
{ id: 2, name: 'Lumpia Goreng', price: 6000 }
|
|
37
58
|
];
|
|
38
|
-
|
|
39
59
|
export default productData;
|
|
40
60
|
`;
|
|
41
61
|
|
|
42
62
|
const productControllerTemplate = `import { Controller, Model } from 'lumpiajs';
|
|
43
|
-
import ProductData from '../../app/models/Product.
|
|
63
|
+
import ProductData from '../../app/models/Product.lmp';
|
|
44
64
|
|
|
45
65
|
export default class ProductController extends Controller {
|
|
46
66
|
index() {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
data: result,
|
|
54
|
-
debug_mode: this.env.APP_DEBUG
|
|
55
|
-
});
|
|
67
|
+
// Model Static juga bisa pakai ->
|
|
68
|
+
const result = Model->use(ProductData)
|
|
69
|
+
->dimana('price', '>', 5500)
|
|
70
|
+
->jupuk();
|
|
71
|
+
|
|
72
|
+
return this->json({ status: 'success', data: result });
|
|
56
73
|
}
|
|
57
74
|
}
|
|
58
75
|
`;
|
|
59
76
|
|
|
60
|
-
const viewHomeTemplate = `<lump>
|
|
61
|
-
<klambi>
|
|
62
|
-
h1 { color: #d35400; text-align: center; }
|
|
63
|
-
.box { border: 1px solid #ddd; padding: 20px; text-align: center; margin-top: 20px; }
|
|
64
|
-
.badge { background: #eee; padding: 5px; border-radius: 4px; font-size: 12px; }
|
|
65
|
-
</klambi>
|
|
66
|
-
|
|
67
|
-
<kulit>
|
|
68
|
-
<h1>{{ message }}</h1>
|
|
69
|
-
<div class="box">
|
|
70
|
-
<span class="badge">Environment: {{ env }}</span>
|
|
71
|
-
<p>Created with love by: <strong>{{ author }}</strong></p>
|
|
72
|
-
<button onclick="checkPrice()">Check Price API</button>
|
|
73
|
-
<br><br>
|
|
74
|
-
<a href="/profile">Go to Profile</a>
|
|
75
|
-
</div>
|
|
76
|
-
</kulit>
|
|
77
|
-
|
|
78
|
-
<isi>
|
|
79
|
-
gawe checkPrice() {
|
|
80
|
-
fetch('/api/products')
|
|
81
|
-
.then(res => res.json())
|
|
82
|
-
.then(data => {
|
|
83
|
-
alert('Data from API: ' + JSON.stringify(data));
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
</isi>
|
|
87
|
-
</lump>`;
|
|
88
|
-
|
|
89
77
|
const viewProfileTemplate = `<lump>
|
|
90
78
|
<kulit>
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
79
|
+
<div class="container mt-5">
|
|
80
|
+
<h1>User Profile</h1>
|
|
81
|
+
<p>Hello, <strong>{{ name }}</strong>!</p>
|
|
82
|
+
<a href="/" class="btn btn-secondary">Back Home</a>
|
|
83
|
+
</div>
|
|
94
84
|
</kulit>
|
|
95
85
|
</lump>`;
|
|
96
86
|
|
|
97
|
-
const packageJsonTemplate = (name) => `{
|
|
98
|
-
"name": "${name}",
|
|
99
|
-
"version": "1.0.0",
|
|
100
|
-
"main": "routes/web.js",
|
|
101
|
-
"type": "module",
|
|
102
|
-
"scripts": {
|
|
103
|
-
"start": "lumpia kukus",
|
|
104
|
-
"serve": "lumpia kukus"
|
|
105
|
-
},
|
|
106
|
-
"dependencies": {
|
|
107
|
-
"lumpiajs": "latest"
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
`;
|
|
111
|
-
|
|
112
87
|
const envTemplate = `
|
|
113
88
|
BASE_URL="http://localhost:3000"
|
|
114
89
|
APP_ENV="local"
|
|
115
|
-
# Pilihan: 'local', 'development', 'production'
|
|
116
90
|
APP_DEBUG="true"
|
|
117
|
-
|
|
91
|
+
|
|
92
|
+
# Database Config
|
|
93
|
+
DB_HOST="localhost"
|
|
94
|
+
DB_USER="root"
|
|
95
|
+
DB_PASSWORD=""
|
|
96
|
+
DB_NAME="lumpia_db"
|
|
118
97
|
`;
|
|
119
98
|
|
|
120
|
-
|
|
121
|
-
|
|
99
|
+
// Helper for CSS/View generation (Collapsed for brevity but functional as before)
|
|
100
|
+
const tailwindConfigTemplate = `/** @type {import('tailwindcss').Config} */
|
|
101
|
+
module.exports = { content: ["./views/**/*.{html,js,lmp}"], theme: { extend: {}, }, plugins: [], }`;
|
|
102
|
+
const mainCssTemplate = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n`;
|
|
103
|
+
|
|
104
|
+
const generateHomeView = (style) => {
|
|
105
|
+
let css = '', html = '';
|
|
106
|
+
if (style === 'bootstrap') {
|
|
107
|
+
html = `
|
|
108
|
+
<div class="container mt-5">
|
|
109
|
+
<div class="card shadow">
|
|
110
|
+
<div class="card-body text-center">
|
|
111
|
+
<h1 class="text-primary">{{ message }}</h1>
|
|
112
|
+
<span class="badge bg-secondary mb-3">Env: {{ env }}</span>
|
|
113
|
+
<p>Created by: <strong>{{ author }}</strong></p>
|
|
114
|
+
<div class="mt-4">
|
|
115
|
+
<a href="/db-test" class="btn btn-warning">Test DB (Laravel Style)</a>
|
|
116
|
+
<a href="/profile" class="btn btn-link">Go to Profile</a>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>`;
|
|
121
|
+
} else if (style === 'tailwindcss') {
|
|
122
|
+
html = `
|
|
123
|
+
<div class="container mx-auto mt-10 p-5">
|
|
124
|
+
<div class="bg-white shadow-lg rounded-lg p-8 text-center border border-gray-200">
|
|
125
|
+
<h1 class="text-4xl font-bold text-orange-600 mb-4">{{ message }}</h1>
|
|
126
|
+
<span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mb-4">Env: {{ env }}</span>
|
|
127
|
+
<div class="mt-6 space-x-4">
|
|
128
|
+
<a href="/db-test" class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded">Test DB</a>
|
|
129
|
+
<a href="/profile" class="text-blue-500 hover:text-blue-800">Go to Profile</a>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>`;
|
|
133
|
+
} else {
|
|
134
|
+
css = `h1{text-align:center} .box{text-align:center;margin-top:20px}`;
|
|
135
|
+
html = `<div class="box"><h1>{{ message }}</h1><p>Env: {{ env }}</p><a href="/db-test">Test DB</a> | <a href="/profile">Profile</a><br>(Check Controller for Laravel Syntax Demo)</div>`;
|
|
136
|
+
}
|
|
137
|
+
return `<lump><klambi>${css}</klambi><kulit>${html}</kulit><isi></isi></lump>`;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const generatePackageJson = (name, style) => {
|
|
141
|
+
const scripts = { "start": "lumpia kukus", "serve": "lumpia kukus" };
|
|
142
|
+
const dependencies = { "lumpiajs": "latest" };
|
|
143
|
+
const devDependencies = {};
|
|
144
|
+
if (style === 'tailwindcss') {
|
|
145
|
+
devDependencies["tailwindcss"] = "^3.4.0";
|
|
146
|
+
devDependencies["postcss"] = "^8.4.0";
|
|
147
|
+
devDependencies["autoprefixer"] = "^10.4.0";
|
|
148
|
+
} else if (style === 'bootstrap') dependencies["bootstrap"] = "^5.3.0";
|
|
149
|
+
return JSON.stringify({ name, version: "1.0.0", main: "routes/web.lmp", type: "module", scripts, dependencies, devDependencies }, null, 2);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export async function createProject(parameter) {
|
|
153
|
+
let projectName = parameter;
|
|
122
154
|
if (!projectName) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return;
|
|
155
|
+
const res = await prompts({ type: 'text', name: 'val', message: 'Jeneng project?', initial: 'my-app' });
|
|
156
|
+
projectName = res.val;
|
|
126
157
|
}
|
|
127
|
-
|
|
158
|
+
if (!projectName) return;
|
|
159
|
+
|
|
128
160
|
const root = path.join(process.cwd(), projectName);
|
|
129
|
-
if (fs.existsSync(root))
|
|
161
|
+
if (fs.existsSync(root)) { console.log('❌ Folder Exists'); return; }
|
|
130
162
|
|
|
131
|
-
|
|
132
|
-
|
|
163
|
+
const styleRes = await prompts({
|
|
164
|
+
type: 'select', name: 'val', message: 'Styling?',
|
|
165
|
+
choices: [{title:'Vanilla',value:'none'},{title:'Tailwind',value:'tailwindcss'},{title:'Bootstrap',value:'bootstrap'}],
|
|
166
|
+
initial: 0
|
|
167
|
+
});
|
|
168
|
+
const style = styleRes.val;
|
|
169
|
+
if (!style) return;
|
|
170
|
+
|
|
171
|
+
// Structure
|
|
133
172
|
fs.mkdirSync(root);
|
|
134
|
-
|
|
135
173
|
fs.mkdirSync(path.join(root, 'app', 'controllers'), { recursive: true });
|
|
136
174
|
fs.mkdirSync(path.join(root, 'app', 'models'), { recursive: true });
|
|
137
175
|
fs.mkdirSync(path.join(root, 'routes'));
|
|
138
|
-
fs.mkdirSync(path.join(root, 'views'));
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
fs.writeFileSync(path.join(root, '
|
|
144
|
-
fs.writeFileSync(path.join(root, '
|
|
145
|
-
fs.writeFileSync(path.join(root, '
|
|
146
|
-
fs.writeFileSync(path.join(root, '
|
|
147
|
-
|
|
176
|
+
fs.mkdirSync(path.join(root, 'views'));
|
|
177
|
+
fs.mkdirSync(path.join(root, 'aset', 'css'), { recursive: true });
|
|
178
|
+
fs.mkdirSync(path.join(root, 'public', 'css'), { recursive: true });
|
|
179
|
+
|
|
180
|
+
// Files
|
|
181
|
+
fs.writeFileSync(path.join(root, 'package.json'), generatePackageJson(projectName, style));
|
|
182
|
+
fs.writeFileSync(path.join(root, '.gitignore'), `.lumpia\nnode_modules\n.env\n`);
|
|
183
|
+
fs.writeFileSync(path.join(root, '.env'), envTemplate);
|
|
184
|
+
fs.writeFileSync(path.join(root, 'config.lmp'), JSON.stringify({ klambi: style }, null, 2));
|
|
185
|
+
|
|
186
|
+
fs.writeFileSync(path.join(root, 'routes', 'web.lmp'), routesTemplate);
|
|
187
|
+
fs.writeFileSync(path.join(root, 'app', 'controllers', 'HomeController.lmp'), controllerTemplate);
|
|
188
|
+
fs.writeFileSync(path.join(root, 'app', 'controllers', 'ProductController.lmp'), productControllerTemplate);
|
|
189
|
+
fs.writeFileSync(path.join(root, 'app', 'models', 'Product.lmp'), modelTemplate);
|
|
190
|
+
fs.writeFileSync(path.join(root, 'views', 'home.lmp'), generateHomeView(style));
|
|
148
191
|
fs.writeFileSync(path.join(root, 'views', 'profile.lmp'), viewProfileTemplate);
|
|
192
|
+
|
|
193
|
+
// Style Assets
|
|
194
|
+
if (style === 'tailwindcss') {
|
|
195
|
+
fs.writeFileSync(path.join(root, 'tailwind.config.js'), tailwindConfigTemplate);
|
|
196
|
+
fs.writeFileSync(path.join(root, 'aset', 'css', 'style.css'), mainCssTemplate);
|
|
197
|
+
} else {
|
|
198
|
+
fs.writeFileSync(path.join(root, 'aset', 'css', 'style.css'), style === 'bootstrap' ? '/* Bootstrap Imported in HTML */' : '/* CSS */');
|
|
199
|
+
}
|
|
149
200
|
|
|
150
|
-
console.log(
|
|
151
|
-
console.log(
|
|
152
|
-
console.log(`cd ${projectName}`);
|
|
153
|
-
console.log('npm install');
|
|
154
|
-
console.log('lumpia kukus');
|
|
155
|
-
console.log('----------------------------------------------------');
|
|
201
|
+
console.log(`✅ Project "${projectName}" Ready!`);
|
|
202
|
+
console.log(`cd ${projectName} && npm install && lumpia kukus`);
|
|
156
203
|
}
|
package/lib/commands/serve.js
CHANGED
|
@@ -1,95 +1,146 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import http from 'http';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
4
5
|
import { routes } from '../core/Router.js';
|
|
5
6
|
import { renderLumpia } from '../core/View.js';
|
|
6
7
|
import { loadEnv } from '../core/Env.js';
|
|
8
|
+
import { loadConfig } from '../core/Config.js';
|
|
7
9
|
|
|
8
|
-
// Baca versi LumpiaJS global (CLI ini)
|
|
9
10
|
const cliPackageJson = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url)));
|
|
10
11
|
|
|
11
|
-
// Helper
|
|
12
|
+
// Helper to Match Routes
|
|
12
13
|
function matchRoute(definedRoute, method, pathname) {
|
|
13
14
|
if (definedRoute.method !== method) return null;
|
|
14
|
-
|
|
15
|
-
// 1. Exact Match
|
|
16
15
|
if (definedRoute.path === pathname) return { params: {} };
|
|
17
16
|
|
|
18
|
-
// 2. Dynamic Match (Regex)
|
|
19
|
-
// Convert /produk/{id}/{slug} -> ^\/produk\/([^\/]+)\/([^\/]+)$
|
|
20
17
|
const paramNames = [];
|
|
21
18
|
const regexPath = definedRoute.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, name) => {
|
|
22
19
|
paramNames.push(name);
|
|
23
20
|
return '([^/]+)';
|
|
24
21
|
});
|
|
25
22
|
|
|
26
|
-
// Skip if no params found in definition (optimization)
|
|
27
23
|
if (regexPath === definedRoute.path) return null;
|
|
28
|
-
|
|
29
24
|
const regex = new RegExp(`^${regexPath}$`);
|
|
30
25
|
const match = pathname.match(regex);
|
|
31
|
-
|
|
32
26
|
if (match) {
|
|
33
27
|
const params = {};
|
|
34
28
|
paramNames.forEach((name, index) => {
|
|
35
|
-
params[name] = match[index + 1];
|
|
29
|
+
params[name] = match[index + 1];
|
|
36
30
|
});
|
|
37
31
|
return { params };
|
|
38
32
|
}
|
|
39
|
-
|
|
40
33
|
return null;
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
const backgroundProcess = [];
|
|
37
|
+
|
|
38
|
+
function startTailwindWatcher(root) {
|
|
39
|
+
console.log('🎨 TailwindCSS detected! Starting watcher...');
|
|
40
|
+
const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
41
|
+
const tailwind = spawn(cmd, [
|
|
42
|
+
'tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--watch'
|
|
43
|
+
], { cwd: root, stdio: 'inherit', shell: true });
|
|
44
|
+
backgroundProcess.push(tailwind);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function handleBootstrap(root) {
|
|
48
|
+
const bootDestDist = path.join(root, 'public', 'vendor', 'bootstrap');
|
|
49
|
+
const bootSrc = path.join(root, 'node_modules', 'bootstrap', 'dist');
|
|
50
|
+
if (fs.existsSync(bootSrc) && !fs.existsSync(bootDestDist)) {
|
|
51
|
+
console.log('📦 Menyalin library Bootstrap ke public...');
|
|
52
|
+
try { fs.cpSync(bootSrc, bootDestDist, { recursive: true }); } catch(e) {}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// LOADER UTAMA UNTUK .lmp (Controller/Model/Routes)
|
|
57
|
+
async function loadLumpiaModule(filePath) {
|
|
58
|
+
const originalContent = fs.readFileSync(filePath, 'utf-8');
|
|
59
|
+
const cacheDir = path.join(process.cwd(), '.lumpia', 'cache');
|
|
60
|
+
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
|
|
45
61
|
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
63
|
+
const flatName = relativePath.replace(/[\/\\]/g, '_').replace('.lmp', '.js');
|
|
64
|
+
const destPath = path.join(cacheDir, flatName);
|
|
65
|
+
|
|
66
|
+
// --- TRANSPILE LOGIC ---
|
|
67
|
+
let transcoded = originalContent;
|
|
68
|
+
|
|
69
|
+
// 1. Replace Imports: .lmp -> .js
|
|
70
|
+
transcoded = transcoded.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
|
|
71
|
+
|
|
72
|
+
// 2. SYNTAX LARAVEL -> JS (Fitur Request User: "->")
|
|
73
|
+
// Mengubah tanda panah "->" menjadi titik "."
|
|
74
|
+
// Hati-hati: Kita coba sebisa mungkin tidak mengubah "->" yang ada di dalam string.
|
|
75
|
+
// Regex ini mencari "->" yang TIDAK diapit kutip (Simpel, mungkin tidak 100% sempurna tapi cukup untuk have fun)
|
|
76
|
+
// Atau kita brute force saja asalkan user tahu resikonya.
|
|
77
|
+
// Kita gunakan pendekatan brute replace tapi hindari arrow function "=>" (aman karena beda karakter)
|
|
78
|
+
|
|
79
|
+
// Replace "->" dengan "."
|
|
80
|
+
transcoded = transcoded.split('\n').map(line => {
|
|
81
|
+
// Cek komentar //
|
|
82
|
+
if (line.trim().startsWith('//')) return line;
|
|
51
83
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
// Simple replace "->" to "."
|
|
85
|
+
// Note: Ini akan mengubah string "go -> to" menjadi "go . to".
|
|
86
|
+
// Untuk framework "Have Fun", ini fitur, bukan bug. XD
|
|
87
|
+
return line.replace(/->/g, '.');
|
|
88
|
+
}).join('\n');
|
|
89
|
+
|
|
90
|
+
fs.writeFileSync(destPath, transcoded);
|
|
91
|
+
|
|
92
|
+
// 4. Import file .js
|
|
93
|
+
const module = await import('file://' + destPath + '?t=' + Date.now());
|
|
94
|
+
return module;
|
|
95
|
+
}
|
|
96
|
+
|
|
63
97
|
|
|
64
|
-
|
|
65
|
-
|
|
98
|
+
export async function serveProject() {
|
|
99
|
+
const root = process.cwd();
|
|
100
|
+
|
|
101
|
+
const routesFile = path.join(root, 'routes', 'web.lmp');
|
|
102
|
+
if (!fs.existsSync(routesFile)) return console.log("❌ Not a LumpiaJS Project. (Missing routes/web.lmp)");
|
|
66
103
|
|
|
67
|
-
// --- 2. Load ENV ---
|
|
68
104
|
const env = loadEnv(root);
|
|
69
|
-
|
|
70
|
-
console.log(`🐛 Debug Mode: ${env.APP_DEBUG}`);
|
|
105
|
+
const config = loadConfig(root);
|
|
71
106
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const userRouteUrl = path.join(root, 'routes', 'web.js');
|
|
75
|
-
await import('file://' + userRouteUrl);
|
|
107
|
+
if (config.klambi === 'tailwindcss') startTailwindWatcher(root);
|
|
108
|
+
else if (config.klambi === 'bootstrap') handleBootstrap(root);
|
|
76
109
|
|
|
110
|
+
try {
|
|
111
|
+
await loadLumpiaModule(routesFile);
|
|
77
112
|
console.log(`🛣️ Routes registered: ${routes.length}`);
|
|
113
|
+
|
|
114
|
+
// Info Syntax
|
|
115
|
+
console.log(`✨ Syntax Mode: PHP/Laravel Style (->) is enabled in .lmp files!`);
|
|
78
116
|
|
|
79
|
-
// Start Server
|
|
80
117
|
const server = http.createServer(async (req, res) => {
|
|
81
118
|
const method = req.method;
|
|
82
119
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
83
120
|
const pathname = url.pathname;
|
|
84
121
|
|
|
85
|
-
if (env.APP_DEBUG === 'true') {
|
|
86
|
-
|
|
122
|
+
if (env.APP_DEBUG === 'true') console.log(`📥 ${method} ${pathname}`);
|
|
123
|
+
|
|
124
|
+
const publicMap = {
|
|
125
|
+
'/css/': path.join(root, 'public', 'css'),
|
|
126
|
+
'/vendor/': path.join(root, 'public', 'vendor')
|
|
127
|
+
};
|
|
128
|
+
for (const [prefix, localPath] of Object.entries(publicMap)) {
|
|
129
|
+
if (pathname.startsWith(prefix)) {
|
|
130
|
+
const relativePath = pathname.slice(prefix.length);
|
|
131
|
+
const filePath = path.join(localPath, relativePath);
|
|
132
|
+
if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
|
|
133
|
+
const ext = path.extname(filePath);
|
|
134
|
+
const mime = ext === '.css' ? 'text/css' : (ext === '.js' ? 'text/javascript' : 'application/octet-stream');
|
|
135
|
+
res.writeHead(200, {'Content-Type': mime});
|
|
136
|
+
res.end(fs.readFileSync(filePath));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
87
140
|
}
|
|
88
141
|
|
|
89
|
-
// FIND MATCHING ROUTE
|
|
90
142
|
let match = null;
|
|
91
143
|
let params = {};
|
|
92
|
-
|
|
93
144
|
for (const route of routes) {
|
|
94
145
|
const result = matchRoute(route, method, pathname);
|
|
95
146
|
if (result) {
|
|
@@ -102,29 +153,20 @@ export async function serveProject() {
|
|
|
102
153
|
if (match) {
|
|
103
154
|
try {
|
|
104
155
|
const [controllerName, methodName] = match.action.split('@');
|
|
105
|
-
const controllerPath = path.join(root, 'app', 'controllers', controllerName + '.
|
|
156
|
+
const controllerPath = path.join(root, 'app', 'controllers', controllerName + '.lmp');
|
|
106
157
|
|
|
107
|
-
if (!fs.existsSync(controllerPath)) throw new Error(`Controller ${controllerName} not found
|
|
158
|
+
if (!fs.existsSync(controllerPath)) throw new Error(`Controller ${controllerName} not found!`);
|
|
108
159
|
|
|
109
|
-
|
|
110
|
-
let importUrl = 'file://' + controllerPath;
|
|
111
|
-
if (env.APP_ENV === 'local' || env.APP_ENV === 'development') {
|
|
112
|
-
importUrl += '?update=' + Date.now();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const module = await import(importUrl);
|
|
160
|
+
const module = await loadLumpiaModule(controllerPath);
|
|
116
161
|
const ControllerClass = module.default;
|
|
117
162
|
const instance = new ControllerClass();
|
|
118
163
|
|
|
119
|
-
// Inject ENV & Params to Controller
|
|
120
164
|
instance.env = env;
|
|
121
|
-
instance.params = params;
|
|
165
|
+
instance.params = params;
|
|
166
|
+
instance.config = config;
|
|
122
167
|
|
|
123
|
-
if (typeof instance[methodName] !== 'function') {
|
|
124
|
-
throw new Error(`Method ${methodName} does not exist in ${controllerName}`);
|
|
125
|
-
}
|
|
168
|
+
if (typeof instance[methodName] !== 'function') throw new Error(`Method ${methodName} missing`);
|
|
126
169
|
|
|
127
|
-
// Pass params as spread arguments to the method: index(id, slug)
|
|
128
170
|
const args = Object.values(params);
|
|
129
171
|
const result = await instance[methodName](...args);
|
|
130
172
|
|
|
@@ -140,26 +182,28 @@ export async function serveProject() {
|
|
|
140
182
|
}
|
|
141
183
|
} catch (e) {
|
|
142
184
|
console.error(e);
|
|
143
|
-
const errorMsg = env.APP_DEBUG === 'true'
|
|
144
|
-
? `<h1>500: Server Error</h1><pre>${e.message}\n${e.stack}</pre>`
|
|
145
|
-
: `<h1>500: Server Error</h1><p>Something went wrong.</p>`;
|
|
146
|
-
|
|
185
|
+
const errorMsg = env.APP_DEBUG === 'true' ? `<pre>${e.stack}</pre>` : `<h1>Server Error</h1>`;
|
|
147
186
|
res.writeHead(500, {'Content-Type': 'text/html'});
|
|
148
187
|
res.end(errorMsg);
|
|
149
188
|
}
|
|
150
189
|
} else {
|
|
151
190
|
res.writeHead(404, {'Content-Type': 'text/html'});
|
|
152
|
-
res.end('<h1>404
|
|
191
|
+
res.end('<h1>404 Not Found</h1>');
|
|
153
192
|
}
|
|
154
193
|
});
|
|
155
194
|
|
|
156
195
|
const port = 3000;
|
|
157
196
|
server.listen(port, () => {
|
|
158
197
|
console.log(`🚀 Server running at ${env.BASE_URL || 'http://localhost:3000'}`);
|
|
159
|
-
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
process.on('SIGINT', () => {
|
|
201
|
+
backgroundProcess.forEach(p => p.kill());
|
|
202
|
+
try { fs.rmSync(path.join(root, '.lumpia'), { recursive: true, force: true }); } catch(e){}
|
|
203
|
+
process.exit();
|
|
160
204
|
});
|
|
161
205
|
|
|
162
206
|
} catch (err) {
|
|
163
|
-
console.error('Fatal Error
|
|
207
|
+
console.error('Fatal Error:', err);
|
|
164
208
|
}
|
|
165
209
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export function loadConfig(root) {
|
|
5
|
+
const configPath = path.join(root, 'config.lmp');
|
|
6
|
+
const config = {
|
|
7
|
+
klambi: 'none' // default
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
if (fs.existsSync(configPath)) {
|
|
11
|
+
try {
|
|
12
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
13
|
+
const parsed = JSON.parse(content);
|
|
14
|
+
if (parsed.klambi) config.klambi = parsed.klambi;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.error('⚠️ Gagal moco config.lmp', e.message);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return config;
|
|
20
|
+
}
|