lumpiajs 1.0.9 → 1.0.10

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 CHANGED
@@ -1,46 +1,64 @@
1
1
  # 🥟 LumpiaJS
2
2
 
3
- **Bahasa Pemrograman Web dengan Kearifan Lokal Semarangan.**
3
+ **"Bahasa Pemrograman Web dengan Kearifan Lokal Semarangan."**
4
+
5
+ Framework Static SPA 100% Client-Side. Coding pakai bahasa sehari-hari.
4
6
 
5
7
  ---
6
8
 
7
- ## 🦄 Deployment Ajaib (Universal)
9
+ ## 🗣️ Kamus Bahasa
8
10
 
9
- Ini fitur andalan LumpiaJS. Satu folder build (`dist`) bisa jalan di mana saja tanpa ubah kodingan.
11
+ | Semarangan | JS Asli | Arti |
12
+ | :------------ | :------------ | :-------------------- |
13
+ | **`aku`** | `this` | Diri Sendiri (Object) |
14
+ | **`fungsi`** | `function` | Fungsi |
15
+ | **`paten`** | `const` | Konstan |
16
+ | **`ono`** | `let` | Ada / Variabel |
17
+ | **`mengko`** | `async` | Nanti (Async) |
18
+ | **`nteni`** | `await` | Tunggu (Await) |
19
+ | **`balek`** | `return` | Kembali |
20
+ | **`kandani`** | `console.log` | Bilangi |
10
21
 
11
- User (Browser) akan menembak alamat `/api`.
22
+ _Plus fitur **Laravel Syntax**: `aku->tampil()`._
12
23
 
13
- - Jika di **Hosting PHP**, server otomatis mengarahkan ke `api.php`.
14
- - Jika di **Vercel/Node**, server otomatis mengarahkan ke `api.js`.
24
+ **Contoh Coding (`HomeController.lmp`):**
15
25
 
16
- ### 1. Build Project
26
+ ```javascript
27
+ export default class HomeController extends Controller {
28
+ mengko index() {
29
+ paten pesan = 'Halo Lur!';
17
30
 
18
- ```bash
19
- lumpia goreng
31
+ // Panggil fungsi view
32
+ balek aku->tampil('home', { msg: pesan });
33
+ }
34
+ }
20
35
  ```
21
36
 
22
- ### 2. Panduan Deploy
37
+ ---
23
38
 
24
- **A. Hosting PHP / XAMPP (Apache)**
39
+ ## 🚀 Cara Pakai
25
40
 
26
- 1. Copy `dist` ke server.
27
- 2. Edit **`api.php`** (Isi config database).
28
- 3. Selesai.
29
- _Server otomatis pakai `.htaccess` untuk routing._
41
+ **1. Install**
30
42
 
31
- **B. Vercel (Gratis)**
43
+ ```bash
44
+ npm install -g lumpiajs
45
+ ```
32
46
 
33
- 1. Drag `dist` ke Vercel (atau push git).
34
- 2. Set Environment Variables di Vercel (`DB_HOST`, `DB_USER`, dll).
35
- 3. Selesai.
36
- _Vercel otomatis baca `vercel.json` dan pakai `api.js` sebagai serverless function._
47
+ **2. Buat Project & Develop**
37
48
 
38
- **C. VPS (Node.js)**
49
+ ```bash
50
+ lumpia create-project warung-ku
51
+ cd warung-ku && npm install
52
+ lumpia kukus
53
+ ```
54
+
55
+ **3. Build Static (Goreng)**
56
+
57
+ ```bash
58
+ lumpia goreng
59
+ ```
39
60
 
40
- 1. Upload `dist`.
41
- 2. `npm install`
42
- 3. `npm start`
43
- _Node akan menjalankan `server.js`._
61
+ Upload folder `dist` kemana saja (Hosting Biasa/GitHub Pages).
44
62
 
45
63
  ---
46
64
 
@@ -1,45 +1,68 @@
1
1
 
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { spawnSync } from 'child_process';
5
- import { loadConfig } from '../core/Config.js';
6
-
7
- // --- HELPERS ---
8
- function transpileContent(content) {
2
+ // KAMUS SEMARANGAN (Regex Replacement Rules)
3
+ // Urutan penting! Keyword panjang dulu baru pendek.
4
+ const KAMUS = [
5
+ { from: /paten\s/g, to: 'const ' },
6
+ { from: /ono\s/g, to: 'let ' },
7
+ { from: /fungsi\s/g, to: 'function ' }, // Changed from 'gawe'
8
+ { from: /nteni\s/g, to: 'await ' },
9
+ { from: /mengko\s/g, to: 'async ' },
10
+ { from: /balek\s/g, to: 'return ' },
11
+ { from: /yen\s*\(/g, to: 'if(' },
12
+ { from: /liyane\s/g, to: 'else ' },
13
+ { from: /jajal\s*\{/g, to: 'try {' },
14
+ { from: /gagal\s*\(/g, to: 'catch(' },
15
+ { from: /kandani\(/g, to: 'console.log(' },
16
+ { from: /aku->/g, to: 'this.' }, // NEW: this -> aku
17
+ { from: /aku\./g, to: 'this.' }, // Support dot notation too
18
+ { from: /->/g, to: '.' }
19
+ ];
20
+
21
+ function transpileSemarangan(content) {
9
22
  let code = content;
23
+
24
+ // Import .lmp -> .js
10
25
  code = code.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
26
+ code = code.replace(/from\s+['"]lumpiajs['"]/g, "from '/core/lumpia.js'");
27
+
28
+ // Safe Replace Logic
29
+ // We must handle this carefully to not break valid JS code if mixed.
30
+ // 'aku' is common word, but as keyword usually followed by -> or .
31
+
11
32
  code = code.split('\n').map(line => {
12
- if (line.trim().startsWith('//')) return line;
13
- return line.replace(/->/g, '.');
33
+ let l = line;
34
+ if (l.trim().startsWith('//')) return l;
35
+
36
+ KAMUS.forEach(rule => {
37
+ l = l.replace(rule.from, rule.to);
38
+ });
39
+ return l;
14
40
  }).join('\n');
15
-
16
- // Rewrite imports for Browser
17
- code = code.replace(/from\s+['"]lumpiajs['"]/g, "from '/core/index.js'");
18
- code = code.replace(/from\s+['"]lumpiajs\/lib\/(.+?)['"]/g, "from '/core/$1'");
41
+
19
42
  return code;
20
43
  }
21
44
 
45
+ // ... Rest of build.js code (processDirectory, browserCore, indexHtml, buildProject) ...
46
+ // ... I will copy the previous logic but update browserCore as well.
47
+
48
+ import fs from 'fs';
49
+ import path from 'path';
50
+ import { spawnSync } from 'child_process';
51
+ import { loadConfig } from '../core/Config.js';
52
+
22
53
  function processDirectory(source, dest) {
23
54
  if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
24
-
25
- const items = fs.readdirSync(source);
26
- items.forEach(item => {
55
+ fs.readdirSync(source).forEach(item => {
27
56
  const srcPath = path.join(source, item);
28
57
  const destPath = path.join(dest, item);
29
- const stat = fs.statSync(srcPath);
30
-
31
- if (stat.isDirectory()) {
58
+ if (fs.statSync(srcPath).isDirectory()) {
32
59
  processDirectory(srcPath, destPath);
33
60
  } else {
34
- if (item.endsWith('.lmp')) {
61
+ if (item.endsWith('.lmp') || item.endsWith('.js')) {
35
62
  const content = fs.readFileSync(srcPath, 'utf8');
36
- const jsContent = transpileContent(content);
37
- const jsDest = destPath.replace('.lmp', '.js');
38
- fs.writeFileSync(jsDest, jsContent);
39
- } else if (item.endsWith('.js')) {
40
- const content = fs.readFileSync(srcPath, 'utf8');
41
- const jsContent = transpileContent(content);
42
- fs.writeFileSync(destPath, jsContent);
63
+ const jsContent = transpileSemarangan(content);
64
+ const finalDest = destPath.replace('.lmp', '.js');
65
+ fs.writeFileSync(finalDest, jsContent);
43
66
  } else {
44
67
  fs.copyFileSync(srcPath, destPath);
45
68
  }
@@ -47,263 +70,87 @@ function processDirectory(source, dest) {
47
70
  });
48
71
  }
49
72
 
50
- // --- 1. PHP BACKEND ADAPTER ---
51
- const phpBridgeContent = `<?php
52
- header("Access-Control-Allow-Origin: *");
53
- header("Access-Control-Allow-Headers: Content-Type");
54
- header("Content-Type: application/json");
55
-
56
- // ⚠️ EDIT CONFIG INI
57
- $host = "localhost";
58
- $user = "root";
59
- $pass = "";
60
- $db = "lumpia_db";
61
-
62
- $conn = new mysqli($host, $user, $pass, $db);
63
- if ($conn->connect_error) die(json_encode(["error" => $conn->connect_error]));
64
-
65
- $input = json_decode(file_get_contents('php://input'), true);
66
- if (!$input) exit;
67
-
68
- try {
69
- $stmt = $conn->prepare($input['sql']);
70
- if($input['params']) {
71
- $types = str_repeat("s", count($input['params']));
72
- $stmt->bind_param($types, ...$input['params']);
73
- }
74
- $stmt->execute();
75
- $res = $stmt->get_result();
76
- $data = $res ? $res->fetch_all(MYSQLI_ASSOC) : ["affected" => $stmt->affected_rows];
77
- echo json_encode($data);
78
- } catch (Exception $e) {
79
- http_response_code(500);
80
- echo json_encode(["error" => $e->getMessage()]);
81
- }
82
- $conn->close();
83
- ?>`;
84
-
85
- // --- 2. NODE.JS / VERCEL BACKEND ADAPTER ---
86
- // Ini file 'api.js' yang akan dijalankan oleh Vercel atau Server.js local
87
- const nodeBridgeContent = `
88
- import { createPool } from 'mysql2/promise';
89
-
90
- // ⚠️ CONFIG DARI ENV (Vercel/Node style)
91
- const pool = createPool({
92
- host: process.env.DB_HOST || 'localhost',
93
- user: process.env.DB_USER || 'root',
94
- password: process.env.DB_PASSWORD || '',
95
- database: process.env.DB_NAME || 'lumpia_db',
96
- waitForConnections: true,
97
- connectionLimit: 10
98
- });
99
-
100
- export default async function handler(req, res) {
101
- // Vercel / Express handler signature
102
- if (req.method !== 'POST') {
103
- res.statusCode = 405;
104
- return res.end('Method Not Allowed');
105
- }
106
-
107
- try {
108
- // Parsing body helper
109
- let body = req.body;
110
- if (typeof body === 'string') body = JSON.parse(body); // if raw string
111
-
112
- const { sql, params } = body;
113
- const [rows] = await pool.execute(sql, params);
114
-
115
- res.statusCode = 200;
116
- res.setHeader('Content-Type', 'application/json');
117
- res.end(JSON.stringify(rows));
118
- } catch (error) {
119
- console.error(error);
120
- res.statusCode = 500;
121
- res.setHeader('Content-Type', 'application/json');
122
- res.end(JSON.stringify({ error: error.message }));
123
- }
124
- }
125
- `;
126
-
127
- // --- 3. SERVER.JS (Standalone Node Server) ---
128
- // Server statis + API Handler
129
- const serverJsContent = `
130
- import http from 'http';
131
- import fs from 'fs';
132
- import path from 'path';
133
- import apiHandler from './api.js';
134
-
135
- const root = process.cwd();
136
-
137
- const server = http.createServer(async (req, res) => {
138
- const url = new URL(req.url, 'http://' + req.headers.host);
139
-
140
- // API ROUTE
141
- if (url.pathname === '/api') {
142
- let body = '';
143
- req.on('data', chunk => body += chunk);
144
- req.on('end', () => {
145
- req.body = body ? JSON.parse(body) : {};
146
- apiHandler(req, res);
147
- });
148
- return;
149
- }
150
-
151
- // STATIC FILES
152
- let filePath = path.join(root, url.pathname === '/' ? 'index.html' : url.pathname);
153
-
154
- // SPA Fallback: If not file, serve index.html
155
- if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
156
- filePath = path.join(root, 'index.html');
157
- }
158
-
159
- const ext = path.extname(filePath);
160
- const mime = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css' };
161
- res.writeHead(200, { 'Content-Type': mime[ext] || 'application/octet-stream' });
162
- fs.createReadStream(filePath).pipe(res);
163
- });
164
-
165
- const port = process.env.PORT || 3000;
166
- server.listen(port, () => console.log('🚀 Server running on port ' + port));
167
- `;
168
-
169
- // --- 4. BROWSER CORE (Polymorphic Client) ---
170
- const browserCoreIndex = `
73
+ const browserCore = `
171
74
  export class Controller {
172
- constructor() { this.env = {}; this.params = {}; }
173
-
174
- async tampil(viewName, data = {}) {
175
- const response = await fetch('/views/' + viewName + '.lmp');
176
- let html = await response.text();
177
-
178
- const matchKulit = html.match(/<kulit>([\\s\\S]*?)<\\/kulit>/);
179
- const matchIsi = html.match(/<isi>([\\s\\S]*?)<\\/isi>/);
180
- const matchKlambi = html.match(/<klambi>([\\s\\S]*?)<\\/klambi>/);
181
-
182
- let body = matchKulit ? matchKulit[1] : '';
183
- let script = matchIsi ? matchIsi[1] : '';
184
- let css = matchKlambi ? matchKlambi[1] : '';
185
-
186
- for (const [key, value] of Object.entries(data)) {
187
- const regex = new RegExp('{{\\\\s*' + key + '\\\\s*}}', 'g');
188
- body = body.replace(regex, value);
189
- }
190
-
191
- document.getElementById('app').innerHTML = body;
192
-
193
- if(css) {
194
- const style = document.createElement('style');
195
- style.textContent = css;
196
- document.head.appendChild(style);
197
- }
198
-
199
- const dict = [
200
- { asal: /ono\\s/g, jadi: 'let ' }, { asal: /paten\\s/g, jadi: 'const ' },
201
- { asal: /gawe\\s/g, jadi: 'function ' }, { asal: /yen\\s/g, jadi: 'if ' },
202
- { asal: /liyane/g, jadi: 'else' }, { asal: /mandek;/g, jadi: 'return;' },
203
- { asal: /ora\\s/g, jadi: '!' },
204
- ];
205
- dict.forEach(k => script = script.replace(k.asal, k.jadi));
206
-
207
- try { new Function(script)(); } catch(e) { console.error("Error script <isi>:", e); }
75
+ constructor() { this.params={}; }
76
+ async tampil(viewName, data={}) {
77
+ try {
78
+ const res = await fetch('/views/'+viewName+'.lmp');
79
+ if(!res.ok) throw new Error('View 404');
80
+ let html = await res.text();
81
+
82
+ const matchKulit = html.match(/<kulit>([\\s\\S]*?)<\\/kulit>/);
83
+ const matchIsi = html.match(/<isi>([\\s\\S]*?)<\\/isi>/);
84
+ const matchKlambi = html.match(/<klambi>([\\s\\S]*?)<\\/klambi>/);
85
+
86
+ let body = matchKulit?matchKulit[1]:'', script=matchIsi?matchIsi[1]:'', css=matchKlambi?matchKlambi[1]:'';
87
+
88
+ for(const [k,v] of Object.entries(data)) {
89
+ let s = typeof v === 'object' ? JSON.stringify(v) : v;
90
+ body = body.replace(new RegExp('{{\\\\s*'+k+'\\\\s*}}','g'), s);
91
+ }
92
+
93
+ document.getElementById('app').innerHTML = body;
94
+ if(css && !document.getElementById('css-'+viewName)) {
95
+ const s = document.createElement('style'); s.id='css-'+viewName; s.textContent=css;
96
+ document.head.appendChild(s);
97
+ }
98
+
99
+ if(script) {
100
+ // Client-side transpile
101
+ const kamus = [
102
+ {f:/paten\\s/g,t:'const '}, {f:/ono\\s/g,t:'let '}, {f:/fungsi\\s/g,t:'function '},
103
+ {f:/nteni\\s/g,t:'await '}, {f:/mengko\\s/g,t:'async '}, {f:/balek\\s/g,t:'return '},
104
+ {f:/yen\\s*\\(/g,t:'if('}, {f:/liyane\\s/g,t:'else '}, {f:/kandani\\(/g,t:'console.log('},
105
+ {f:/aku->/g,t:'this.'}, {f:/aku\\./g,t:'this.'}
106
+ ];
107
+ kamus.forEach(r => script = script.replace(r.f, r.t));
108
+ new Function(script)();
109
+ }
110
+ } catch(e) { document.getElementById('app').innerHTML = e.message; }
208
111
  }
209
-
210
- json(data) { document.getElementById('app').innerText = JSON.stringify(data, null, 2); }
211
112
  }
113
+ export const Jalan = { routes:[], get:(p,a)=>Jalan.routes.push({p,a}) };
114
+ `;
212
115
 
213
- // POLYMORPHIC DB CLIENT
214
- export class DB {
215
- static table(name) { return new QueryBuilder(name); }
216
- static async query(sql, params) {
217
- // Tembak ke endpoint /api
218
- // Server (Apache/Vercel/Node) yang akan nentuin diteruske ke api.php atau api.js
219
- const res = await fetch('/api', {
220
- method: 'POST',
221
- headers: {'Content-Type': 'application/json'},
222
- body: JSON.stringify({sql, params})
223
- });
224
- return await res.json();
116
+ const indexHtml = `<!DOCTYPE html>
117
+ <html lang="en">
118
+ <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>LumpiaJS</title><link rel="stylesheet" href="/public/css/style.css"></head>
119
+ <body><div id="app">Loading...</div>
120
+ <script type="module">
121
+ import { Jalan } from '/routes/web.js';
122
+ async function navigate() {
123
+ const p = window.location.pathname;
124
+ let m = null, args = {};
125
+ for(let r of Jalan.routes) {
126
+ let reg = new RegExp('^'+r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)')+'$');
127
+ let res = p.match(reg);
128
+ if(res){ m=r; args=res.slice(1); break; }
225
129
  }
130
+ if(m) {
131
+ const [cName, fName] = m.a.split('@');
132
+ try {
133
+ const mod = await import('/app/controllers/'+cName+'.js?'+Date.now());
134
+ const C = mod.default; const i = new C(); i.params=args;
135
+ await i[fName](...args);
136
+ } catch(e) { console.error(e); document.getElementById('app').innerHTML='Error'; }
137
+ } else { document.getElementById('app').innerHTML='404'; }
226
138
  }
227
-
228
- class QueryBuilder {
229
- constructor(table) { this.table = table; this.conds = []; this.binds = []; }
230
- where(c, o, v) { if(v===undefined){v=o;o='=';} this.conds.push(c+' '+o+' ?'); this.binds.push(v); return this; }
231
- orderBy(c, d='ASC') { this.order = c+' '+d; return this; }
232
- async get() {
233
- let sql = 'SELECT * FROM ' + this.table;
234
- if(this.conds.length) sql += ' WHERE ' + this.conds.join(' AND ');
235
- if(this.order) sql += ' ORDER BY ' + this.order;
236
- return await DB.query(sql, this.binds);
139
+ window.addEventListener('popstate', navigate);
140
+ document.body.addEventListener('click', e => {
141
+ if(e.target.tagName==='A' && e.target.href.startsWith(window.location.origin)) {
142
+ e.preventDefault(); history.pushState(null,'',e.target.href); navigate();
237
143
  }
238
- }
239
-
240
- export const Jalan = {
241
- routes: [],
242
- get: (p, a) => Jalan.routes.push({p, a, m:'GET'}),
243
- post: (p, a) => Jalan.routes.push({p, a, m:'POST'})
244
- };
245
- `;
246
-
247
- const indexHtmlContent = `<!DOCTYPE html>
248
- <html lang="en">
249
- <head>
250
- <meta charset="UTF-8">
251
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
252
- <title>LumpiaJS App</title>
253
- <link rel="stylesheet" href="/public/css/style.css">
254
- </head>
255
- <body>
256
- <div id="app">Loading...</div>
257
-
258
- <script type="module">
259
- import { Jalan } from '/routes/web.js';
260
-
261
- async function navigate() {
262
- const path = window.location.pathname;
263
- let match = null, params = {};
264
- for(let r of Jalan.routes) {
265
- const regexStr = '^' + r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)') + '$';
266
- const regex = new RegExp(regexStr);
267
- const m = path.match(regex);
268
- if(m) { match = r; params = m.slice(1); break; }
269
- }
270
-
271
- if(match) {
272
- const [cName, mName] = match.a.split('@');
273
- try {
274
- const module = await import('/app/controllers/' + cName + '.js?' + Date.now());
275
- const Controller = module.default;
276
- const ctrl = new Controller();
277
- await ctrl[mName](...params);
278
- } catch(e) {
279
- document.getElementById('app').innerHTML = '<h1>Error</h1><p>' + e.message + '</p>';
280
- }
281
- } else {
282
- document.getElementById('app').innerHTML = '<h1>404</h1>';
283
- }
284
- }
285
-
286
- window.addEventListener('popstate', navigate);
287
- document.body.addEventListener('click', e => {
288
- if(e.target.tagName === 'A' && e.target.href.startsWith(window.location.origin)) {
289
- e.preventDefault();
290
- history.pushState(null, '', e.target.href);
291
- navigate();
292
- }
293
- });
294
- navigate();
295
- </script>
296
- </body>
297
- </html>`;
298
-
144
+ });
145
+ navigate();
146
+ </script></body></html>`;
299
147
 
300
148
  export function buildProject() {
301
149
  const root = process.cwd();
302
150
  const dist = path.join(root, 'dist');
303
151
  const config = loadConfig(root);
304
-
305
- console.log('🍳 Mulai Menggoreng (Universal Hybrid Build)...');
306
-
152
+
153
+ console.log('🍳 Goreng Project (Mode Bahasa Semarangan)...');
307
154
  if (fs.existsSync(dist)) fs.rmSync(dist, { recursive: true, force: true });
308
155
  fs.mkdirSync(dist);
309
156
 
@@ -312,69 +159,15 @@ export function buildProject() {
312
159
  spawnSync(cmd, ['tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--minify'], { cwd: root, stdio: 'ignore', shell: true });
313
160
  }
314
161
 
315
- console.log('📂 Converting Code...');
316
162
  processDirectory(path.join(root, 'app'), path.join(dist, 'app'));
317
163
  processDirectory(path.join(root, 'routes'), path.join(dist, 'routes'));
318
-
319
- fs.mkdirSync(path.join(dist, 'core'), { recursive: true });
320
- fs.writeFileSync(path.join(dist, 'core', 'index.js'), browserCoreIndex);
321
-
322
164
  fs.cpSync(path.join(root, 'views'), path.join(dist, 'views'), { recursive: true });
323
-
324
- if (fs.existsSync(path.join(root, 'public'))) {
325
- fs.cpSync(path.join(root, 'public'), path.join(dist, 'public'), { recursive: true });
326
- }
165
+ if (fs.existsSync(path.join(root, 'public'))) fs.cpSync(path.join(root, 'public'), path.join(dist, 'public'), { recursive: true });
327
166
 
328
- fs.writeFileSync(path.join(dist, 'index.html'), indexHtmlContent);
329
-
330
- // --- GENERATE ALL ADAPTERS ---
331
-
332
- // 1. PHP Adapter
333
- fs.writeFileSync(path.join(dist, 'api.php'), phpBridgeContent);
334
-
335
- // 2. Node/Vercel Adapter
336
- fs.writeFileSync(path.join(dist, 'api.js'), nodeBridgeContent);
337
- fs.writeFileSync(path.join(dist, 'server.js'), serverJsContent);
338
- fs.writeFileSync(path.join(dist, 'package.json'), JSON.stringify({
339
- "type": "module",
340
- "scripts": { "start": "node server.js" },
341
- "dependencies": { "mysql2": "^3.0.0" }
342
- }, null, 2));
343
-
344
- // 3. Routing Rules
345
-
346
- // .htaccess (Apache) -> Redirect /api ke api.php
347
- fs.writeFileSync(path.join(dist, '.htaccess'), `
348
- <IfModule mod_rewrite.c>
349
- RewriteEngine On
350
- RewriteBase /
351
-
352
- # API Routing: /api -> api.php
353
- RewriteRule ^api$ api.php [L]
354
-
355
- # SPA Routing: Everything else -> index.html
356
- RewriteCond %{REQUEST_FILENAME} !-f
357
- RewriteCond %{REQUEST_FILENAME} !-d
358
- RewriteRule . /index.html [L]
359
- </IfModule>
360
- `);
361
-
362
- // vercel.json (Vercel) -> Redirect /api ke api.js
363
- fs.writeFileSync(path.join(dist, 'vercel.json'), JSON.stringify({
364
- "rewrites": [
365
- { "source": "/api", "destination": "/api.js" },
366
- { "source": "/(.*)", "destination": "/index.html" }
367
- ],
368
- "functions": {
369
- "api.js": { "includeFiles": "package.json" }
370
- }
371
- }, null, 2));
167
+ fs.mkdirSync(path.join(dist, 'core'), { recursive: true });
168
+ fs.writeFileSync(path.join(dist, 'core', 'lumpia.js'), browserCore);
169
+ fs.writeFileSync(path.join(dist, 'index.html'), indexHtml);
170
+ fs.writeFileSync(path.join(dist, '.htaccess'), `<IfModule mod_rewrite.c>\nRewriteEngine On\nRewriteBase /\nRewriteRule ^index\\.html$ - [L]\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule . /index.html [L]\n</IfModule>`);
372
171
 
373
- console.log('✅ Mateng! (Universal Build)');
374
- console.log('----------------------------------------------------');
375
- console.log('📂 Folder "dist" ini UNIVERSAL:');
376
- console.log(' - Hosting PHP (XAMPP/cPanel): Otomatis pake api.php (via .htaccess)');
377
- console.log(' - Vercel: Otomatis pake api.js (via vercel.json)');
378
- console.log(' - Node VPS: Otomatis pake api.js (via server.js)');
379
- console.log('----------------------------------------------------');
172
+ console.log('✅ Mateng! (Support Bahasa Semarangan)');
380
173
  }
@@ -4,155 +4,84 @@ import prompts from 'prompts';
4
4
 
5
5
  const routesTemplate = `import { Jalan } from 'lumpiajs';
6
6
 
7
- // Pakai gaya Laravel (->) enak to?
8
7
  Jalan->get('/', 'HomeController@index');
9
- Jalan->get('/db-test', 'HomeController@testDb');
10
- Jalan->get('/profile', 'HomeController@profile');
11
- Jalan->get('/api/products', 'ProductController@index');
8
+ Jalan->get('/toko', 'ProductController@tampilBarang');
12
9
  `;
13
10
 
14
- const controllerTemplate = `import { Controller, DB } from 'lumpiajs';
11
+ const controllerTemplate = `import { Controller } from 'lumpiajs';
15
12
 
16
13
  export default class HomeController extends Controller {
17
- index() {
18
- // "this.tampil" juga bisa ditulis "this->tampil"
19
- // Transpiler LumpiaJS sing ngatur, Bos!
20
- return this->tampil('home', {
21
- message: 'Welcome to LumpiaJS MVC!',
22
- author: 'Pakdhe Koding',
23
- env: this.env.APP_ENV
14
+ mengko index() {
15
+ // Pake Bahasa Semarangan, Lur!
16
+ paten pesen = 'Sugeng Rawuh di Website Statis!';
17
+
18
+ // 'aku' menggantikan 'this'
19
+ balek aku->tampil('home', {
20
+ message: pesen,
21
+ info: 'Dibuat dengan LumpiaJS'
24
22
  });
25
23
  }
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
-
48
- profile() {
49
- return this->tampil('profile', { name: 'Loyal User' });
50
- }
51
24
  }
52
25
  `;
53
26
 
54
- const modelTemplate = `// Example dummy data (Static)
55
- const productData = [
56
- { id: 1, name: 'Lumpia Basah', price: 5000 },
57
- { id: 2, name: 'Lumpia Goreng', price: 6000 }
58
- ];
59
- export default productData;
60
- `;
61
-
62
- const productControllerTemplate = `import { Controller, Model } from 'lumpiajs';
63
- import ProductData from '../../app/models/Product.lmp';
27
+ const productControllerTemplate = `import { Controller } from 'lumpiajs';
64
28
 
65
29
  export default class ProductController extends Controller {
66
- index() {
67
- // Model Static juga bisa pakai ->
68
- const result = Model->use(ProductData)
69
- ->dimana('price', '>', 5500)
70
- ->jupuk();
30
+ mengko tampilBarang() {
31
+ // Contoh API
32
+ ono data = [];
33
+
34
+ jajal {
35
+ paten respon = nteni fetch('https://fakestoreapi.com/products?limit=3');
36
+ data = nteni respon.json();
37
+ kandani('Data sukses!');
38
+ } gagal (e) {
39
+ kandani(e);
40
+ }
71
41
 
72
- return this->json({ status: 'success', data: result });
42
+ balek aku->tampil('product', {
43
+ daftar: data.map(i => '<li>' + i.title + '</li>').join('')
44
+ });
73
45
  }
74
46
  }
75
47
  `;
76
48
 
77
- const viewProfileTemplate = `<lump>
49
+ const homeViewTemplate = `<lump>
50
+ <klambi>
51
+ h1 { color: #d35400; text-align: center; }
52
+ </klambi>
78
53
  <kulit>
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>
54
+ <div style="text-align: center; font-family: sans-serif; margin-top: 50px;">
55
+ <h1>{{ message }}</h1>
56
+ <p>{{ info }}</p>
57
+ <a href="/toko">Cek Toko Sebelah</a>
83
58
  </div>
84
59
  </kulit>
85
60
  </lump>`;
86
61
 
87
- const envTemplate = `
88
- BASE_URL="http://localhost:3000"
89
- APP_ENV="local"
90
- APP_DEBUG="true"
91
-
92
- # Database Config
93
- DB_HOST="localhost"
94
- DB_USER="root"
95
- DB_PASSWORD=""
96
- DB_NAME="lumpia_db"
97
- `;
98
-
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
- };
62
+ const productViewTemplate = `<lump>
63
+ <kulit>
64
+ <div style="padding: 20px; font-family: sans-serif;">
65
+ <h1>Daftar Barang</h1>
66
+ <ul>{{ daftar }}</ul>
67
+ <a href="/">Balik Omah</a>
68
+ </div>
69
+ </kulit>
70
+ </lump>`;
139
71
 
140
72
  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);
73
+ return JSON.stringify({
74
+ name, version: "1.0.0", type: "module",
75
+ scripts: { "start": "lumpia kukus", "build": "lumpia goreng" },
76
+ dependencies: { "lumpiajs": "latest" },
77
+ devDependencies: style==='tailwindcss'?{"tailwindcss":"^3.4.0"}:{}
78
+ }, null, 2);
150
79
  };
151
80
 
152
81
  export async function createProject(parameter) {
153
82
  let projectName = parameter;
154
83
  if (!projectName) {
155
- const res = await prompts({ type: 'text', name: 'val', message: 'Jeneng project?', initial: 'my-app' });
84
+ const res = await prompts({ type: 'text', name: 'val', message: 'Jeneng project?', initial: 'my-lumpia-app' });
156
85
  projectName = res.val;
157
86
  }
158
87
  if (!projectName) return;
@@ -162,42 +91,33 @@ export async function createProject(parameter) {
162
91
 
163
92
  const styleRes = await prompts({
164
93
  type: 'select', name: 'val', message: 'Styling?',
165
- choices: [{title:'Vanilla',value:'none'},{title:'Tailwind',value:'tailwindcss'},{title:'Bootstrap',value:'bootstrap'}],
94
+ choices: [{title:'Vanilla',value:'none'},{title:'Tailwind',value:'tailwindcss'}],
166
95
  initial: 0
167
96
  });
168
- const style = styleRes.val;
169
- if (!style) return;
170
97
 
171
- // Structure
172
98
  fs.mkdirSync(root);
173
99
  fs.mkdirSync(path.join(root, 'app', 'controllers'), { recursive: true });
174
- fs.mkdirSync(path.join(root, 'app', 'models'), { recursive: true });
175
100
  fs.mkdirSync(path.join(root, 'routes'));
176
101
  fs.mkdirSync(path.join(root, 'views'));
177
102
  fs.mkdirSync(path.join(root, 'aset', 'css'), { recursive: true });
178
103
  fs.mkdirSync(path.join(root, 'public', 'css'), { recursive: true });
179
104
 
180
- // Files
181
- fs.writeFileSync(path.join(root, 'package.json'), generatePackageJson(projectName, style));
105
+ fs.writeFileSync(path.join(root, 'package.json'), generatePackageJson(projectName, styleRes.val));
182
106
  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));
107
+ fs.writeFileSync(path.join(root, 'config.lmp'), JSON.stringify({ klambi: styleRes.val }, null, 2));
185
108
 
186
109
  fs.writeFileSync(path.join(root, 'routes', 'web.lmp'), routesTemplate);
187
110
  fs.writeFileSync(path.join(root, 'app', 'controllers', 'HomeController.lmp'), controllerTemplate);
188
111
  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));
191
- fs.writeFileSync(path.join(root, 'views', 'profile.lmp'), viewProfileTemplate);
112
+ fs.writeFileSync(path.join(root, 'views', 'home.lmp'), homeViewTemplate);
113
+ fs.writeFileSync(path.join(root, 'views', 'product.lmp'), productViewTemplate);
192
114
 
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);
115
+ if (styleRes.val === 'tailwindcss') {
116
+ fs.writeFileSync(path.join(root, 'tailwind.config.js'), `module.exports={content:["./views/**/*.lmp"],theme:{extend:{}},plugins:[]}`);
117
+ fs.writeFileSync(path.join(root, 'aset', 'css', 'style.css'), '@tailwind base; @tailwind components; @tailwind utilities;');
197
118
  } else {
198
- fs.writeFileSync(path.join(root, 'aset', 'css', 'style.css'), style === 'bootstrap' ? '/* Bootstrap Imported in HTML */' : '/* CSS */');
119
+ fs.writeFileSync(path.join(root, 'aset', 'css', 'style.css'), '/* CSS */');
199
120
  }
200
121
 
201
122
  console.log(`✅ Project "${projectName}" Ready!`);
202
- console.log(`cd ${projectName} && npm install && lumpia kukus`);
203
123
  }
@@ -1,59 +1,57 @@
1
+
2
+ // KAMUS SEMARANGAN (Regex Replacement Rules)
3
+ const KAMUS = [
4
+ { from: /paten\s/g, to: 'const ' },
5
+ { from: /ono\s/g, to: 'let ' },
6
+ { from: /fungsi\s/g, to: 'function ' },
7
+ { from: /nteni\s/g, to: 'await ' },
8
+ { from: /mengko\s/g, to: 'async ' },
9
+ { from: /balek\s/g, to: 'return ' },
10
+ { from: /yen\s*\(/g, to: 'if(' },
11
+ { from: /liyane\s/g, to: 'else ' },
12
+ { from: /jajal\s*\{/g, to: 'try {' },
13
+ { from: /gagal\s*\(/g, to: 'catch(' },
14
+ { from: /kandani\(/g, to: 'console.log(' },
15
+ { from: /aku->/g, to: 'this.' },
16
+ { from: /aku\./g, to: 'this.' },
17
+ { from: /->/g, to: '.' }
18
+ ];
19
+
1
20
  import fs from 'fs';
2
21
  import path from 'path';
3
22
  import http from 'http';
4
23
  import { spawn } from 'child_process';
5
- import { routes } from '../core/Router.js';
6
- import { renderLumpia } from '../core/View.js';
7
24
  import { loadEnv } from '../core/Env.js';
8
25
  import { loadConfig } from '../core/Config.js';
9
26
 
10
- const cliPackageJson = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url)));
27
+ // ... Same helper functions (matchRoute, backgroundProcess, startTailwindWatcher) ...
11
28
 
12
- // Helper to Match Routes
13
29
  function matchRoute(definedRoute, method, pathname) {
14
30
  if (definedRoute.method !== method) return null;
15
31
  if (definedRoute.path === pathname) return { params: {} };
16
-
17
32
  const paramNames = [];
18
33
  const regexPath = definedRoute.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, name) => {
19
34
  paramNames.push(name);
20
35
  return '([^/]+)';
21
36
  });
22
-
23
37
  if (regexPath === definedRoute.path) return null;
24
38
  const regex = new RegExp(`^${regexPath}$`);
25
39
  const match = pathname.match(regex);
26
40
  if (match) {
27
41
  const params = {};
28
- paramNames.forEach((name, index) => {
29
- params[name] = match[index + 1];
30
- });
42
+ paramNames.forEach((name, index) => { params[name] = match[index + 1]; });
31
43
  return { params };
32
44
  }
33
45
  return null;
34
46
  }
35
47
 
36
48
  const backgroundProcess = [];
37
-
38
49
  function startTailwindWatcher(root) {
39
- console.log('🎨 TailwindCSS detected! Starting watcher...');
40
50
  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 });
51
+ const tailwind = spawn(cmd, ['tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--watch'], { cwd: root, stdio: 'ignore', shell: true });
44
52
  backgroundProcess.push(tailwind);
45
53
  }
46
54
 
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
55
  async function loadLumpiaModule(filePath) {
58
56
  const originalContent = fs.readFileSync(filePath, 'utf-8');
59
57
  const cacheDir = path.join(process.cwd(), '.lumpia', 'cache');
@@ -63,72 +61,45 @@ async function loadLumpiaModule(filePath) {
63
61
  const flatName = relativePath.replace(/[\/\\]/g, '_').replace('.lmp', '.js');
64
62
  const destPath = path.join(cacheDir, flatName);
65
63
 
66
- // --- TRANSPILE LOGIC ---
67
64
  let transcoded = originalContent;
68
-
69
- // 1. Replace Imports: .lmp -> .js
70
65
  transcoded = transcoded.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
71
66
 
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
67
  transcoded = transcoded.split('\n').map(line => {
81
- // Cek komentar //
82
- if (line.trim().startsWith('//')) return line;
83
-
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, '.');
68
+ let l = line;
69
+ if (l.trim().startsWith('//')) return l;
70
+ KAMUS.forEach(rule => l = l.replace(rule.from, rule.to));
71
+ return l;
88
72
  }).join('\n');
89
73
 
90
74
  fs.writeFileSync(destPath, transcoded);
91
-
92
- // 4. Import file .js
93
75
  const module = await import('file://' + destPath + '?t=' + Date.now());
94
76
  return module;
95
77
  }
96
78
 
97
-
98
79
  export async function serveProject() {
99
80
  const root = process.cwd();
100
-
101
81
  const routesFile = path.join(root, 'routes', 'web.lmp');
102
- if (!fs.existsSync(routesFile)) return console.log("❌ Not a LumpiaJS Project. (Missing routes/web.lmp)");
82
+ if (!fs.existsSync(routesFile)) return console.log("❌ Missing routes/web.lmp");
103
83
 
104
84
  const env = loadEnv(root);
105
85
  const config = loadConfig(root);
106
-
107
86
  if (config.klambi === 'tailwindcss') startTailwindWatcher(root);
108
- else if (config.klambi === 'bootstrap') handleBootstrap(root);
109
87
 
110
88
  try {
111
89
  await loadLumpiaModule(routesFile);
112
- console.log(`🛣️ Routes registered: ${routes.length}`);
113
-
114
- // Info Syntax
115
- console.log(`✨ Syntax Mode: PHP/Laravel Style (->) is enabled in .lmp files!`);
90
+ const activeRoutes = global.LumpiaRouter || [];
91
+ console.log(`🛣️ Routes registered: ${activeRoutes.length}`);
92
+ console.log(`✨ Mode: Semarangan (paten, ono, fungsi, aku)`);
116
93
 
117
94
  const server = http.createServer(async (req, res) => {
118
95
  const method = req.method;
119
96
  const url = new URL(req.url, `http://${req.headers.host}`);
120
97
  const pathname = url.pathname;
121
98
 
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
- };
99
+ const publicMap = { '/css/': path.join(root, 'public', 'css'), '/vendor/': path.join(root, 'public', 'vendor') };
128
100
  for (const [prefix, localPath] of Object.entries(publicMap)) {
129
101
  if (pathname.startsWith(prefix)) {
130
- const relativePath = pathname.slice(prefix.length);
131
- const filePath = path.join(localPath, relativePath);
102
+ const filePath = path.join(localPath, pathname.slice(prefix.length));
132
103
  if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
133
104
  const ext = path.extname(filePath);
134
105
  const mime = ext === '.css' ? 'text/css' : (ext === '.js' ? 'text/javascript' : 'application/octet-stream');
@@ -139,71 +110,39 @@ export async function serveProject() {
139
110
  }
140
111
  }
141
112
 
142
- let match = null;
143
- let params = {};
144
- for (const route of routes) {
113
+ let match = null, params = {};
114
+ const currentRoutes = global.LumpiaRouter || [];
115
+ for (const route of currentRoutes) {
145
116
  const result = matchRoute(route, method, pathname);
146
- if (result) {
147
- match = route;
148
- params = result.params;
149
- break;
150
- }
117
+ if (result) { match = route; params = result.params; break; }
151
118
  }
152
119
 
153
120
  if (match) {
154
121
  try {
155
- const [controllerName, methodName] = match.action.split('@');
156
- const controllerPath = path.join(root, 'app', 'controllers', controllerName + '.lmp');
157
-
158
- if (!fs.existsSync(controllerPath)) throw new Error(`Controller ${controllerName} not found!`);
122
+ const [cName, mName] = match.action.split('@');
123
+ const cPath = path.join(root, 'app', 'controllers', cName + '.lmp');
124
+ if (!fs.existsSync(cPath)) throw new Error('Controller Not Found');
159
125
 
160
- const module = await loadLumpiaModule(controllerPath);
161
- const ControllerClass = module.default;
162
- const instance = new ControllerClass();
126
+ const module = await loadLumpiaModule(cPath);
127
+ const Ctrl = module.default;
128
+ const instance = new Ctrl();
129
+ instance.env = env; instance.params = params; instance.config = config;
163
130
 
164
- instance.env = env;
165
- instance.params = params;
166
- instance.config = config;
167
-
168
- if (typeof instance[methodName] !== 'function') throw new Error(`Method ${methodName} missing`);
169
-
170
- const args = Object.values(params);
171
- const result = await instance[methodName](...args);
172
-
173
- if (result.type === 'html') {
174
- res.writeHead(200, {'Content-Type': 'text/html'});
175
- res.end(result.content);
176
- } else if (result.type === 'json') {
177
- res.writeHead(200, {'Content-Type': 'application/json'});
131
+ const result = await instance[mName](...Object.values(params));
132
+
133
+ if (result && result.type) {
134
+ res.writeHead(200, {'Content-Type': result.type==='json'?'application/json':'text/html'});
178
135
  res.end(result.content);
179
136
  } else {
180
- res.writeHead(200, {'Content-Type': 'text/plain'});
181
137
  res.end(String(result));
182
138
  }
183
139
  } catch (e) {
184
- console.error(e);
185
- const errorMsg = env.APP_DEBUG === 'true' ? `<pre>${e.stack}</pre>` : `<h1>Server Error</h1>`;
186
- res.writeHead(500, {'Content-Type': 'text/html'});
187
- res.end(errorMsg);
140
+ res.writeHead(500); res.end(`<pre>${e.stack}</pre>`);
188
141
  }
189
142
  } else {
190
- res.writeHead(404, {'Content-Type': 'text/html'});
191
- res.end('<h1>404 Not Found</h1>');
143
+ res.writeHead(404); res.end('404 Not Found');
192
144
  }
193
145
  });
194
-
195
- const port = 3000;
196
- server.listen(port, () => {
197
- console.log(`🚀 Server running at ${env.BASE_URL || 'http://localhost:3000'}`);
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();
204
- });
205
-
206
- } catch (err) {
207
- console.error('Fatal Error:', err);
208
- }
146
+ server.listen(3000, () => console.log('🚀 Server: http://localhost:3000'));
147
+ } catch (e) { console.error(e); }
209
148
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumpiajs",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Bahasa Pemrograman Semarangan",
5
5
  "type": "module",
6
6
  "main": "index.js",