lumpiajs 1.0.9 → 1.0.11

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,75 @@
1
1
 
2
+ // ... (KAMUS dan Helper functions sama seperti sebelumnya) ...
3
+ // Saya akan fokus update browserCore dan indexHtml
4
+
5
+ const KAMUS = [
6
+ { from: /paten\s/g, to: 'const ' },
7
+ { from: /ono\s/g, to: 'let ' },
8
+ { from: /fungsi\s/g, to: 'function ' },
9
+ { from: /nteni\s/g, to: 'await ' },
10
+ { from: /mengko\s/g, to: 'async ' },
11
+ { from: /balek\s/g, to: 'return ' },
12
+ { from: /yen\s*\(/g, to: 'if(' },
13
+ { from: /liyane\s/g, to: 'else ' },
14
+ { from: /jajal\s*\{/g, to: 'try {' },
15
+ { from: /gagal\s*\(/g, to: 'catch(' },
16
+ { from: /kandani\(/g, to: 'console.log(' },
17
+ { from: /aku->/g, to: 'this.' },
18
+ { from: /aku\./g, to: 'this.' },
19
+ { from: /->/g, to: '.' }
20
+ ];
21
+
2
22
  import fs from 'fs';
3
23
  import path from 'path';
4
24
  import { spawnSync } from 'child_process';
5
25
  import { loadConfig } from '../core/Config.js';
6
26
 
7
- // --- HELPERS ---
8
- function transpileContent(content) {
27
+ function transpileSemarangan(content) {
9
28
  let code = content;
10
29
  code = code.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
30
+ code = code.replace(/from\s+['"]lumpiajs['"]/g, "from './core/lumpia.js'"); // FIX: Relative import to core
31
+
32
+ // Handle imports agar path-nya relatif browser freindly
33
+ // Jika import './...' -> aman.
34
+ // Jika import '/...' -> bahaya kalau di subfolder.
35
+
11
36
  code = code.split('\n').map(line => {
12
- if (line.trim().startsWith('//')) return line;
13
- return line.replace(/->/g, '.');
37
+ let l = line;
38
+ if (l.trim().startsWith('//')) return l;
39
+ KAMUS.forEach(rule => { l = l.replace(rule.from, rule.to); });
40
+ return l;
14
41
  }).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'");
19
42
  return code;
20
43
  }
21
44
 
22
- function processDirectory(source, dest) {
45
+ function processDirectory(source, dest, rootDepth = 0) {
23
46
  if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
24
-
25
- const items = fs.readdirSync(source);
26
- items.forEach(item => {
47
+ fs.readdirSync(source).forEach(item => {
27
48
  const srcPath = path.join(source, item);
28
49
  const destPath = path.join(dest, item);
29
- const stat = fs.statSync(srcPath);
30
-
31
- if (stat.isDirectory()) {
32
- processDirectory(srcPath, destPath);
50
+ if (fs.statSync(srcPath).isDirectory()) {
51
+ processDirectory(srcPath, destPath, rootDepth + 1);
33
52
  } else {
34
- if (item.endsWith('.lmp')) {
35
- 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);
53
+ if (item.endsWith('.lmp') || item.endsWith('.js')) {
54
+ let content = fs.readFileSync(srcPath, 'utf8');
55
+
56
+ // ADJUST IMPORT PATHS FOR BROWSER (CRITICAL!)
57
+ // Controller/Route ada di kedalaman tertentu. Core ada di /dist/core.
58
+ // Kita harus rewrite "from 'lumpiajs'" menjadi path relative yang benar ke core.
59
+ // Misal dari /app/controllers (depth 2) -> '../../core/lumpia.js'
60
+
61
+ // Hitung relative backstep
62
+ let backSteps = '../'.repeat(rootDepth);
63
+ // Karena structure dist:
64
+ // dist/routes/web.js (depth 1) -> butuh '../core/lumpia.js'
65
+ // dist/app/controllers/Home.js (depth 2) -> butuh '../../core/lumpia.js'
66
+ // Jadi logicnya benar.
67
+
68
+ content = content.replace(/from\s+['"]lumpiajs['"]/g, `from '${backSteps}../core/lumpia.js'`);
69
+
70
+ content = transpileSemarangan(content);
71
+ const finalDest = destPath.replace('.lmp', '.js');
72
+ fs.writeFileSync(finalDest, content);
43
73
  } else {
44
74
  fs.copyFileSync(srcPath, destPath);
45
75
  }
@@ -47,247 +77,119 @@ function processDirectory(source, dest) {
47
77
  });
48
78
  }
49
79
 
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 = `
80
+ const browserCore = `
171
81
  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); }
208
- }
209
-
210
- json(data) { document.getElementById('app').innerText = JSON.stringify(data, null, 2); }
211
- }
212
-
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();
225
- }
226
- }
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);
82
+ constructor() { this.params={}; }
83
+ async tampil(viewName, data={}) {
84
+ try {
85
+ // Fetch view relative to root (we assume <base> tag is set or we detect root)
86
+ // Biar aman, kita cari 'views' relatif terhadap posisi script ini? Gak bisa.
87
+ // Kita asumsi deployment di root atau subfolder dengan <base> tag HTML yang benar.
88
+
89
+ const res = await fetch('views/'+viewName+'.lmp');
90
+ if(!res.ok) throw new Error('View 404: ' + viewName);
91
+ let html = await res.text();
92
+
93
+ // ... (Parsing logic same as before) ...
94
+ const matchKulit = html.match(/<kulit>([\\s\\S]*?)<\\/kulit>/);
95
+ const matchIsi = html.match(/<isi>([\\s\\S]*?)<\\/isi>/);
96
+ const matchKlambi = html.match(/<klambi>([\\s\\S]*?)<\\/klambi>/);
97
+
98
+ let body = matchKulit?matchKulit[1]:'', script=matchIsi?matchIsi[1]:'', css=matchKlambi?matchKlambi[1]:'';
99
+
100
+ for(const [k,v] of Object.entries(data)) {
101
+ let s = typeof v === 'object' ? JSON.stringify(v) : v;
102
+ body = body.replace(new RegExp('{{\\\\s*'+k+'\\\\s*}}','g'), s);
103
+ }
104
+
105
+ document.getElementById('app').innerHTML = body;
106
+ if(css && !document.getElementById('css-'+viewName)) {
107
+ const s = document.createElement('style'); s.id='css-'+viewName; s.textContent=css;
108
+ document.head.appendChild(s);
109
+ }
110
+
111
+ if(script) {
112
+ const kamus = [
113
+ {f:/paten\\s/g,t:'const '}, {f:/ono\\s/g,t:'let '}, {f:/fungsi\\s/g,t:'function '},
114
+ {f:/nteni\\s/g,t:'await '}, {f:/mengko\\s/g,t:'async '}, {f:/balek\\s/g,t:'return '},
115
+ {f:/yen\\s*\\(/g,t:'if('}, {f:/liyane\\s/g,t:'else '}, {f:/kandani\\(/g,t:'console.log('},
116
+ {f:/aku->/g,t:'this.'}, {f:/aku\\./g,t:'this.'}
117
+ ];
118
+ kamus.forEach(r => script = script.replace(r.f, r.t));
119
+ try { new Function(script)(); } catch(e){ console.error(e); }
120
+ }
121
+ } catch(e) { document.getElementById('app').innerHTML = e.message; }
237
122
  }
238
123
  }
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
- };
124
+ export const Jalan = { routes:[], get:(p,a)=>Jalan.routes.push({p,a}) };
245
125
  `;
246
126
 
247
- const indexHtmlContent = `<!DOCTYPE html>
127
+ const indexHtml = `<!DOCTYPE html>
248
128
  <html lang="en">
249
129
  <head>
250
130
  <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">
131
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
132
+ <title>LumpiaJS</title>
133
+ <!-- BASE HREF AUTOMATIC DETECTION MAGIC -->
134
+ <script>
135
+ // Set base href to current folder so imports work in subfolders
136
+ // But for SPA routing, this might fight with History API.
137
+ // Let's rely on relative imports './' everywhere.
138
+ // document.write("<base href='" + document.location.pathname.replace(/index\\.html$/,'') + "' />");
139
+ </script>
140
+ <link rel="stylesheet" href="public/css/style.css">
254
141
  </head>
255
142
  <body>
256
143
  <div id="app">Loading...</div>
257
-
144
+
258
145
  <script type="module">
259
- import { Jalan } from '/routes/web.js';
146
+ // Import paths WITHOUT leading slash to be relative to index.html
147
+ import { Jalan } from './routes/web.js';
260
148
 
261
149
  async function navigate() {
262
- const path = window.location.pathname;
263
- let match = null, params = {};
150
+ // Normalize path: Remove base folder logic needed?
151
+ // Simple approach: Match pathname against routes.
152
+ // If app is in /my-app/, and route is '/', browser pathname is '/my-app/'.
153
+ // WE NEED TO STRIP THE BASE PATH.
154
+
155
+ // Hacky detection of Base Path (where index.html sits)
156
+ const basePath = window.location.pathname.replace(/\/index\.html$/, '').replace(/\/$/, '');
157
+ let currentPath = window.location.pathname.replace(/\/index\.html$/, '');
158
+ if (basePath && currentPath.startsWith(basePath)) {
159
+ currentPath = currentPath.substring(basePath.length);
160
+ }
161
+ if (!currentPath) currentPath = '/'; // Root
162
+
163
+ let m = null, args = {};
264
164
  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; }
165
+ // Route defined as '/home'
166
+ let reg = new RegExp('^'+r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)')+'$');
167
+ let res = currentPath.match(reg);
168
+ if(res){ m=r; args=res.slice(1); break; }
269
169
  }
270
170
 
271
- if(match) {
272
- const [cName, mName] = match.a.split('@');
171
+ if(m) {
172
+ const [cName, fName] = m.a.split('@');
273
173
  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>';
174
+ // Import Relative to index.html
175
+ const mod = await import('./app/controllers/'+cName+'.js?'+Date.now());
176
+ const C = mod.default; const i = new C(); i.params=args;
177
+ await i[fName](...args);
178
+ } catch(e) {
179
+ console.error(e);
180
+ document.getElementById('app').innerHTML='<h1>Error Loading Controller</h1><pre>'+e.message+'</pre>';
280
181
  }
281
- } else {
282
- document.getElementById('app').innerHTML = '<h1>404</h1>';
182
+ } else {
183
+ document.getElementById('app').innerHTML='<h1>404 Not Found</h1><p>Path: '+currentPath+'</p>';
283
184
  }
284
185
  }
285
186
 
286
187
  window.addEventListener('popstate', navigate);
287
188
  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);
189
+ if(e.target.tagName==='A' && e.target.href.startsWith(window.location.origin)) {
190
+ e.preventDefault();
191
+ // Push relative path?
192
+ history.pushState(null,'',e.target.href);
291
193
  navigate();
292
194
  }
293
195
  });
@@ -296,14 +198,12 @@ const indexHtmlContent = `<!DOCTYPE html>
296
198
  </body>
297
199
  </html>`;
298
200
 
299
-
300
201
  export function buildProject() {
301
202
  const root = process.cwd();
302
203
  const dist = path.join(root, 'dist');
303
204
  const config = loadConfig(root);
304
-
305
- console.log('🍳 Mulai Menggoreng (Universal Hybrid Build)...');
306
-
205
+
206
+ console.log('🍳 Goreng Project (Mode Bahasa Semarangan + Relative Paths)...');
307
207
  if (fs.existsSync(dist)) fs.rmSync(dist, { recursive: true, force: true });
308
208
  fs.mkdirSync(dist);
309
209
 
@@ -312,69 +212,24 @@ export function buildProject() {
312
212
  spawnSync(cmd, ['tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--minify'], { cwd: root, stdio: 'ignore', shell: true });
313
213
  }
314
214
 
315
- console.log('📂 Converting Code...');
316
- processDirectory(path.join(root, 'app'), path.join(dist, 'app'));
317
- processDirectory(path.join(root, 'routes'), path.join(dist, 'routes'));
215
+ // Process using Relative Path Logic
216
+ // We pass 'rootDepth' to calculate imports to 'core'
217
+ processDirectory(path.join(root, 'app'), path.join(dist, 'app'), 2); // app/controllers/X -> depth 2 from dist
218
+ processDirectory(path.join(root, 'routes'), path.join(dist, 'routes'), 1); // routes/X -> depth 1 from dist
318
219
 
319
- fs.mkdirSync(path.join(dist, 'core'), { recursive: true });
320
- fs.writeFileSync(path.join(dist, 'core', 'index.js'), browserCoreIndex);
321
-
322
220
  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
- }
327
-
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));
221
+ if (fs.existsSync(path.join(root, 'public'))) fs.cpSync(path.join(root, 'public'), path.join(dist, 'public'), { recursive: true });
343
222
 
344
- // 3. Routing Rules
223
+ fs.mkdirSync(path.join(dist, 'core'), { recursive: true });
224
+ fs.writeFileSync(path.join(dist, 'core', 'lumpia.js'), browserCore);
225
+ fs.writeFileSync(path.join(dist, 'index.html'), indexHtml);
345
226
 
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));
372
-
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('----------------------------------------------------');
227
+ // .htaccess for "Subfolder Friendly" SPA?
228
+ // RewriteRule ^index\.html$ - [L]
229
+ // RewriteCond %{REQUEST_FILENAME} !-f
230
+ // RewriteRule . index.html [L]
231
+ // Note: Removed leading slash in redirect destination to be relative
232
+ fs.writeFileSync(path.join(dist, '.htaccess'), `<IfModule mod_rewrite.c>\nRewriteEngine On\nRewriteRule ^index\\.html$ - [L]\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule . index.html [L]\n</IfModule>`);
233
+
234
+ console.log('✅ Mateng! (Support Subfolder Hosting)');
380
235
  }
@@ -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
  }
@@ -2,58 +2,52 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import http from 'http';
4
4
  import { spawn } from 'child_process';
5
- import { routes } from '../core/Router.js';
6
- import { renderLumpia } from '../core/View.js';
7
5
  import { loadEnv } from '../core/Env.js';
8
6
  import { loadConfig } from '../core/Config.js';
9
7
 
10
- const cliPackageJson = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url)));
8
+ const KAMUS = [
9
+ { from: /paten\s/g, to: 'const ' },
10
+ { from: /ono\s/g, to: 'let ' },
11
+ { from: /fungsi\s/g, to: 'function ' },
12
+ { from: /nteni\s/g, to: 'await ' },
13
+ { from: /mengko\s/g, to: 'async ' },
14
+ { from: /balek\s/g, to: 'return ' },
15
+ { from: /yen\s*\(/g, to: 'if(' },
16
+ { from: /liyane\s/g, to: 'else ' },
17
+ { from: /jajal\s*\{/g, to: 'try {' },
18
+ { from: /gagal\s*\(/g, to: 'catch(' },
19
+ { from: /kandani\(/g, to: 'console.log(' },
20
+ { from: /aku->/g, to: 'this.' },
21
+ { from: /aku\./g, to: 'this.' },
22
+ { from: /->/g, to: '.' }
23
+ ];
11
24
 
12
- // Helper to Match Routes
13
25
  function matchRoute(definedRoute, method, pathname) {
14
26
  if (definedRoute.method !== method) return null;
15
27
  if (definedRoute.path === pathname) return { params: {} };
16
-
17
28
  const paramNames = [];
18
29
  const regexPath = definedRoute.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, name) => {
19
30
  paramNames.push(name);
20
31
  return '([^/]+)';
21
32
  });
22
-
23
33
  if (regexPath === definedRoute.path) return null;
24
34
  const regex = new RegExp(`^${regexPath}$`);
25
35
  const match = pathname.match(regex);
26
36
  if (match) {
27
37
  const params = {};
28
- paramNames.forEach((name, index) => {
29
- params[name] = match[index + 1];
30
- });
38
+ paramNames.forEach((name, index) => { params[name] = match[index + 1]; });
31
39
  return { params };
32
40
  }
33
41
  return null;
34
42
  }
35
43
 
36
44
  const backgroundProcess = [];
37
-
38
45
  function startTailwindWatcher(root) {
39
- console.log('🎨 TailwindCSS detected! Starting watcher...');
40
46
  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 });
47
+ const tailwind = spawn(cmd, ['tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--watch'], { cwd: root, stdio: 'ignore', shell: true });
44
48
  backgroundProcess.push(tailwind);
45
49
  }
46
50
 
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
51
  async function loadLumpiaModule(filePath) {
58
52
  const originalContent = fs.readFileSync(filePath, 'utf-8');
59
53
  const cacheDir = path.join(process.cwd(), '.lumpia', 'cache');
@@ -63,72 +57,64 @@ async function loadLumpiaModule(filePath) {
63
57
  const flatName = relativePath.replace(/[\/\\]/g, '_').replace('.lmp', '.js');
64
58
  const destPath = path.join(cacheDir, flatName);
65
59
 
66
- // --- TRANSPILE LOGIC ---
67
- let transcoded = originalContent;
60
+ // INI DEV SERVER: Path ke Core Library bisa tetap absolute (package) atau relative
61
+ // Karena ini dijalankan di Node.js, 'lumpiajs' masih resolve ke node_modules dengan benar.
62
+ // TAPI content user (seperti `from 'lumpiajs'`) harus dijaga tetap valid buat Node.
63
+ // Jika user nulis `from 'lumpiajs'`, biarkan.
64
+ // Hanya transpile Semarangan -> JS.
68
65
 
69
- // 1. Replace Imports: .lmp -> .js
66
+ let transcoded = originalContent;
67
+
68
+ // DEV MODE: Keep .lmp imports as .js imports
70
69
  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)
70
+ // DEV MODE: Do NOT rewrite 'lumpiajs' import because Node handles it via package.json resolution
78
71
 
79
- // Replace "->" dengan "."
80
72
  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, '.');
73
+ let l = line;
74
+ if (l.trim().startsWith('//')) return l;
75
+ KAMUS.forEach(rule => l = l.replace(rule.from, rule.to));
76
+ return l;
88
77
  }).join('\n');
89
78
 
90
79
  fs.writeFileSync(destPath, transcoded);
91
-
92
- // 4. Import file .js
93
80
  const module = await import('file://' + destPath + '?t=' + Date.now());
94
81
  return module;
95
82
  }
96
83
 
97
-
98
84
  export async function serveProject() {
99
85
  const root = process.cwd();
100
-
101
86
  const routesFile = path.join(root, 'routes', 'web.lmp');
102
- if (!fs.existsSync(routesFile)) return console.log("❌ Not a LumpiaJS Project. (Missing routes/web.lmp)");
87
+ if (!fs.existsSync(routesFile)) return console.log("❌ Missing routes/web.lmp");
103
88
 
104
89
  const env = loadEnv(root);
105
90
  const config = loadConfig(root);
106
-
107
91
  if (config.klambi === 'tailwindcss') startTailwindWatcher(root);
108
- else if (config.klambi === 'bootstrap') handleBootstrap(root);
109
92
 
110
93
  try {
111
94
  await loadLumpiaModule(routesFile);
112
- console.log(`🛣️ Routes registered: ${routes.length}`);
95
+ const activeRoutes = global.LumpiaRouter || [];
96
+ console.log(`🛣️ Routes registered: ${activeRoutes.length}`);
97
+
98
+ // Cek URL, jika '/' tapi masih loading, itu karena logic view belum sempurna?
99
+ // Di Dev Server (Node), kita merender HTML string langsung (SSR).
100
+ // Jadi tidak ada masalah "loading..." dari sisi client-side fetch.
101
+ // Jika dev server loading terus, itu karena res.end() tidak terpanggil.
102
+
103
+ // Periksa controller user: apakah memanggil `balek` (return)?
104
+ // Jika user lupa return, function return undefined.
105
+ // Logic di bawah menangani undefined result -> `res.end(String(result))` -> `undefined`.
106
+ // Browser akan menampilkan "undefined". Bukan loading terus.
107
+ // Loading terus biasanya hang.
113
108
 
114
- // Info Syntax
115
- console.log(`✨ Syntax Mode: PHP/Laravel Style (->) is enabled in .lmp files!`);
116
-
117
109
  const server = http.createServer(async (req, res) => {
118
110
  const method = req.method;
119
111
  const url = new URL(req.url, `http://${req.headers.host}`);
120
112
  const pathname = url.pathname;
121
113
 
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
- };
114
+ const publicMap = { '/css/': path.join(root, 'public', 'css'), '/vendor/': path.join(root, 'public', 'vendor') };
128
115
  for (const [prefix, localPath] of Object.entries(publicMap)) {
129
116
  if (pathname.startsWith(prefix)) {
130
- const relativePath = pathname.slice(prefix.length);
131
- const filePath = path.join(localPath, relativePath);
117
+ const filePath = path.join(localPath, pathname.slice(prefix.length));
132
118
  if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
133
119
  const ext = path.extname(filePath);
134
120
  const mime = ext === '.css' ? 'text/css' : (ext === '.js' ? 'text/javascript' : 'application/octet-stream');
@@ -139,71 +125,42 @@ export async function serveProject() {
139
125
  }
140
126
  }
141
127
 
142
- let match = null;
143
- let params = {};
144
- for (const route of routes) {
128
+ let match = null, params = {};
129
+ const currentRoutes = global.LumpiaRouter || [];
130
+ for (const route of currentRoutes) {
145
131
  const result = matchRoute(route, method, pathname);
146
- if (result) {
147
- match = route;
148
- params = result.params;
149
- break;
150
- }
132
+ if (result) { match = route; params = result.params; break; }
151
133
  }
152
134
 
153
135
  if (match) {
154
136
  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!`);
137
+ const [cName, mName] = match.action.split('@');
138
+ const cPath = path.join(root, 'app', 'controllers', cName + '.lmp');
139
+ if (!fs.existsSync(cPath)) throw new Error('Controller Not Found');
159
140
 
160
- const module = await loadLumpiaModule(controllerPath);
161
- const ControllerClass = module.default;
162
- const instance = new ControllerClass();
141
+ const module = await loadLumpiaModule(cPath);
142
+ const Ctrl = module.default;
143
+ const instance = new Ctrl();
144
+ instance.env = env; instance.params = params; instance.config = config;
163
145
 
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'});
146
+ const result = await instance[mName](...Object.values(params));
147
+
148
+ if (result && result.type) {
149
+ res.writeHead(200, {'Content-Type': result.type==='json'?'application/json':'text/html'});
178
150
  res.end(result.content);
179
151
  } else {
180
- res.writeHead(200, {'Content-Type': 'text/plain'});
181
- res.end(String(result));
152
+ // FIX: Jika result null/undefined (lupa return balek), kirim response kosong atau error
153
+ if(result === undefined) res.end('');
154
+ else res.end(String(result));
182
155
  }
183
156
  } 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);
157
+ res.writeHead(500); res.end(`<pre>${e.stack}</pre>`);
188
158
  }
189
159
  } else {
190
- res.writeHead(404, {'Content-Type': 'text/html'});
191
- res.end('<h1>404 Not Found</h1>');
160
+ res.writeHead(404); res.end('404 Not Found');
192
161
  }
193
162
  });
194
-
195
163
  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
- }
164
+ server.listen(port, () => console.log(`🚀 Server running at http://localhost:${port}`));
165
+ } catch (e) { console.error(e); }
209
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumpiajs",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Bahasa Pemrograman Semarangan",
5
5
  "type": "module",
6
6
  "main": "index.js",