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.
@@ -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
- // Define Routes
8
- Jalan.gawe('/', 'HomeController@index');
9
- Jalan.gawe('/profile', 'HomeController@profile');
10
- Jalan.gawe('/api/products', 'ProductController@index');
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
- // Tampilkan halaman home
18
- // this.env bisa diakses di sini
19
- return this.tampil('home', {
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.tampil('profile', { name: 'Loyal User' });
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.js';
63
+ import ProductData from '../../app/models/Product.lmp';
44
64
 
45
65
  export default class ProductController extends Controller {
46
66
  index() {
47
- const result = Model.use(ProductData)
48
- .dimana('price', '>', 5500)
49
- .jupuk();
50
-
51
- return this.json({
52
- status: 'success',
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
- <h1>User Profile</h1>
92
- <p>Hello, <strong>{{ name }}</strong>!</p>
93
- <a href="/">Back Home</a>
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
- # Pilihan: 'true', 'false'
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
- export function createProject(parameter) {
121
- const projectName = parameter;
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
- console.log(' Please specify project name.');
124
- console.log('Example: lumpia create-project my-app');
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)) return console.log(`❌ Folder ${projectName} already exists.`);
161
+ if (fs.existsSync(root)) { console.log('❌ Folder Exists'); return; }
130
162
 
131
- console.log(`🔨 Building MVC foundation in ${projectName}...`);
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
- // Write files
141
- fs.writeFileSync(path.join(root, 'package.json'), packageJsonTemplate(projectName));
142
- fs.writeFileSync(path.join(root, '.env'), envTemplate); // Create .env
143
- fs.writeFileSync(path.join(root, 'routes', 'web.js'), routesTemplate);
144
- fs.writeFileSync(path.join(root, 'app', 'controllers', 'HomeController.js'), controllerTemplate);
145
- fs.writeFileSync(path.join(root, 'app', 'controllers', 'ProductController.js'), productControllerTemplate);
146
- fs.writeFileSync(path.join(root, 'app', 'models', 'Product.js'), modelTemplate);
147
- fs.writeFileSync(path.join(root, 'views', 'home.lmp'), viewHomeTemplate);
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('✅ Project ready! Standard MVC Structure with .env Support.');
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
  }
@@ -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 untuk mencocokkan Route dengan Parameter {id} dll
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]; // capture groups start at 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
- export async function serveProject() {
44
- const root = process.cwd();
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
- // --- 1. Version Check ---
47
- const userPackageJsonPath = path.join(root, 'package.json');
48
- if (fs.existsSync(userPackageJsonPath)) {
49
- const userPkg = JSON.parse(fs.readFileSync(userPackageJsonPath, 'utf-8'));
50
- const userLumpiaVer = (userPkg.dependencies && userPkg.dependencies.lumpiajs) || 'unknown';
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
- console.log(`ℹ️ LumpiaJS CLI Version: ${cliPackageJson.version}`);
53
- console.log(`ℹ️ Project Dependency Version: ${userLumpiaVer}`);
54
-
55
- const cleanUserVer = userLumpiaVer.replace(/[^0-9.]/g, '');
56
- if (cleanUserVer !== cliPackageJson.version && userLumpiaVer !== 'latest') {
57
- console.log('\n⚠️ PERINGATAN BEDA VERSI!');
58
- console.log(` Versi CLI kamu (${cliPackageJson.version}) beda dengan versi di project (${userLumpiaVer}).`);
59
- console.log(' Saran: Update project dependencies agar sinkron.');
60
- console.log(' Cara update: npm install lumpiajs@latest\n');
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
- const routesFile = path.join(root, 'routes', 'web.js');
65
- if (!fs.existsSync(routesFile)) return console.log("❌ This is not a LumpiaJS MVC project! (Cannot find routes/web.js)");
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
- console.log(`🌍 Environment: ${env.APP_ENV}`);
70
- console.log(`🐛 Debug Mode: ${env.APP_DEBUG}`);
105
+ const config = loadConfig(root);
71
106
 
72
- try {
73
- // Load User Routes
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
- console.log(`📥 ${method} ${pathname}`);
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 + '.js');
156
+ const controllerPath = path.join(root, 'app', 'controllers', controllerName + '.lmp');
106
157
 
107
- if (!fs.existsSync(controllerPath)) throw new Error(`Controller ${controllerName} not found in app/controllers!`);
158
+ if (!fs.existsSync(controllerPath)) throw new Error(`Controller ${controllerName} not found!`);
108
159
 
109
- // In Development/Local, always invalidate cache
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; // Available as this.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: Not Found</h1>');
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
- console.log(`(Press Ctrl+C to stop)`);
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 loading routes:', err);
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
+ }