lumpiajs 1.0.6 → 1.0.8

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,149 +1,203 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import prompts from 'prompts';
3
4
 
4
- // --- TEMPLATES ---
5
5
  const routesTemplate = `import { Jalan } from 'lumpiajs';
6
6
 
7
- // Define Routes
8
- Jalan.gawe('/', 'HomeController@index');
9
- Jalan.gawe('/profile', 'HomeController@profile');
10
- Jalan.gawe('/api/products', 'ProductController@index');
7
+ // Pakai gaya Laravel (->) enak to?
8
+ Jalan->get('/', 'HomeController@index');
9
+ Jalan->get('/db-test', 'HomeController@testDb');
10
+ Jalan->get('/profile', 'HomeController@profile');
11
+ Jalan->get('/api/products', 'ProductController@index');
11
12
  `;
12
13
 
13
- const controllerTemplate = `import { Controller } from 'lumpiajs';
14
+ const controllerTemplate = `import { Controller, DB } from 'lumpiajs';
14
15
 
15
16
  export default class HomeController extends Controller {
16
17
  index() {
17
- // Render view in 'views' folder
18
- return this.tampil('home', {
18
+ // "this.tampil" juga bisa ditulis "this->tampil"
19
+ // Transpiler LumpiaJS sing ngatur, Bos!
20
+ return this->tampil('home', {
19
21
  message: 'Welcome to LumpiaJS MVC!',
20
- author: 'Pakdhe Koding'
22
+ author: 'Pakdhe Koding',
23
+ env: this.env.APP_ENV
21
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
+
24
48
  profile() {
25
- return this.tampil('profile', { name: 'Loyal User' });
49
+ return this->tampil('profile', { name: 'Loyal User' });
26
50
  }
27
51
  }
28
52
  `;
29
53
 
30
- const modelTemplate = `// Example dummy data
54
+ const modelTemplate = `// Example dummy data (Static)
31
55
  const productData = [
32
56
  { id: 1, name: 'Lumpia Basah', price: 5000 },
33
- { id: 2, name: 'Lumpia Goreng', price: 6000 },
34
- { id: 3, name: 'Tahu Gimbal', price: 12000 }
57
+ { id: 2, name: 'Lumpia Goreng', price: 6000 }
35
58
  ];
36
-
37
59
  export default productData;
38
60
  `;
39
61
 
40
62
  const productControllerTemplate = `import { Controller, Model } from 'lumpiajs';
41
- import ProductData from '../../app/models/Product.js';
63
+ import ProductData from '../../app/models/Product.lmp';
42
64
 
43
65
  export default class ProductController extends Controller {
44
66
  index() {
45
- // Eloquent-style: Model.use(data).dimana(...).jupuk()
46
- const result = Model.use(ProductData)
47
- .dimana('price', '>', 5500)
48
- .jupuk();
49
-
50
- return this.json({
51
- status: 'success',
52
- data: result
53
- });
67
+ // Model Static juga bisa pakai ->
68
+ const result = Model->use(ProductData)
69
+ ->dimana('price', '>', 5500)
70
+ ->jupuk();
71
+
72
+ return this->json({ status: 'success', data: result });
54
73
  }
55
74
  }
56
75
  `;
57
76
 
58
- const viewHomeTemplate = `<lump>
59
- <klambi>
60
- h1 { color: #d35400; text-align: center; }
61
- .box { border: 1px solid #ddd; padding: 20px; text-align: center; margin-top: 20px; }
62
- </klambi>
63
-
77
+ const viewProfileTemplate = `<lump>
64
78
  <kulit>
65
- <h1>{{ message }}</h1>
66
- <div class="box">
67
- <p>Created with love by: <strong>{{ author }}</strong></p>
68
- <button onclick="checkPrice()">Check Price API</button>
69
- <br><br>
70
- <a href="/profile">Go to Profile</a>
79
+ <div class="container mt-5">
80
+ <h1>User Profile</h1>
81
+ <p>Hello, <strong>{{ name }}</strong>!</p>
82
+ <a href="/" class="btn btn-secondary">Back Home</a>
71
83
  </div>
72
84
  </kulit>
73
-
74
- <isi>
75
- gawe checkPrice() {
76
- fetch('/api/products')
77
- .then(res => res.json())
78
- .then(data => {
79
- alert('Data from API: ' + JSON.stringify(data));
80
- });
81
- }
82
- </isi>
83
85
  </lump>`;
84
86
 
85
- const viewProfileTemplate = `<lump>
86
- <kulit>
87
- <h1>User Profile</h1>
88
- <p>Hello, <strong>{{ name }}</strong>!</p>
89
- <a href="/">Back Home</a>
90
- </kulit>
91
- </lump>`;
87
+ const envTemplate = `
88
+ BASE_URL="http://localhost:3000"
89
+ APP_ENV="local"
90
+ APP_DEBUG="true"
92
91
 
93
- const packageJsonTemplate = (name) => `{
94
- "name": "${name}",
95
- "version": "1.0.0",
96
- "main": "routes/web.js",
97
- "type": "module",
98
- "scripts": {
99
- "start": "lumpia dodolan",
100
- "serve": "lumpia dodolan"
101
- },
102
- "dependencies": {
103
- "lumpiajs": "latest"
104
- }
105
- }
92
+ # Database Config
93
+ DB_HOST="localhost"
94
+ DB_USER="root"
95
+ DB_PASSWORD=""
96
+ DB_NAME="lumpia_db"
106
97
  `;
107
98
 
108
- export function createProject(parameter) {
109
- const projectName = parameter;
99
+ // Helper for CSS/View generation (Collapsed for brevity but functional as before)
100
+ const tailwindConfigTemplate = `/** @type {import('tailwindcss').Config} */
101
+ module.exports = { content: ["./views/**/*.{html,js,lmp}"], theme: { extend: {}, }, plugins: [], }`;
102
+ const mainCssTemplate = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n`;
103
+
104
+ const generateHomeView = (style) => {
105
+ let css = '', html = '';
106
+ if (style === 'bootstrap') {
107
+ html = `
108
+ <div class="container mt-5">
109
+ <div class="card shadow">
110
+ <div class="card-body text-center">
111
+ <h1 class="text-primary">{{ message }}</h1>
112
+ <span class="badge bg-secondary mb-3">Env: {{ env }}</span>
113
+ <p>Created by: <strong>{{ author }}</strong></p>
114
+ <div class="mt-4">
115
+ <a href="/db-test" class="btn btn-warning">Test DB (Laravel Style)</a>
116
+ <a href="/profile" class="btn btn-link">Go to Profile</a>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </div>`;
121
+ } else if (style === 'tailwindcss') {
122
+ html = `
123
+ <div class="container mx-auto mt-10 p-5">
124
+ <div class="bg-white shadow-lg rounded-lg p-8 text-center border border-gray-200">
125
+ <h1 class="text-4xl font-bold text-orange-600 mb-4">{{ message }}</h1>
126
+ <span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mb-4">Env: {{ env }}</span>
127
+ <div class="mt-6 space-x-4">
128
+ <a href="/db-test" class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded">Test DB</a>
129
+ <a href="/profile" class="text-blue-500 hover:text-blue-800">Go to Profile</a>
130
+ </div>
131
+ </div>
132
+ </div>`;
133
+ } else {
134
+ css = `h1{text-align:center} .box{text-align:center;margin-top:20px}`;
135
+ html = `<div class="box"><h1>{{ message }}</h1><p>Env: {{ env }}</p><a href="/db-test">Test DB</a> | <a href="/profile">Profile</a><br>(Check Controller for Laravel Syntax Demo)</div>`;
136
+ }
137
+ return `<lump><klambi>${css}</klambi><kulit>${html}</kulit><isi></isi></lump>`;
138
+ };
139
+
140
+ const generatePackageJson = (name, style) => {
141
+ const scripts = { "start": "lumpia kukus", "serve": "lumpia kukus" };
142
+ const dependencies = { "lumpiajs": "latest" };
143
+ const devDependencies = {};
144
+ if (style === 'tailwindcss') {
145
+ devDependencies["tailwindcss"] = "^3.4.0";
146
+ devDependencies["postcss"] = "^8.4.0";
147
+ devDependencies["autoprefixer"] = "^10.4.0";
148
+ } else if (style === 'bootstrap') dependencies["bootstrap"] = "^5.3.0";
149
+ return JSON.stringify({ name, version: "1.0.0", main: "routes/web.lmp", type: "module", scripts, dependencies, devDependencies }, null, 2);
150
+ };
151
+
152
+ export async function createProject(parameter) {
153
+ let projectName = parameter;
110
154
  if (!projectName) {
111
- console.log(' Please specify project name.');
112
- console.log('Example: lumpia create-project my-app');
113
- return;
155
+ const res = await prompts({ type: 'text', name: 'val', message: 'Jeneng project?', initial: 'my-app' });
156
+ projectName = res.val;
114
157
  }
115
-
158
+ if (!projectName) return;
159
+
116
160
  const root = path.join(process.cwd(), projectName);
117
- if (fs.existsSync(root)) return console.log(`❌ Folder ${projectName} already exists.`);
161
+ if (fs.existsSync(root)) { console.log('❌ Folder Exists'); return; }
118
162
 
119
- console.log(`🔨 Building MVC foundation in ${projectName}...`);
120
-
163
+ const styleRes = await prompts({
164
+ type: 'select', name: 'val', message: 'Styling?',
165
+ choices: [{title:'Vanilla',value:'none'},{title:'Tailwind',value:'tailwindcss'},{title:'Bootstrap',value:'bootstrap'}],
166
+ initial: 0
167
+ });
168
+ const style = styleRes.val;
169
+ if (!style) return;
170
+
171
+ // Structure
121
172
  fs.mkdirSync(root);
122
-
123
- // STANDARD MVC STRUCTURE:
124
- // app/controllers
125
- // app/models
126
- // views
127
- // routes
128
-
129
173
  fs.mkdirSync(path.join(root, 'app', 'controllers'), { recursive: true });
130
174
  fs.mkdirSync(path.join(root, 'app', 'models'), { recursive: true });
131
175
  fs.mkdirSync(path.join(root, 'routes'));
132
- fs.mkdirSync(path.join(root, 'views'));
133
-
134
- // Write files
135
- fs.writeFileSync(path.join(root, 'package.json'), packageJsonTemplate(projectName));
136
- fs.writeFileSync(path.join(root, 'routes', 'web.js'), routesTemplate);
137
- fs.writeFileSync(path.join(root, 'app', 'controllers', 'HomeController.js'), controllerTemplate);
138
- fs.writeFileSync(path.join(root, 'app', 'controllers', 'ProductController.js'), productControllerTemplate);
139
- fs.writeFileSync(path.join(root, 'app', 'models', 'Product.js'), modelTemplate);
140
- fs.writeFileSync(path.join(root, 'views', 'home.lmp'), viewHomeTemplate);
176
+ fs.mkdirSync(path.join(root, 'views'));
177
+ fs.mkdirSync(path.join(root, 'aset', 'css'), { recursive: true });
178
+ fs.mkdirSync(path.join(root, 'public', 'css'), { recursive: true });
179
+
180
+ // Files
181
+ fs.writeFileSync(path.join(root, 'package.json'), generatePackageJson(projectName, style));
182
+ fs.writeFileSync(path.join(root, '.gitignore'), `.lumpia\nnode_modules\n.env\n`);
183
+ fs.writeFileSync(path.join(root, '.env'), envTemplate);
184
+ fs.writeFileSync(path.join(root, 'config.lmp'), JSON.stringify({ klambi: style }, null, 2));
185
+
186
+ fs.writeFileSync(path.join(root, 'routes', 'web.lmp'), routesTemplate);
187
+ fs.writeFileSync(path.join(root, 'app', 'controllers', 'HomeController.lmp'), controllerTemplate);
188
+ fs.writeFileSync(path.join(root, 'app', 'controllers', 'ProductController.lmp'), productControllerTemplate);
189
+ fs.writeFileSync(path.join(root, 'app', 'models', 'Product.lmp'), modelTemplate);
190
+ fs.writeFileSync(path.join(root, 'views', 'home.lmp'), generateHomeView(style));
141
191
  fs.writeFileSync(path.join(root, 'views', 'profile.lmp'), viewProfileTemplate);
192
+
193
+ // Style Assets
194
+ if (style === 'tailwindcss') {
195
+ fs.writeFileSync(path.join(root, 'tailwind.config.js'), tailwindConfigTemplate);
196
+ fs.writeFileSync(path.join(root, 'aset', 'css', 'style.css'), mainCssTemplate);
197
+ } else {
198
+ fs.writeFileSync(path.join(root, 'aset', 'css', 'style.css'), style === 'bootstrap' ? '/* Bootstrap Imported in HTML */' : '/* CSS */');
199
+ }
142
200
 
143
- console.log('✅ Project ready! Standard MVC Structure.');
144
- console.log('----------------------------------------------------');
145
- console.log(`cd ${projectName}`);
146
- console.log('npm install');
147
- console.log('npm start');
148
- console.log('----------------------------------------------------');
201
+ console.log(`✅ Project "${projectName}" Ready!`);
202
+ console.log(`cd ${projectName} && npm install && lumpia kukus`);
149
203
  }
@@ -1,53 +1,174 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import http from 'http';
4
+ import { spawn } from 'child_process';
4
5
  import { routes } from '../core/Router.js';
5
6
  import { renderLumpia } from '../core/View.js';
7
+ import { loadEnv } from '../core/Env.js';
8
+ import { loadConfig } from '../core/Config.js';
9
+
10
+ const cliPackageJson = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url)));
11
+
12
+ // Helper to Match Routes
13
+ function matchRoute(definedRoute, method, pathname) {
14
+ if (definedRoute.method !== method) return null;
15
+ if (definedRoute.path === pathname) return { params: {} };
16
+
17
+ const paramNames = [];
18
+ const regexPath = definedRoute.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, name) => {
19
+ paramNames.push(name);
20
+ return '([^/]+)';
21
+ });
22
+
23
+ if (regexPath === definedRoute.path) return null;
24
+ const regex = new RegExp(`^${regexPath}$`);
25
+ const match = pathname.match(regex);
26
+ if (match) {
27
+ const params = {};
28
+ paramNames.forEach((name, index) => {
29
+ params[name] = match[index + 1];
30
+ });
31
+ return { params };
32
+ }
33
+ return null;
34
+ }
35
+
36
+ const backgroundProcess = [];
37
+
38
+ function startTailwindWatcher(root) {
39
+ console.log('🎨 TailwindCSS detected! Starting watcher...');
40
+ const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
41
+ const tailwind = spawn(cmd, [
42
+ 'tailwindcss', '-i', './aset/css/style.css', '-o', './public/css/style.css', '--watch'
43
+ ], { cwd: root, stdio: 'inherit', shell: true });
44
+ backgroundProcess.push(tailwind);
45
+ }
46
+
47
+ function handleBootstrap(root) {
48
+ const bootDestDist = path.join(root, 'public', 'vendor', 'bootstrap');
49
+ const bootSrc = path.join(root, 'node_modules', 'bootstrap', 'dist');
50
+ if (fs.existsSync(bootSrc) && !fs.existsSync(bootDestDist)) {
51
+ console.log('📦 Menyalin library Bootstrap ke public...');
52
+ try { fs.cpSync(bootSrc, bootDestDist, { recursive: true }); } catch(e) {}
53
+ }
54
+ }
55
+
56
+ // LOADER UTAMA UNTUK .lmp (Controller/Model/Routes)
57
+ async function loadLumpiaModule(filePath) {
58
+ const originalContent = fs.readFileSync(filePath, 'utf-8');
59
+ const cacheDir = path.join(process.cwd(), '.lumpia', 'cache');
60
+ if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
61
+
62
+ const relativePath = path.relative(process.cwd(), filePath);
63
+ const flatName = relativePath.replace(/[\/\\]/g, '_').replace('.lmp', '.js');
64
+ const destPath = path.join(cacheDir, flatName);
65
+
66
+ // --- TRANSPILE LOGIC ---
67
+ let transcoded = originalContent;
68
+
69
+ // 1. Replace Imports: .lmp -> .js
70
+ transcoded = transcoded.replace(/from\s+['"](.+?)\.lmp['"]/g, "from '$1.js'");
71
+
72
+ // 2. SYNTAX LARAVEL -> JS (Fitur Request User: "->")
73
+ // Mengubah tanda panah "->" menjadi titik "."
74
+ // Hati-hati: Kita coba sebisa mungkin tidak mengubah "->" yang ada di dalam string.
75
+ // Regex ini mencari "->" yang TIDAK diapit kutip (Simpel, mungkin tidak 100% sempurna tapi cukup untuk have fun)
76
+ // Atau kita brute force saja asalkan user tahu resikonya.
77
+ // Kita gunakan pendekatan brute replace tapi hindari arrow function "=>" (aman karena beda karakter)
78
+
79
+ // Replace "->" dengan "."
80
+ transcoded = transcoded.split('\n').map(line => {
81
+ // Cek komentar //
82
+ if (line.trim().startsWith('//')) return line;
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, '.');
88
+ }).join('\n');
89
+
90
+ fs.writeFileSync(destPath, transcoded);
91
+
92
+ // 4. Import file .js
93
+ const module = await import('file://' + destPath + '?t=' + Date.now());
94
+ return module;
95
+ }
96
+
6
97
 
7
98
  export async function serveProject() {
8
99
  const root = process.cwd();
9
- const routesFile = path.join(root, 'routes', 'web.js');
100
+
101
+ const routesFile = path.join(root, 'routes', 'web.lmp');
102
+ if (!fs.existsSync(routesFile)) return console.log("❌ Not a LumpiaJS Project. (Missing routes/web.lmp)");
10
103
 
11
- if (!fs.existsSync(routesFile)) return console.log("❌ This is not a LumpiaJS MVC project! (Cannot find routes/web.js)");
104
+ const env = loadEnv(root);
105
+ const config = loadConfig(root);
12
106
 
13
- console.log('🔄 Loading core framework...');
14
-
15
- try {
16
- // Load User Routes
17
- const userRouteUrl = path.join(root, 'routes', 'web.js');
18
- await import('file://' + userRouteUrl);
107
+ if (config.klambi === 'tailwindcss') startTailwindWatcher(root);
108
+ else if (config.klambi === 'bootstrap') handleBootstrap(root);
19
109
 
20
- console.log(`🛣️ Routes found: ${routes.length}`);
110
+ try {
111
+ 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!`);
21
116
 
22
- // Start Server
23
117
  const server = http.createServer(async (req, res) => {
24
118
  const method = req.method;
25
119
  const url = new URL(req.url, `http://${req.headers.host}`);
26
120
  const pathname = url.pathname;
27
121
 
28
- console.log(`📥 ${method} ${pathname}`);
122
+ if (env.APP_DEBUG === 'true') console.log(`📥 ${method} ${pathname}`);
29
123
 
30
- const match = routes.find(r => r.path === pathname && r.method === method);
124
+ const publicMap = {
125
+ '/css/': path.join(root, 'public', 'css'),
126
+ '/vendor/': path.join(root, 'public', 'vendor')
127
+ };
128
+ for (const [prefix, localPath] of Object.entries(publicMap)) {
129
+ if (pathname.startsWith(prefix)) {
130
+ const relativePath = pathname.slice(prefix.length);
131
+ const filePath = path.join(localPath, relativePath);
132
+ if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
133
+ const ext = path.extname(filePath);
134
+ const mime = ext === '.css' ? 'text/css' : (ext === '.js' ? 'text/javascript' : 'application/octet-stream');
135
+ res.writeHead(200, {'Content-Type': mime});
136
+ res.end(fs.readFileSync(filePath));
137
+ return;
138
+ }
139
+ }
140
+ }
141
+
142
+ let match = null;
143
+ let params = {};
144
+ for (const route of routes) {
145
+ const result = matchRoute(route, method, pathname);
146
+ if (result) {
147
+ match = route;
148
+ params = result.params;
149
+ break;
150
+ }
151
+ }
31
152
 
32
153
  if (match) {
33
154
  try {
34
- // Action format: 'ControllerName@method'
35
155
  const [controllerName, methodName] = match.action.split('@');
156
+ const controllerPath = path.join(root, 'app', 'controllers', controllerName + '.lmp');
36
157
 
37
- // Path to 'app/controllers'
38
- const controllerPath = path.join(root, 'app', 'controllers', controllerName + '.js');
39
-
40
- if (!fs.existsSync(controllerPath)) throw new Error(`Controller ${controllerName} not found in app/controllers!`);
158
+ if (!fs.existsSync(controllerPath)) throw new Error(`Controller ${controllerName} not found!`);
41
159
 
42
- const module = await import('file://' + controllerPath + '?update=' + Date.now());
160
+ const module = await loadLumpiaModule(controllerPath);
43
161
  const ControllerClass = module.default;
44
162
  const instance = new ControllerClass();
45
163
 
46
- if (typeof instance[methodName] !== 'function') {
47
- throw new Error(`Method ${methodName} does not exist in ${controllerName}`);
48
- }
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`);
49
169
 
50
- const result = await instance[methodName]();
170
+ const args = Object.values(params);
171
+ const result = await instance[methodName](...args);
51
172
 
52
173
  if (result.type === 'html') {
53
174
  res.writeHead(200, {'Content-Type': 'text/html'});
@@ -61,22 +182,28 @@ export async function serveProject() {
61
182
  }
62
183
  } catch (e) {
63
184
  console.error(e);
185
+ const errorMsg = env.APP_DEBUG === 'true' ? `<pre>${e.stack}</pre>` : `<h1>Server Error</h1>`;
64
186
  res.writeHead(500, {'Content-Type': 'text/html'});
65
- res.end(`<h1>500: Server Error</h1><pre>${e.message}\n${e.stack}</pre>`);
187
+ res.end(errorMsg);
66
188
  }
67
189
  } else {
68
190
  res.writeHead(404, {'Content-Type': 'text/html'});
69
- res.end('<h1>404: Not Found</h1>');
191
+ res.end('<h1>404 Not Found</h1>');
70
192
  }
71
193
  });
72
194
 
73
195
  const port = 3000;
74
196
  server.listen(port, () => {
75
- console.log(`🚀 Server running at http://localhost:${port}`);
76
- console.log(`(Press Ctrl+C to stop)`);
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();
77
204
  });
78
205
 
79
206
  } catch (err) {
80
- console.error('Fatal Error loading routes:', err);
207
+ console.error('Fatal Error:', err);
81
208
  }
82
209
  }
@@ -0,0 +1,20 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export function loadConfig(root) {
5
+ const configPath = path.join(root, 'config.lmp');
6
+ const config = {
7
+ klambi: 'none' // default
8
+ };
9
+
10
+ if (fs.existsSync(configPath)) {
11
+ try {
12
+ const content = fs.readFileSync(configPath, 'utf-8');
13
+ const parsed = JSON.parse(content);
14
+ if (parsed.klambi) config.klambi = parsed.klambi;
15
+ } catch (e) {
16
+ console.error('⚠️ Gagal moco config.lmp', e.message);
17
+ }
18
+ }
19
+ return config;
20
+ }