lumpiajs 1.0.8 → 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,72 +1,64 @@
1
1
  # 🥟 LumpiaJS
2
2
 
3
- **Bahasa Pemrograman Web dengan Kearifan Lokal Semarangan.**
3
+ **"Bahasa Pemrograman Web dengan Kearifan Lokal Semarangan."**
4
4
 
5
- ---
6
-
7
- ## 🦄 Fitur Unik: Laravel Syntax di JavaScript! (`->`)
8
-
9
- ```javascript
10
- // Valid di LumpiaJS (.lmp)
11
- const users = await DB.table('users')->where('active', 1)->get();
12
- Jalan->get('/', 'HomeController@index');
13
- ```
5
+ Framework Static SPA 100% Client-Side. Coding pakai bahasa sehari-hari.
14
6
 
15
7
  ---
16
8
 
17
- ## 🏗️ Cara Deploy ke Production (Server Asli)
18
-
19
- Ini yang sering ditanyain: **"Mas, file mana yang harus saya upload ke hosting?"**
9
+ ## 🗣️ Kamus Bahasa
20
10
 
21
- Tenang, LumpiaJS punya fitur **Goreng** (Build) biar kamu nggak bingung.
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 |
22
21
 
23
- ### 1. Goreng Project (Build)
22
+ _Plus fitur **Laravel Syntax**: `aku->tampil()`._
24
23
 
25
- Jalankan perintah ini di komputermu:
24
+ **Contoh Coding (`HomeController.lmp`):**
26
25
 
27
- ```bash
28
- lumpia goreng
26
+ ```javascript
27
+ export default class HomeController extends Controller {
28
+ mengko index() {
29
+ paten pesan = 'Halo Lur!';
30
+
31
+ // Panggil fungsi view
32
+ balek aku->tampil('home', { msg: pesan });
33
+ }
34
+ }
29
35
  ```
30
36
 
31
- _(Atau: `lumpia build`)_
32
-
33
- Sistem akan memasak projectmu:
34
-
35
- - Mentranspile sintaks `->` menjadi JS standard.
36
- - Mengkompilasi CSS (minify Tailwind/Bootstrap).
37
- - Menyiapkan folder `dist` yang siap saji.
38
-
39
- ### 2. Upload ke Server
40
-
41
- Setelah digoreng, akan muncul folder **`dist`**.
42
-
43
- 👉 **HANYA ISI FOLDER `dist`** inilah yang perlu kamu upload ke server.
44
- (Isinya: `server.js`, `package.json`, `.env`, folder `app`, `routes`, `views`, `public`)
37
+ ---
45
38
 
46
- ### 3. Install & Start di Server
39
+ ## 🚀 Cara Pakai
47
40
 
48
- Di panel hosting (Terminal/SSH) atau VPS:
41
+ **1. Install**
49
42
 
50
43
  ```bash
51
- # Masuk ke folder yang barusan diupload
52
- cd /path/to/your/app
44
+ npm install -g lumpiajs
45
+ ```
53
46
 
54
- # Install dependencies (LumpiaJS core, mysql driver, dll)
55
- npm install --production
47
+ **2. Buat Project & Develop**
56
48
 
57
- # Jalankan Aplikasi
58
- npm start
49
+ ```bash
50
+ lumpia create-project warung-ku
51
+ cd warung-ku && npm install
52
+ lumpia kukus
59
53
  ```
60
54
 
61
- ---
62
-
63
- ## 🗄️ Database
55
+ **3. Build Static (Goreng)**
64
56
 
65
- Database (MySQL) itu **SERVICE**, bukan file. Jadi:
57
+ ```bash
58
+ lumpia goreng
59
+ ```
66
60
 
67
- 1. Export database dari localhost (phpMyAdmin -> Export .sql).
68
- 2. Import file .sql itu ke database di server production kamu.
69
- 3. Edit file `.env` yang sudah diupload, sesuaikan `DB_HOST`, `DB_USER`, `DB_PASSWORD` dengan credential server.
61
+ Upload folder `dist` kemana saja (Hosting Biasa/GitHub Pages).
70
62
 
71
63
  ---
72
64
 
@@ -0,0 +1,59 @@
1
+ <?php
2
+ header("Access-Control-Allow-Origin: *");
3
+ header("Access-Control-Allow-Headers: Content-Type");
4
+ header("Content-Type: application/json");
5
+
6
+ // CONFIG
7
+ $host = "localhost";
8
+ $user = "root";
9
+ $pass = "";
10
+ $db = "lumpia_db";
11
+
12
+ // Koneksi
13
+ $conn = new mysqli($host, $user, $pass, $db);
14
+ if ($conn->connect_error) {
15
+ die(json_encode(["status" => "error", "message" => "Connection failed: " . $conn->connect_error]));
16
+ }
17
+
18
+ // Ambil Query dari Request
19
+ $input = json_decode(file_get_contents('php://input'), true);
20
+ $sql = $input['sql'] ?? '';
21
+ $params = $input['params'] ?? [];
22
+
23
+ if (empty($sql)) {
24
+ echo json_encode(["status" => "error", "message" => "No SQL provided"]);
25
+ exit;
26
+ }
27
+
28
+ // SECURITY: Basic SQL Injection prevention?
29
+ // NO, karena ini adalah 'bridge' untuk client-side DB.table().
30
+ // User framework bertanggung jawab atas query-nya via binding params.
31
+ // TAPI INI SANGAT BERBAHAYA JIKA DIEKPOS KE PUBLIK TANPA AUTH.
32
+ // Untuk 'Have Fun' framework, kita biarkan dulu, tapi kasih warning.
33
+
34
+ try {
35
+ $stmt = $conn->prepare($sql);
36
+ if($params) {
37
+ $types = str_repeat("s", count($params)); // Asumsikan string semua biar aman
38
+ $stmt->bind_param($types, ...$params);
39
+ }
40
+ $stmt->execute();
41
+ $result = $stmt->get_result();
42
+
43
+ $data = [];
44
+ if($result) {
45
+ while ($row = $result->fetch_assoc()) {
46
+ $data[] = $row;
47
+ }
48
+ } else {
49
+ // Non-select query
50
+ $data = ["affected_rows" => $stmt->affected_rows];
51
+ }
52
+
53
+ echo json_encode(["status" => "success", "data" => $data]);
54
+ } catch (Exception $e) {
55
+ echo json_encode(["status" => "error", "message" => $e->getMessage()]);
56
+ }
57
+
58
+ $conn->close();
59
+ ?>
@@ -1,49 +1,68 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { spawnSync } from 'child_process';
4
- import { loadConfig } from '../core/Config.js';
5
1
 
6
- // Transpiler Logic (Sama kayak serve.js tapi ini permanen ke disk)
7
- 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) {
8
22
  let code = content;
9
- // 1. Import: .lmp -> .js
23
+
24
+ // Import .lmp -> .js
10
25
  code = code.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
11
- // 2. Syntax: "->" -> "."
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
+
12
32
  code = code.split('\n').map(line => {
13
- if (line.trim().startsWith('//')) return line;
14
- 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;
15
40
  }).join('\n');
41
+
16
42
  return code;
17
43
  }
18
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
+
19
53
  function processDirectory(source, dest) {
20
54
  if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
21
-
22
- const items = fs.readdirSync(source);
23
- items.forEach(item => {
55
+ fs.readdirSync(source).forEach(item => {
24
56
  const srcPath = path.join(source, item);
25
57
  const destPath = path.join(dest, item);
26
- const stat = fs.statSync(srcPath);
27
-
28
- if (stat.isDirectory()) {
58
+ if (fs.statSync(srcPath).isDirectory()) {
29
59
  processDirectory(srcPath, destPath);
30
60
  } else {
31
- if (item.endsWith('.lmp')) {
32
- // Transpile .lmp to .js
61
+ if (item.endsWith('.lmp') || item.endsWith('.js')) {
33
62
  const content = fs.readFileSync(srcPath, 'utf8');
34
- const jsContent = transpileContent(content);
35
- const jsDest = destPath.replace('.lmp', '.js');
36
- fs.writeFileSync(jsDest, jsContent);
37
- } else if (item.endsWith('.js') || item.endsWith('.json') || item.endsWith('.css') || item.endsWith('.html')) {
38
- // Copy as is (but maybe transpile .js too for "->" support if mixed?)
39
- // For safety, let's also transpile .js files just in case user used "->" there
40
- if (item.endsWith('.js')) {
41
- const content = fs.readFileSync(srcPath, 'utf8');
42
- const jsContent = transpileContent(content);
43
- fs.writeFileSync(destPath, jsContent);
44
- } else {
45
- fs.copyFileSync(srcPath, destPath);
46
- }
63
+ const jsContent = transpileSemarangan(content);
64
+ const finalDest = destPath.replace('.lmp', '.js');
65
+ fs.writeFileSync(finalDest, jsContent);
47
66
  } else {
48
67
  fs.copyFileSync(srcPath, destPath);
49
68
  }
@@ -51,196 +70,104 @@ function processDirectory(source, dest) {
51
70
  });
52
71
  }
53
72
 
54
- const serverScript = `
55
- import http from 'http';
56
- import fs from 'fs';
57
- import path from 'path';
58
- import { routes, Jalan } from 'lumpiajs/lib/core/Router.js';
59
- import { loadEnv } from 'lumpiajs/lib/core/Env.js';
60
- import { loadConfig } from 'lumpiajs/lib/core/Config.js';
61
-
62
- const root = process.cwd();
63
- const env = loadEnv(root);
64
- const config = loadConfig(root);
65
-
66
- // ROUTE MATCHER (Copied from Core)
67
- function matchRoute(definedRoute, method, pathname) {
68
- if (definedRoute.method !== method) return null;
69
- if (definedRoute.path === pathname) return { params: {} };
70
- const paramNames = [];
71
- const regexPath = definedRoute.path.replace(/\\{([a-zA-Z0-9_]+)\\}/g, (match, name) => {
72
- paramNames.push(name);
73
- return '([^/]+)';
74
- });
75
- if (regexPath === definedRoute.path) return null;
76
- const regex = new RegExp('^' + regexPath + '$');
77
- const match = pathname.match(regex);
78
- if (match) {
79
- const params = {};
80
- paramNames.forEach((name, index) => params[name] = match[index + 1]);
81
- return { params };
82
- }
83
- return null;
84
- }
85
-
86
- async function start() {
87
- // 1. Load Routes (Compiled JS)
88
- const routesUrl = path.join(root, 'routes', 'web.js');
89
- if (fs.existsSync(routesUrl)) {
90
- await import('file://' + routesUrl);
91
- } else {
92
- console.error("❌ Error: routes/web.js not found in build!");
93
- }
94
-
95
- // 2. Start Server
96
- const server = http.createServer(async (req, res) => {
97
- const method = req.method;
98
- const url = new URL(req.url, 'http://' + req.headers.host);
99
- const pathname = url.pathname;
100
-
101
- // Static Files
102
- const publicMap = {
103
- '/css/': path.join(root, 'public', 'css'),
104
- '/vendor/': path.join(root, 'public', 'vendor')
105
- };
106
- for (const [prefix, localPath] of Object.entries(publicMap)) {
107
- if (pathname.startsWith(prefix)) {
108
- const relativePath = pathname.slice(prefix.length);
109
- const filePath = path.join(localPath, relativePath);
110
- if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
111
- const ext = path.extname(filePath);
112
- const mime = ext === '.css' ? 'text/css' : (ext === '.js' ? 'text/javascript' : 'application/octet-stream');
113
- res.writeHead(200, {'Content-Type': mime});
114
- res.end(fs.readFileSync(filePath));
115
- return;
116
- }
73
+ const browserCore = `
74
+ export class Controller {
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);
117
91
  }
118
- }
119
-
120
- // Routing
121
- let match = null;
122
- let params = {};
123
- for (const route of routes) {
124
- const result = matchRoute(route, method, pathname);
125
- if (result) {
126
- match = route;
127
- params = result.params;
128
- break;
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);
129
97
  }
130
- }
131
-
132
- if (match) {
133
- try {
134
- const [controllerName, methodName] = match.action.split('@');
135
- const controllerPath = path.join(root, 'app', 'controllers', controllerName + '.js');
136
- if (!fs.existsSync(controllerPath)) throw new Error('Controller ' + controllerName + ' not found');
137
-
138
- const module = await import('file://' + controllerPath);
139
- const ControllerClass = module.default;
140
- const instance = new ControllerClass();
141
- instance.env = env;
142
- instance.params = params;
143
- instance.config = config;
144
-
145
- const result = await instance[methodName](...Object.values(params));
146
- if (result.type === 'html') {
147
- res.writeHead(200, {'Content-Type': 'text/html'});
148
- res.end(result.content);
149
- } else if (result.type === 'json') {
150
- res.writeHead(200, {'Content-Type': 'application/json'});
151
- res.end(result.content);
152
- } else {
153
- res.writeHead(200, {'Content-Type': 'text/plain'});
154
- res.end(String(result));
155
- }
156
- } catch (e) {
157
- console.error(e);
158
- res.writeHead(500, {'Content-Type': 'text/html'});
159
- res.end('<h1>500 Server Error</h1>');
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)();
160
109
  }
161
- } else {
162
- res.writeHead(404);
163
- res.end('404 Not Found');
164
- }
165
- });
166
-
167
- const port = env.PORT || 3000;
168
- server.listen(port, () => {
169
- console.log('🚀 Production Server running on port ' + port);
170
- });
110
+ } catch(e) { document.getElementById('app').innerHTML = e.message; }
111
+ }
171
112
  }
172
-
173
- start();
113
+ export const Jalan = { routes:[], get:(p,a)=>Jalan.routes.push({p,a}) };
174
114
  `;
175
115
 
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; }
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'; }
138
+ }
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();
143
+ }
144
+ });
145
+ navigate();
146
+ </script></body></html>`;
147
+
176
148
  export function buildProject() {
177
149
  const root = process.cwd();
178
150
  const dist = path.join(root, 'dist');
179
151
  const config = loadConfig(root);
180
-
181
- console.log('🍳 Mulai Menggoreng (Building Project)...');
182
-
183
- // 1. Cleanup Old Dist
184
- if (fs.existsSync(dist)) {
185
- fs.rmSync(dist, { recursive: true, force: true });
186
- }
152
+
153
+ console.log('🍳 Goreng Project (Mode Bahasa Semarangan)...');
154
+ if (fs.existsSync(dist)) fs.rmSync(dist, { recursive: true, force: true });
187
155
  fs.mkdirSync(dist);
188
156
 
189
- // 2. Build Assets (Tailwind)
190
157
  if (config.klambi === 'tailwindcss') {
191
- console.log('🎨 Compiling Tailwind CSS (Minified)...');
192
158
  const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
193
- spawnSync(cmd, ['tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--minify'], {
194
- cwd: root, stdio: 'inherit', shell: true
195
- });
159
+ spawnSync(cmd, ['tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--minify'], { cwd: root, stdio: 'ignore', shell: true });
196
160
  }
197
161
 
198
- // 3. Copy & Transpile Code
199
- console.log('📂 Copying & Transpiling (.lmp -> .js)...');
200
162
  processDirectory(path.join(root, 'app'), path.join(dist, 'app'));
201
163
  processDirectory(path.join(root, 'routes'), path.join(dist, 'routes'));
202
- processDirectory(path.join(root, 'views'), path.join(dist, 'views')); // Views .lmp usually not imported, but kept as is? OR Transpiled?
203
- // Wait, View.js reads raw .lmp file content. So views should strictly be copied AS IS, or renamed to .lmp but content untouched generally?
204
- // Actually View.js `renderLumpia` expects file path.
205
- // Let's COPY views folder AS IS (recursive copy), no renaming extensions usually needed for View Engine unless we change View.js to look for .html?
206
- // Lumpia View Engine expects `<lump>` tags.
207
- // Let's just copy views folder using simple copy to ensure .lmp extension stays for view engine to find it.
208
-
209
- // RE-DO views copy: FORCE copy only
210
- // Overwrite the 'processDirectory' for views to be simple copy
211
164
  fs.cpSync(path.join(root, 'views'), path.join(dist, 'views'), { recursive: true });
165
+ if (fs.existsSync(path.join(root, 'public'))) fs.cpSync(path.join(root, 'public'), path.join(dist, 'public'), { recursive: true });
212
166
 
213
- // 4. Copy Static Assets
214
- if (fs.existsSync(path.join(root, 'public'))) {
215
- fs.cpSync(path.join(root, 'public'), path.join(dist, 'public'), { recursive: true });
216
- }
217
-
218
- // 5. Configs & Env
219
- fs.copyFileSync(path.join(root, 'package.json'), path.join(dist, 'package.json'));
220
- fs.copyFileSync(path.join(root, 'config.lmp'), path.join(dist, 'config.lmp'));
221
- if (fs.existsSync(path.join(root, '.env'))) {
222
- fs.copyFileSync(path.join(root, '.env'), path.join(dist, '.env'));
223
- }
224
-
225
- // 6. Generate Standalone Server Entry
226
- fs.writeFileSync(path.join(dist, 'server.js'), serverScript);
227
-
228
- // 7. Update package.json in dist
229
- const pkg = JSON.parse(fs.readFileSync(path.join(dist, 'package.json'), 'utf8'));
230
- pkg.scripts = {
231
- "start": "node server.js"
232
- };
233
- // Ensure lumpiajs dependency is preserved
234
- fs.writeFileSync(path.join(dist, 'package.json'), JSON.stringify(pkg, 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>`);
235
171
 
236
- console.log('✅ Mateng! (Build Finished)');
237
- console.log('----------------------------------------------------');
238
- console.log('🎁 Yang harus dikirim ke Server (Production):');
239
- console.log(' 📂 Folder: dist/');
240
- console.log('');
241
- console.log('👉 Cara Deploy:');
242
- console.log(' 1. Upload isi folder "dist" ke server.');
243
- console.log(' 2. Jalankan "npm install --production" di server.');
244
- console.log(' 3. Jalankan "npm start".');
245
- console.log('----------------------------------------------------');
172
+ console.log('✅ Mateng! (Support Bahasa Semarangan)');
246
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
  }
@@ -0,0 +1,54 @@
1
+ export class DB {
2
+ static async query(sql, params = []) {
3
+ // Cek mode: Node.js (Server) atau Browser (Client - Build HTML)
4
+ if (typeof window === 'undefined') {
5
+ // --- SERVER SIDE (NODE) ---
6
+ // Import native mysql driver dynamically to avoid bundle errors in browser
7
+ const { createPool } = await import('mysql2/promise');
8
+ // ... logic koneksi nodejs lama ...
9
+ return []; // placeholder
10
+ } else {
11
+ // --- CLIENT SIDE (BROWSER) ---
12
+ // Tembak ke file PHP Bridge
13
+ const response = await fetch('/api.php', {
14
+ method: 'POST',
15
+ headers: { 'Content-Type': 'application/json' },
16
+ body: JSON.stringify({ sql, params })
17
+ });
18
+ const json = await response.json();
19
+ if(json.status === 'error') throw new Error(json.message);
20
+ return json.data;
21
+ }
22
+ }
23
+
24
+ static table(name) {
25
+ return new QueryBuilder(name);
26
+ }
27
+ }
28
+
29
+ class QueryBuilder {
30
+ constructor(table) {
31
+ this.tableName = table;
32
+ this.conditions = [];
33
+ this.bindings = [];
34
+ this.selects = '*';
35
+ this.limitVal = null;
36
+ this.orderByRaw = null;
37
+ }
38
+
39
+ select(fields) { this.selects = fields; return this; }
40
+ where(col, op, val) { if (val === undefined) { val = op; op = '='; } this.conditions.push(`${col} ${op} ?`); this.bindings.push(val); return this; }
41
+ orderBy(col, dir='ASC') { this.orderByRaw = `${col} ${dir}`; return this; }
42
+ take(n) { this.limitVal = n; return this; }
43
+
44
+ async get() {
45
+ let sql = `SELECT ${this.selects} FROM ${this.tableName}`;
46
+ if (this.conditions.length > 0) sql += ' WHERE ' + this.conditions.join(' AND ');
47
+ if (this.orderByRaw) sql += ' ORDER BY ' + this.orderByRaw;
48
+ if (this.limitVal) sql += ' LIMIT ' + this.limitVal;
49
+
50
+ return await DB.query(sql, this.bindings);
51
+ }
52
+
53
+ // ... insert/update/delete logic similar ...
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumpiajs",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Bahasa Pemrograman Semarangan",
5
5
  "type": "module",
6
6
  "main": "index.js",