lumpiajs 1.0.10 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,9 @@
1
1
 
2
- // KAMUS SEMARANGAN (Regex Replacement Rules)
3
- // Urutan penting! Keyword panjang dulu baru pendek.
2
+ // ... (KAMUS & Imports same as before) ...
4
3
  const KAMUS = [
5
4
  { from: /paten\s/g, to: 'const ' },
6
5
  { from: /ono\s/g, to: 'let ' },
7
- { from: /fungsi\s/g, to: 'function ' }, // Changed from 'gawe'
6
+ { from: /fungsi\s/g, to: 'function ' },
8
7
  { from: /nteni\s/g, to: 'await ' },
9
8
  { from: /mengko\s/g, to: 'async ' },
10
9
  { from: /balek\s/g, to: 'return ' },
@@ -13,56 +12,55 @@ const KAMUS = [
13
12
  { from: /jajal\s*\{/g, to: 'try {' },
14
13
  { from: /gagal\s*\(/g, to: 'catch(' },
15
14
  { 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
15
+ { from: /aku->/g, to: 'this.' },
16
+ { from: /aku\./g, to: 'this.' },
18
17
  { from: /->/g, to: '.' }
19
18
  ];
20
19
 
20
+ import fs from 'fs';
21
+ import path from 'path';
22
+ import { spawnSync } from 'child_process';
23
+ import { loadConfig } from '../core/Config.js';
24
+
21
25
  function transpileSemarangan(content) {
22
26
  let code = content;
23
-
24
- // Import .lmp -> .js
25
27
  code = code.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
26
- code = code.replace(/from\s+['"]lumpiajs['"]/g, "from '/core/lumpia.js'");
28
+ code = code.replace(/from\s+['"]lumpiajs['"]/g, "from './core/lumpia.js'");
27
29
 
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
-
32
30
  code = code.split('\n').map(line => {
33
31
  let l = line;
34
32
  if (l.trim().startsWith('//')) return l;
35
-
36
- KAMUS.forEach(rule => {
37
- l = l.replace(rule.from, rule.to);
38
- });
33
+ KAMUS.forEach(rule => { l = l.replace(rule.from, rule.to); });
39
34
  return l;
40
35
  }).join('\n');
41
-
42
36
  return code;
43
37
  }
44
38
 
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
-
53
- function processDirectory(source, dest) {
39
+ function processDirectory(source, dest, rootDepth = 0) {
54
40
  if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
55
41
  fs.readdirSync(source).forEach(item => {
56
42
  const srcPath = path.join(source, item);
57
43
  const destPath = path.join(dest, item);
58
44
  if (fs.statSync(srcPath).isDirectory()) {
59
- processDirectory(srcPath, destPath);
45
+ processDirectory(srcPath, destPath, rootDepth + 1);
60
46
  } else {
61
47
  if (item.endsWith('.lmp') || item.endsWith('.js')) {
62
- const content = fs.readFileSync(srcPath, 'utf8');
63
- const jsContent = transpileSemarangan(content);
48
+ let content = fs.readFileSync(srcPath, 'utf8');
49
+ let backSteps = rootDepth > 0 ? '../'.repeat(rootDepth) : './';
50
+ if(rootDepth === 0) backSteps = './'; // core is in dist/core/.
51
+
52
+ // Fix path calculation to Core
53
+ // dist/routes/web.js (depth 1) -> ../core/lumpia.js
54
+ // dist/app/controllers/Home.js (depth 2) -> ../../core/lumpia.js
55
+
56
+ // Let's rely on simple replace logic based on file type location usually known
57
+ // But dynamic is better.
58
+
59
+ content = content.replace(/from\s+['"]lumpiajs['"]/g, `from '${backSteps}../core/lumpia.js'`);
60
+
61
+ content = transpileSemarangan(content);
64
62
  const finalDest = destPath.replace('.lmp', '.js');
65
- fs.writeFileSync(finalDest, jsContent);
63
+ fs.writeFileSync(finalDest, content);
66
64
  } else {
67
65
  fs.copyFileSync(srcPath, destPath);
68
66
  }
@@ -75,8 +73,8 @@ export class Controller {
75
73
  constructor() { this.params={}; }
76
74
  async tampil(viewName, data={}) {
77
75
  try {
78
- const res = await fetch('/views/'+viewName+'.lmp');
79
- if(!res.ok) throw new Error('View 404');
76
+ const res = await fetch('views/'+viewName+'.lmp');
77
+ if(!res.ok) throw new Error('View 404: ' + viewName);
80
78
  let html = await res.text();
81
79
 
82
80
  const matchKulit = html.match(/<kulit>([\\s\\S]*?)<\\/kulit>/);
@@ -97,7 +95,6 @@ export class Controller {
97
95
  }
98
96
 
99
97
  if(script) {
100
- // Client-side transpile
101
98
  const kamus = [
102
99
  {f:/paten\\s/g,t:'const '}, {f:/ono\\s/g,t:'let '}, {f:/fungsi\\s/g,t:'function '},
103
100
  {f:/nteni\\s/g,t:'await '}, {f:/mengko\\s/g,t:'async '}, {f:/balek\\s/g,t:'return '},
@@ -105,7 +102,7 @@ export class Controller {
105
102
  {f:/aku->/g,t:'this.'}, {f:/aku\\./g,t:'this.'}
106
103
  ];
107
104
  kamus.forEach(r => script = script.replace(r.f, r.t));
108
- new Function(script)();
105
+ try { new Function(script)(); } catch(e){ console.error(e); }
109
106
  }
110
107
  } catch(e) { document.getElementById('app').innerHTML = e.message; }
111
108
  }
@@ -115,42 +112,89 @@ export const Jalan = { routes:[], get:(p,a)=>Jalan.routes.push({p,a}) };
115
112
 
116
113
  const indexHtml = `<!DOCTYPE html>
117
114
  <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>`;
115
+ <head>
116
+ <meta charset="UTF-8">
117
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
118
+ <title>LumpiaJS</title>
119
+ <link rel="stylesheet" href="public/css/style.css">
120
+ </head>
121
+ <body>
122
+ <div id="app">Loading...</div>
123
+
124
+ <script type="module">
125
+ import { Jalan } from './routes/web.js';
126
+
127
+ async function navigate() {
128
+ // FIX: Gunakan new RegExp string biar aman dari escaping hell
129
+ const cleanPath = (p) => p.replace(new RegExp('/index\\\\.html$'), '').replace(new RegExp('/$'), '');
130
+
131
+ const basePath = cleanPath(window.location.pathname);
132
+ // Tapi tunggu, basePath itu harusnya folder ROOT project.
133
+ // Misal: /2025/lumpiajs/
134
+ // URL saat ini: /2025/lumpiajs/index.html -> Base: /2025/lumpiajs
135
+ // Kalau kita navigate ke sub-route (via history pushState): /2025/lumpiajs/produk
136
+ // Maka window.location.pathname adalah /2025/lumpiajs/produk.
137
+ // Kita HARUS tau base path awalnya apa. Kita bisa tebak dari script tag src location?
138
+ // Atau kita asumsikan saat pertama load (yang biasanya index.html), itu lah base path.
139
+
140
+ // Trik: Simpan base path di window variable saat pertama load (index.html)
141
+ if (!window.LUMPIA_BASE) {
142
+ window.LUMPIA_BASE = window.location.pathname.replace(new RegExp('/index\\\\.html$'), '').replace(new RegExp('/$'), '');
143
+ }
144
+
145
+ let currentPath = window.location.pathname;
146
+ // Remove Base Path from Current Path to get "Route Path"
147
+ if (currentPath.startsWith(window.LUMPIA_BASE)) {
148
+ currentPath = currentPath.substring(window.LUMPIA_BASE.length);
149
+ }
150
+ if (!currentPath || currentPath === '/index.html') currentPath = '/';
151
+
152
+ let m = null, args = {};
153
+ for(let r of Jalan.routes) {
154
+ // Regex: Replace {slug} -> ([^/]+)
155
+ let regexStr = '^' + r.p.replace(/{([a-zA-Z0-9_]+)}/g, '([^/]+)') + '$';
156
+ let reg = new RegExp(regexStr);
157
+ let res = currentPath.match(reg);
158
+ if(res){ m=r; args=res.slice(1); break; }
159
+ }
160
+
161
+ if(m) {
162
+ const [cName, fName] = m.a.split('@');
163
+ try {
164
+ const mod = await import('./app/controllers/'+cName+'.js?'+Date.now());
165
+ const C = mod.default; const i = new C(); i.params=args;
166
+ await i[fName](...args);
167
+ } catch(e) {
168
+ console.error(e);
169
+ document.getElementById('app').innerHTML='<h1>Error Loading Controller</h1><pre>'+e.message+'</pre>';
170
+ }
171
+ } else {
172
+ document.getElementById('app').innerHTML='<h1>404 Not Found</h1><p>Route: '+currentPath+'</p>';
173
+ }
174
+ }
175
+
176
+ window.addEventListener('popstate', navigate);
177
+ document.body.addEventListener('click', e => {
178
+ if(e.target.tagName==='A' && e.target.href.startsWith(window.location.origin)) {
179
+ e.preventDefault();
180
+ history.pushState(null,'',e.target.href);
181
+ navigate();
182
+ }
183
+ });
184
+
185
+ // Init Base Path immediately
186
+ window.LUMPIA_BASE = window.location.pathname.replace(new RegExp('/index\\\\.html$'), '').replace(new RegExp('/$'), '');
187
+ navigate();
188
+ </script>
189
+ </body>
190
+ </html>`;
147
191
 
148
192
  export function buildProject() {
149
193
  const root = process.cwd();
150
194
  const dist = path.join(root, 'dist');
151
195
  const config = loadConfig(root);
152
196
 
153
- console.log('šŸ³ Goreng Project (Mode Bahasa Semarangan)...');
197
+ console.log('šŸ³ Goreng Project (Mode Aman)...');
154
198
  if (fs.existsSync(dist)) fs.rmSync(dist, { recursive: true, force: true });
155
199
  fs.mkdirSync(dist);
156
200
 
@@ -159,15 +203,17 @@ export function buildProject() {
159
203
  spawnSync(cmd, ['tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--minify'], { cwd: root, stdio: 'ignore', shell: true });
160
204
  }
161
205
 
162
- processDirectory(path.join(root, 'app'), path.join(dist, 'app'));
163
- processDirectory(path.join(root, 'routes'), path.join(dist, 'routes'));
206
+ processDirectory(path.join(root, 'app'), path.join(dist, 'app'), 2);
207
+ processDirectory(path.join(root, 'routes'), path.join(dist, 'routes'), 1);
208
+
164
209
  fs.cpSync(path.join(root, 'views'), path.join(dist, 'views'), { recursive: true });
165
210
  if (fs.existsSync(path.join(root, 'public'))) fs.cpSync(path.join(root, 'public'), path.join(dist, 'public'), { recursive: true });
166
211
 
167
212
  fs.mkdirSync(path.join(dist, 'core'), { recursive: true });
168
213
  fs.writeFileSync(path.join(dist, 'core', 'lumpia.js'), browserCore);
169
214
  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>`);
215
+
216
+ 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>`);
171
217
 
172
- console.log('āœ… Mateng! (Support Bahasa Semarangan)');
218
+ console.log('āœ… Mateng! Siap upload ke Laragon/Hosting.');
173
219
  }
@@ -119,5 +119,10 @@ export async function createProject(parameter) {
119
119
  fs.writeFileSync(path.join(root, 'aset', 'css', 'style.css'), '/* CSS */');
120
120
  }
121
121
 
122
- console.log(`āœ… Project "${projectName}" Ready!`);
122
+ console.log(`āœ… Project "${projectName}" Siap!`);
123
+ console.log('-------------------------------------------');
124
+ console.log(`šŸ‘‰ cd ${projectName}`);
125
+ console.log(`šŸ‘‰ npm install <-- OJO LALI IKI! (Wajib)`);
126
+ console.log(`šŸ‘‰ lumpia kukus`);
127
+ console.log('-------------------------------------------');
123
128
  }
@@ -1,5 +1,4 @@
1
-
2
- // KAMUS SEMARANGAN (Regex Replacement Rules)
1
+ // ... (Same KAMUS & Imports) ...
3
2
  const KAMUS = [
4
3
  { from: /paten\s/g, to: 'const ' },
5
4
  { from: /ono\s/g, to: 'let ' },
@@ -24,8 +23,6 @@ import { spawn } from 'child_process';
24
23
  import { loadEnv } from '../core/Env.js';
25
24
  import { loadConfig } from '../core/Config.js';
26
25
 
27
- // ... Same helper functions (matchRoute, backgroundProcess, startTailwindWatcher) ...
28
-
29
26
  function matchRoute(definedRoute, method, pathname) {
30
27
  if (definedRoute.method !== method) return null;
31
28
  if (definedRoute.path === pathname) return { params: {} };
@@ -63,7 +60,6 @@ async function loadLumpiaModule(filePath) {
63
60
 
64
61
  let transcoded = originalContent;
65
62
  transcoded = transcoded.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
66
-
67
63
  transcoded = transcoded.split('\n').map(line => {
68
64
  let l = line;
69
65
  if (l.trim().startsWith('//')) return l;
@@ -72,14 +68,33 @@ async function loadLumpiaModule(filePath) {
72
68
  }).join('\n');
73
69
 
74
70
  fs.writeFileSync(destPath, transcoded);
75
- const module = await import('file://' + destPath + '?t=' + Date.now());
76
- return module;
71
+
72
+ // Safety check for user forgetting npm install
73
+ try {
74
+ const module = await import('file://' + destPath + '?t=' + Date.now());
75
+ return module;
76
+ } catch (e) {
77
+ if (e.code === 'ERR_MODULE_NOT_FOUND' && e.message.includes('lumpiajs')) {
78
+ console.error('\nāŒ ERROR: Gagal load "lumpiajs".');
79
+ console.error('šŸ’” Sampeyan wis njalanke "npm install" durung?');
80
+ console.error(' Ketik: npm install\n');
81
+ process.exit(1);
82
+ }
83
+ throw e;
84
+ }
77
85
  }
78
86
 
79
87
  export async function serveProject() {
80
88
  const root = process.cwd();
81
89
  const routesFile = path.join(root, 'routes', 'web.lmp');
82
- if (!fs.existsSync(routesFile)) return console.log("āŒ Missing routes/web.lmp");
90
+ if (!fs.existsSync(routesFile)) return console.log("āŒ Not a LumpiaJS Project (Missing routes/web.lmp)");
91
+
92
+ // Pre-check node_modules
93
+ if (!fs.existsSync(path.join(root, 'node_modules'))) {
94
+ console.log("āš ļø Waduh, folder 'node_modules' ra ono!");
95
+ console.log(" Tolong jalanne 'npm install' sek ya, Lur.");
96
+ return;
97
+ }
83
98
 
84
99
  const env = loadEnv(root);
85
100
  const config = loadConfig(root);
@@ -134,7 +149,8 @@ export async function serveProject() {
134
149
  res.writeHead(200, {'Content-Type': result.type==='json'?'application/json':'text/html'});
135
150
  res.end(result.content);
136
151
  } else {
137
- res.end(String(result));
152
+ if(result === undefined) res.end('');
153
+ else res.end(String(result));
138
154
  }
139
155
  } catch (e) {
140
156
  res.writeHead(500); res.end(`<pre>${e.stack}</pre>`);
@@ -143,6 +159,9 @@ export async function serveProject() {
143
159
  res.writeHead(404); res.end('404 Not Found');
144
160
  }
145
161
  });
146
- server.listen(3000, () => console.log('šŸš€ Server: http://localhost:3000'));
147
- } catch (e) { console.error(e); }
162
+ const port = 3000;
163
+ server.listen(port, () => console.log(`šŸš€ Server: http://localhost:3000`));
164
+ } catch (e) {
165
+ if(e.code !== 'ERR_MODULE_NOT_FOUND') console.error(e);
166
+ }
148
167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumpiajs",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Bahasa Pemrograman Semarangan",
5
5
  "type": "module",
6
6
  "main": "index.js",