lumpiajs 1.0.2 → 1.0.5
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 +196 -0
- package/bin/lumpia.js +29 -0
- package/index.js +3 -141
- package/lib/commands/create.js +149 -0
- package/lib/commands/serve.js +82 -0
- package/lib/core/Controller.js +20 -0
- package/lib/core/Model.js +41 -0
- package/lib/core/Router.js +14 -0
- package/lib/core/View.js +58 -0
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# 🥟 LumpiaJS
|
|
2
|
+
|
|
3
|
+
**Bahasa Pemrograman Web dengan Kearifan Lokal Semarangan.**
|
|
4
|
+
_Framework ini dibuat untuk seru-seruan (have fun) dan biar bikin web jadi lebih cepat dan menyenangkan!_ (Rencananya sih gitu... wkwkwk)
|
|
5
|
+
|
|
6
|
+
## 🚀 Cara Pakai (Quick Start)
|
|
7
|
+
|
|
8
|
+
Kamu bisa pilih cara yang paling enak buat mulai bikin (masak) web:
|
|
9
|
+
|
|
10
|
+
### 1. Langsung Gas (Tanpa Install)
|
|
11
|
+
|
|
12
|
+
Kalau malas install-install, pastikan komputer kamu sudah ada Node.js, lalu langsung aja pakai `npx`:
|
|
13
|
+
|
|
14
|
+
**Langkah 1: Buat Project**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx lumpiajs create-project warung-ku
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
_(Atau pakai istilah lokal: `npx lumpiajs buka-cabang warung-ku`)_
|
|
21
|
+
|
|
22
|
+
**Langkah 2: Masuk & Install Bumbu (Dependencies)**
|
|
23
|
+
Penting! Kamu harus install dependencies biar `import` framework-nya jalan.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd warung-ku
|
|
27
|
+
npm install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Langkah 3: Nyalakan Kompor (Server)**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm start
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
_(Atau manual: `npx lumpia dodolan`)_
|
|
37
|
+
Websitemu bakal jalan di `http://localhost:3000`.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### 2. Install Global (Biar Bisa Dipakai Terus)
|
|
42
|
+
|
|
43
|
+
Kalau kamu pengen perintah `lumpia` bisa dipanggil dari mana saja di terminal:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 1. Install dulu secara global
|
|
47
|
+
npm install -g lumpiajs
|
|
48
|
+
|
|
49
|
+
# 2. Bikin project baru
|
|
50
|
+
lumpia create-project toko-lumpia
|
|
51
|
+
|
|
52
|
+
# 3. Masuk folder & install npm
|
|
53
|
+
cd toko-lumpia
|
|
54
|
+
npm install
|
|
55
|
+
|
|
56
|
+
# 4. Jalanin server
|
|
57
|
+
lumpia dodolan
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Cara Update ke Versi Terbaru:**
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install -g lumpiajs@latest
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🏗️ Struktur Project (Standar MVC)
|
|
69
|
+
|
|
70
|
+
LumpiaJS sekarang menggunakan arsitektur **MVC (Model-View-Controller)** yang mengikuti standar internasional, jadi developer Laravel atau Express pasti langsung paham.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
warung-ku/
|
|
74
|
+
├── app/
|
|
75
|
+
│ ├── controllers/ # Otak Logika (Controller)
|
|
76
|
+
│ └── models/ # Pengolah Data (Model)
|
|
77
|
+
├── routes/
|
|
78
|
+
│ └── web.js # Rute URL
|
|
79
|
+
├── views/ # Tampilan (.lmp)
|
|
80
|
+
├── package.json
|
|
81
|
+
└── ...
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 1. Routes (`routes/web.js`)
|
|
85
|
+
|
|
86
|
+
Tempat mengatur alamat URL web kamu.
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
import { Jalan } from "lumpiajs";
|
|
90
|
+
|
|
91
|
+
// Jalan.gawe(url, 'NamaController@method')
|
|
92
|
+
Jalan.gawe("/", "HomeController@index");
|
|
93
|
+
Jalan.gawe("/api/products", "ProductController@index");
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 2. Controllers (`app/controllers`)
|
|
97
|
+
|
|
98
|
+
Otak dari aplikasimu. Class harus meng-extend `Controller`.
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
import { Controller } from "lumpiajs";
|
|
102
|
+
|
|
103
|
+
export default class HomeController extends Controller {
|
|
104
|
+
index() {
|
|
105
|
+
// Tampilkan file di folder views/home.lmp
|
|
106
|
+
// Kirim data 'pesan' ke view
|
|
107
|
+
return this.tampil("home", {
|
|
108
|
+
pesan: "Halo Dunia!",
|
|
109
|
+
tanggal: new Date().toLocaleDateString(),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 3. Views (`views`)
|
|
116
|
+
|
|
117
|
+
File `.lmp` adalah tempat kamu menulis HTML, CSS, dan JS Semarangan dalam satu file.
|
|
118
|
+
Gunakan `{{ nama_variabel }}` untuk menampilkan data dari Controller.
|
|
119
|
+
|
|
120
|
+
```html
|
|
121
|
+
<lump>
|
|
122
|
+
<klambi> /* CSS di sini */ h1 { color: #d35400; } </klambi>
|
|
123
|
+
|
|
124
|
+
<kulit>
|
|
125
|
+
<!-- HTML di sini -->
|
|
126
|
+
<h1>{{ pesan }}</h1>
|
|
127
|
+
<p>Tanggal server: {{ tanggal }}</p>
|
|
128
|
+
</kulit>
|
|
129
|
+
|
|
130
|
+
<isi>
|
|
131
|
+
// JavaScript Client-side (Bahasa Semarangan) gawe sapa() { alert("Halo,
|
|
132
|
+
Lur!"); }
|
|
133
|
+
</isi>
|
|
134
|
+
</lump>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 4. Models (`app/models`)
|
|
138
|
+
|
|
139
|
+
Tempat mengolah data (Database/API). Mendukung gaya penulisan ala Eloquent.
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
import { Model } from "lumpiajs";
|
|
143
|
+
|
|
144
|
+
// Contoh penggunaan di Controller:
|
|
145
|
+
// Model.use(dataProduk).dimana('harga', '<', 5000).kabeh();
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 🧐 Kamus Bahasa (Transpiler)
|
|
151
|
+
|
|
152
|
+
Khusus di dalam tag **`<isi>`** (pada file `.lmp`), kamu bisa menggunakan sintaks unik ini:
|
|
153
|
+
|
|
154
|
+
- `ono` ➔ `let`
|
|
155
|
+
- `paten` ➔ `const`
|
|
156
|
+
- `gawe` ➔ `function`
|
|
157
|
+
- `yen` ➔ `if`
|
|
158
|
+
- `liyane` ➔ `else`
|
|
159
|
+
- `mandek` ➔ `return`
|
|
160
|
+
- `ora` ➔ `!`
|
|
161
|
+
- `panjang()` ➔ `.length`
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 🤝 Cara Lapor Masalah atau Kasih Saran
|
|
166
|
+
|
|
167
|
+
Baru nemu bug? Atau punya ide jenius biar LumpiaJS makin jos? Sampaikan saja!
|
|
168
|
+
|
|
169
|
+
Caranya gampang:
|
|
170
|
+
|
|
171
|
+
1. Buka link ini: [https://github.com/fastroware/lumpiajs/issues](https://github.com/fastroware/lumpiajs/issues)
|
|
172
|
+
2. Klik tombol warna hijau bertuliskan **"New Issue"**.
|
|
173
|
+
3. Isi Judul dengan jelas.
|
|
174
|
+
4. Jelaskan masalah atau saranmu di kolom deskripsi.
|
|
175
|
+
5. Klik **"Submit new issue"**.
|
|
176
|
+
|
|
177
|
+
Selesai! Masukanmu akan saya baca pas lagi senggang.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## ⚠️ DISCLAIMER (PENTING BANGET, WAJIB DIBACA!) ⚠️
|
|
182
|
+
|
|
183
|
+
**LumpiaJS ini 100% project _Have Fun_ & Eksperimen.**
|
|
184
|
+
|
|
185
|
+
Kami **TIDAK BERTANGGUNG JAWAB** atas segala bentuk kerugian yang mungkin terjadi akibat penggunaan software ini, termasuk tapi tidak terbatas pada:
|
|
186
|
+
|
|
187
|
+
- Kebocoran data.
|
|
188
|
+
- Hilangnya file penting.
|
|
189
|
+
- Komputer meledak (lebay, tapi tetap saja hati-hati).
|
|
190
|
+
- Kerugian materiil maupun immateriil lainnya.
|
|
191
|
+
|
|
192
|
+
Gunakan framework ini dengan resiko ditanggung sendiri (_Use at your own risk_). Kalau ada error di production karena nekat pakai ini, jangan nyalahin kami ya! 🙏
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
_Dibuat dengan ❤️ dan 🥟 dari Semarang._
|
package/bin/lumpia.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createProject } from '../lib/commands/create.js';
|
|
3
|
+
import { serveProject } from '../lib/commands/serve.js';
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const perintah = args[0];
|
|
8
|
+
const parameter = args[1];
|
|
9
|
+
|
|
10
|
+
if (perintah === 'create-project' || perintah === 'buka-cabang') {
|
|
11
|
+
createProject(parameter);
|
|
12
|
+
}
|
|
13
|
+
else if (perintah === 'dodolan' || perintah === 'serve') {
|
|
14
|
+
serveProject();
|
|
15
|
+
}
|
|
16
|
+
else if (perintah === 'goreng') {
|
|
17
|
+
console.log("🚧 Fitur 'goreng' saiki wis otomatis digabung karo 'dodolan' via JIT Compiler MVC.");
|
|
18
|
+
console.log(" Silakan gunake: lumpia dodolan");
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
console.log('Perintah ora dikenal.');
|
|
22
|
+
console.log('------------------------------------------------');
|
|
23
|
+
console.log('1. lumpia create-project <nama> (Bikin project)');
|
|
24
|
+
console.log('2. lumpia dodolan (Jalanin server)');
|
|
25
|
+
console.log('------------------------------------------------');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
main();
|
package/index.js
CHANGED
|
@@ -1,142 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
1
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{ asal: /<\/lump>/g, jadi: '' },
|
|
9
|
-
{ asal: /ono\s/g, jadi: 'let ' },
|
|
10
|
-
{ asal: /paten\s/g, jadi: 'const ' },
|
|
11
|
-
{ asal: /gawe\s/g, jadi: 'function ' },
|
|
12
|
-
{ asal: /yen\s/g, jadi: 'if ' },
|
|
13
|
-
{ asal: /liyane/g, jadi: 'else' },
|
|
14
|
-
{ asal: /mandek;/g, jadi: 'return;' },
|
|
15
|
-
{ asal: /ora\s/g, jadi: '!' },
|
|
16
|
-
{ asal: /panjang\(/g, jadi: 'len(' },
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const runtimeScript = `
|
|
20
|
-
<script>
|
|
21
|
-
function ambil(sel) { return document.querySelector(sel).value; }
|
|
22
|
-
function tampil(txt) {
|
|
23
|
-
let el = document.getElementById('output-lumpia');
|
|
24
|
-
if(el) el.innerText = txt;
|
|
25
|
-
else alert(txt);
|
|
26
|
-
}
|
|
27
|
-
function len(x) { return x.length; }
|
|
28
|
-
</script>
|
|
29
|
-
`;
|
|
30
|
-
|
|
31
|
-
// --- TEMPLATE PROJECT BARU ---
|
|
32
|
-
const boilerplateCode = `<lump>
|
|
33
|
-
<klambi>
|
|
34
|
-
h1 { color: #d35400; font-family: sans-serif; }
|
|
35
|
-
button { padding: 10px 20px; background: #2ecc71; border: none; color: white; cursor: pointer; }
|
|
36
|
-
button:hover { background: #27ae60; }
|
|
37
|
-
</klambi>
|
|
38
|
-
|
|
39
|
-
<kulit>
|
|
40
|
-
<h1>Sugeng Rawuh di LumpiaJS</h1>
|
|
41
|
-
<p>Project anyar siap dimasak!</p>
|
|
42
|
-
<button onclick="sapa()">Pencet Aku</button>
|
|
43
|
-
</kulit>
|
|
44
|
-
|
|
45
|
-
<isi>
|
|
46
|
-
gawe sapa() {
|
|
47
|
-
tampil("Halo! Iki project pertamamu, Bro!");
|
|
48
|
-
}
|
|
49
|
-
</isi>
|
|
50
|
-
</lump>`;
|
|
51
|
-
|
|
52
|
-
async function main() {
|
|
53
|
-
const args = process.argv.slice(2);
|
|
54
|
-
const perintah = args[0]; // misal: 'create-project' atau 'goreng'
|
|
55
|
-
const parameter = args[1]; // misal: 'tokoku'
|
|
56
|
-
|
|
57
|
-
// --- FITUR 1: Bikin Project Baru ---
|
|
58
|
-
if (perintah === 'create-project' || perintah === 'buka-cabang') {
|
|
59
|
-
const namaProject = parameter;
|
|
60
|
-
|
|
61
|
-
if (!namaProject) {
|
|
62
|
-
console.log('❌ Eh, jeneng project-e opo?');
|
|
63
|
-
console.log('Contoh: lumpia create-project warung-baru');
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const projectPath = path.join(process.cwd(), namaProject);
|
|
68
|
-
const srcPath = path.join(projectPath, 'src');
|
|
69
|
-
|
|
70
|
-
if (fs.existsSync(projectPath)) {
|
|
71
|
-
console.log(`⚠️ Waduh, folder "${namaProject}" wis ono, Bro. Ganti jeneng liyo.`);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
console.log(`🔨 Lagi nyiapke lahan kanggo "${namaProject}"...`);
|
|
76
|
-
|
|
77
|
-
// 1. Bikin Folder Utama
|
|
78
|
-
fs.mkdirSync(projectPath);
|
|
79
|
-
|
|
80
|
-
// 2. Bikin Folder src
|
|
81
|
-
fs.mkdirSync(srcPath);
|
|
82
|
-
|
|
83
|
-
// 3. Tulis file contoh (Boilerplate)
|
|
84
|
-
fs.writeFileSync(path.join(srcPath, 'app.lmp'), boilerplateCode);
|
|
85
|
-
|
|
86
|
-
console.log('✅ Siap, Juragan! Project wis dadi.');
|
|
87
|
-
console.log('-------------------------------------------');
|
|
88
|
-
console.log(`cd ${namaProject}`);
|
|
89
|
-
console.log('lumpia goreng');
|
|
90
|
-
console.log('-------------------------------------------');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// --- FITUR 2: Compile (Goreng) ---
|
|
94
|
-
else if (perintah === 'goreng') {
|
|
95
|
-
console.log('🍳 Sik, lagi nggoreng kodingan...');
|
|
96
|
-
const srcDir = './src';
|
|
97
|
-
const distDir = './dist';
|
|
98
|
-
|
|
99
|
-
if (!fs.existsSync(srcDir)) return console.log('❌ Folder src ora ono! (Coba: lumpia create-project namaproject)');
|
|
100
|
-
if (!fs.existsSync(distDir)) fs.mkdirSync(distDir);
|
|
101
|
-
|
|
102
|
-
const files = fs.readdirSync(srcDir).filter(file => file.endsWith('.lmp'));
|
|
103
|
-
|
|
104
|
-
files.forEach(file => {
|
|
105
|
-
let content = fs.readFileSync(path.join(srcDir, file), 'utf-8');
|
|
106
|
-
|
|
107
|
-
const matchKulit = content.match(/<kulit>([\s\S]*?)<\/kulit>/);
|
|
108
|
-
const matchIsi = content.match(/<isi>([\s\S]*?)<\/isi>/);
|
|
109
|
-
const matchKlambi = content.match(/<klambi>([\s\S]*?)<\/klambi>/);
|
|
110
|
-
|
|
111
|
-
let htmlnya = matchKulit ? matchKulit[1] : '';
|
|
112
|
-
let logicnya = matchIsi ? matchIsi[1] : '';
|
|
113
|
-
let cssnya = matchKlambi ? matchKlambi[1] : '';
|
|
114
|
-
|
|
115
|
-
dictionary.forEach(kata => logicnya = logicnya.replace(kata.asal, kata.jadi));
|
|
116
|
-
|
|
117
|
-
const hasil = `<!DOCTYPE html>
|
|
118
|
-
<html>
|
|
119
|
-
<head>
|
|
120
|
-
<title>Lumpia App</title>
|
|
121
|
-
<style>body{font-family:sans-serif;padding:20px;} ${cssnya}</style>
|
|
122
|
-
</head>
|
|
123
|
-
<body>
|
|
124
|
-
${htmlnya}
|
|
125
|
-
<div id="output-lumpia" style="margin-top:20px; font-weight:bold;"></div>
|
|
126
|
-
${runtimeScript}
|
|
127
|
-
<script>${logicnya}</script>
|
|
128
|
-
</body>
|
|
129
|
-
</html>`;
|
|
130
|
-
|
|
131
|
-
const namaFileBaru = file.replace('.lmp', '.html');
|
|
132
|
-
fs.writeFileSync(path.join(distDir, namaFileBaru), hasil);
|
|
133
|
-
console.log(`✅ Mateng: dist/${namaFileBaru}`);
|
|
134
|
-
});
|
|
135
|
-
} else {
|
|
136
|
-
console.log('Perintah ora dikenal.');
|
|
137
|
-
console.log('1. lumpia create-project <nama>');
|
|
138
|
-
console.log('2. lumpia goreng');
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
main();
|
|
2
|
+
export { Jalan, routes } from './lib/core/Router.js';
|
|
3
|
+
export { LumpiaModel as Model } from './lib/core/Model.js';
|
|
4
|
+
export { Controller } from './lib/core/Controller.js';
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
// --- TEMPLATES ---
|
|
5
|
+
const routesTemplate = `import { Jalan } from 'lumpiajs';
|
|
6
|
+
|
|
7
|
+
// Define Routes
|
|
8
|
+
Jalan.gawe('/', 'HomeController@index');
|
|
9
|
+
Jalan.gawe('/profile', 'HomeController@profile');
|
|
10
|
+
Jalan.gawe('/api/products', 'ProductController@index');
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
const controllerTemplate = `import { Controller } from 'lumpiajs';
|
|
14
|
+
|
|
15
|
+
export default class HomeController extends Controller {
|
|
16
|
+
index() {
|
|
17
|
+
// Render view in 'views' folder
|
|
18
|
+
return this.tampil('home', {
|
|
19
|
+
message: 'Welcome to LumpiaJS MVC!',
|
|
20
|
+
author: 'Pakdhe Koding'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
profile() {
|
|
25
|
+
return this.tampil('profile', { name: 'Loyal User' });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const modelTemplate = `// Example dummy data
|
|
31
|
+
const productData = [
|
|
32
|
+
{ id: 1, name: 'Lumpia Basah', price: 5000 },
|
|
33
|
+
{ id: 2, name: 'Lumpia Goreng', price: 6000 },
|
|
34
|
+
{ id: 3, name: 'Tahu Gimbal', price: 12000 }
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export default productData;
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const productControllerTemplate = `import { Controller, Model } from 'lumpiajs';
|
|
41
|
+
import ProductData from '../../app/models/Product.js';
|
|
42
|
+
|
|
43
|
+
export default class ProductController extends Controller {
|
|
44
|
+
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
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
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
|
+
|
|
64
|
+
<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>
|
|
71
|
+
</div>
|
|
72
|
+
</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
|
+
</lump>`;
|
|
84
|
+
|
|
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>`;
|
|
92
|
+
|
|
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
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
export function createProject(parameter) {
|
|
109
|
+
const projectName = parameter;
|
|
110
|
+
if (!projectName) {
|
|
111
|
+
console.log('❌ Please specify project name.');
|
|
112
|
+
console.log('Example: lumpia create-project my-app');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const root = path.join(process.cwd(), projectName);
|
|
117
|
+
if (fs.existsSync(root)) return console.log(`❌ Folder ${projectName} already exists.`);
|
|
118
|
+
|
|
119
|
+
console.log(`🔨 Building MVC foundation in ${projectName}...`);
|
|
120
|
+
|
|
121
|
+
fs.mkdirSync(root);
|
|
122
|
+
|
|
123
|
+
// STANDARD MVC STRUCTURE:
|
|
124
|
+
// app/controllers
|
|
125
|
+
// app/models
|
|
126
|
+
// views
|
|
127
|
+
// routes
|
|
128
|
+
|
|
129
|
+
fs.mkdirSync(path.join(root, 'app', 'controllers'), { recursive: true });
|
|
130
|
+
fs.mkdirSync(path.join(root, 'app', 'models'), { recursive: true });
|
|
131
|
+
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);
|
|
141
|
+
fs.writeFileSync(path.join(root, 'views', 'profile.lmp'), viewProfileTemplate);
|
|
142
|
+
|
|
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('----------------------------------------------------');
|
|
149
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import { routes } from '../core/Router.js';
|
|
5
|
+
import { renderLumpia } from '../core/View.js';
|
|
6
|
+
|
|
7
|
+
export async function serveProject() {
|
|
8
|
+
const root = process.cwd();
|
|
9
|
+
const routesFile = path.join(root, 'routes', 'web.js');
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(routesFile)) return console.log("❌ This is not a LumpiaJS MVC project! (Cannot find routes/web.js)");
|
|
12
|
+
|
|
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);
|
|
19
|
+
|
|
20
|
+
console.log(`🛣️ Routes found: ${routes.length}`);
|
|
21
|
+
|
|
22
|
+
// Start Server
|
|
23
|
+
const server = http.createServer(async (req, res) => {
|
|
24
|
+
const method = req.method;
|
|
25
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
26
|
+
const pathname = url.pathname;
|
|
27
|
+
|
|
28
|
+
console.log(`📥 ${method} ${pathname}`);
|
|
29
|
+
|
|
30
|
+
const match = routes.find(r => r.path === pathname && r.method === method);
|
|
31
|
+
|
|
32
|
+
if (match) {
|
|
33
|
+
try {
|
|
34
|
+
// Action format: 'ControllerName@method'
|
|
35
|
+
const [controllerName, methodName] = match.action.split('@');
|
|
36
|
+
|
|
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!`);
|
|
41
|
+
|
|
42
|
+
const module = await import('file://' + controllerPath + '?update=' + Date.now());
|
|
43
|
+
const ControllerClass = module.default;
|
|
44
|
+
const instance = new ControllerClass();
|
|
45
|
+
|
|
46
|
+
if (typeof instance[methodName] !== 'function') {
|
|
47
|
+
throw new Error(`Method ${methodName} does not exist in ${controllerName}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = await instance[methodName]();
|
|
51
|
+
|
|
52
|
+
if (result.type === 'html') {
|
|
53
|
+
res.writeHead(200, {'Content-Type': 'text/html'});
|
|
54
|
+
res.end(result.content);
|
|
55
|
+
} else if (result.type === 'json') {
|
|
56
|
+
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
57
|
+
res.end(result.content);
|
|
58
|
+
} else {
|
|
59
|
+
res.writeHead(200, {'Content-Type': 'text/plain'});
|
|
60
|
+
res.end(String(result));
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error(e);
|
|
64
|
+
res.writeHead(500, {'Content-Type': 'text/html'});
|
|
65
|
+
res.end(`<h1>500: Server Error</h1><pre>${e.message}\n${e.stack}</pre>`);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
res.writeHead(404, {'Content-Type': 'text/html'});
|
|
69
|
+
res.end('<h1>404: Not Found</h1>');
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const port = 3000;
|
|
74
|
+
server.listen(port, () => {
|
|
75
|
+
console.log(`🚀 Server running at http://localhost:${port}`);
|
|
76
|
+
console.log(`(Press Ctrl+C to stop)`);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error('Fatal Error loading routes:', err);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { renderLumpia } from './View.js';
|
|
4
|
+
|
|
5
|
+
// 3. CONTROLLER BASE
|
|
6
|
+
export class Controller {
|
|
7
|
+
tampil(viewName, data = {}) {
|
|
8
|
+
// Cek lokasi view relative dari CWD user (folder 'views')
|
|
9
|
+
const viewPath = path.join(process.cwd(), 'views', `${viewName}.lmp`);
|
|
10
|
+
if (fs.existsSync(viewPath)) {
|
|
11
|
+
return { type: 'html', content: renderLumpia(viewPath, data) };
|
|
12
|
+
} else {
|
|
13
|
+
return { type: 'html', content: `<h1>404: View '${viewName}' not found in 'views' folder.</h1>` };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
json(data) {
|
|
18
|
+
return { type: 'json', content: JSON.stringify(data) };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// 1. MODEL (Eloquent-like)
|
|
2
|
+
export class LumpiaModel {
|
|
3
|
+
constructor(data = []) {
|
|
4
|
+
this.data = data;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
static use(jsonData) {
|
|
8
|
+
return new LumpiaModel(jsonData);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Ambil kabeh data
|
|
12
|
+
kabeh() {
|
|
13
|
+
return this.data;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Filter cari (where)
|
|
17
|
+
dimana(key, operator, value) {
|
|
18
|
+
if (value === undefined) { value = operator; operator = '=='; }
|
|
19
|
+
|
|
20
|
+
const filtered = this.data.filter(item => {
|
|
21
|
+
if (operator === '==') return item[key] == value;
|
|
22
|
+
if (operator === '>') return item[key] > value;
|
|
23
|
+
if (operator === '<') return item[key] < value;
|
|
24
|
+
if (operator === '>=') return item[key] >= value;
|
|
25
|
+
if (operator === '<=') return item[key] <= value;
|
|
26
|
+
if (operator === '!=') return item[key] != value;
|
|
27
|
+
return false;
|
|
28
|
+
});
|
|
29
|
+
return new LumpiaModel(filtered);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Ambil satu (first)
|
|
33
|
+
siji() {
|
|
34
|
+
return this.data[0] || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return raw array
|
|
38
|
+
jupuk() {
|
|
39
|
+
return this.data;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
// 2. ROUTER : Simpan Jalur
|
|
3
|
+
export const routes = [];
|
|
4
|
+
|
|
5
|
+
export const Jalan = {
|
|
6
|
+
gawe: (path, action) => routes.push({ path, action, method: 'GET' }), // GET
|
|
7
|
+
jupuk: (path, action) => routes.push({ path, action, method: 'GET' }), // Alias GET
|
|
8
|
+
kirim: (path, action) => routes.push({ path, action, method: 'POST' }),
|
|
9
|
+
anyar: (path, action) => routes.push({ path, action, method: 'PUT' }),
|
|
10
|
+
bucak: (path, action) => routes.push({ path, action, method: 'DELETE' }),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Global Router Container (for easy access in runtime)
|
|
14
|
+
global.LumpiaRouter = routes;
|
package/lib/core/View.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
|
|
3
|
+
// --- KAMUS BAHASA SEMARANG (Blade-like Template Engine) ---
|
|
4
|
+
// Mengubah sintaks .lmp ke HTML siap render dengan interpolasi data
|
|
5
|
+
export function renderLumpia(viewPath, data = {}) {
|
|
6
|
+
let content = fs.readFileSync(viewPath, 'utf-8');
|
|
7
|
+
|
|
8
|
+
// 1. Ekstrak bagian <klambi>, <kulit>, <isi>
|
|
9
|
+
const matchKulit = content.match(/<kulit>([\s\S]*?)<\/kulit>/);
|
|
10
|
+
const matchIsi = content.match(/<isi>([\s\S]*?)<\/isi>/); // Client-side JS
|
|
11
|
+
const matchKlambi = content.match(/<klambi>([\s\S]*?)<\/klambi>/);
|
|
12
|
+
|
|
13
|
+
let htmlBody = matchKulit ? matchKulit[1] : '';
|
|
14
|
+
let clientScript = matchIsi ? matchIsi[1] : '';
|
|
15
|
+
let cssStyle = matchKlambi ? matchKlambi[1] : '';
|
|
16
|
+
|
|
17
|
+
// 2. Transpile Client-side JS (Semarangan -> JS Standard)
|
|
18
|
+
const dictionary = [
|
|
19
|
+
{ asal: /ono\s/g, jadi: 'let ' },
|
|
20
|
+
{ asal: /paten\s/g, jadi: 'const ' },
|
|
21
|
+
{ asal: /gawe\s/g, jadi: 'function ' },
|
|
22
|
+
{ asal: /yen\s/g, jadi: 'if ' },
|
|
23
|
+
{ asal: /liyane/g, jadi: 'else' },
|
|
24
|
+
{ asal: /mandek;/g, jadi: 'return;' },
|
|
25
|
+
{ asal: /ora\s/g, jadi: '!' },
|
|
26
|
+
{ asal: /panjang\(/g, jadi: 'len(' },
|
|
27
|
+
];
|
|
28
|
+
dictionary.forEach(kata => clientScript = clientScript.replace(kata.asal, kata.jadi));
|
|
29
|
+
|
|
30
|
+
// 3. Templating Engine (Mirip Blade {{ variable }})
|
|
31
|
+
// Mengganti {{ variable }} dengan value dari `data`
|
|
32
|
+
for (const [key, value] of Object.entries(data)) {
|
|
33
|
+
const regex = new RegExp(`{{\\s*${key}\\s*}}`, 'g');
|
|
34
|
+
// Simple XSS protection could be added here
|
|
35
|
+
htmlBody = htmlBody.replace(regex, value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 4. Rakit Akhir
|
|
39
|
+
return `<!DOCTYPE html>
|
|
40
|
+
<html>
|
|
41
|
+
<head>
|
|
42
|
+
<title>Lumpia App</title>
|
|
43
|
+
<style>body{font-family:sans-serif;padding:20px;} ${cssStyle}</style>
|
|
44
|
+
</head>
|
|
45
|
+
<body>
|
|
46
|
+
${htmlBody}
|
|
47
|
+
<div id="output-lumpia"></div>
|
|
48
|
+
<script>
|
|
49
|
+
// Runtime Helper
|
|
50
|
+
function tampil(txt) {
|
|
51
|
+
let el = document.getElementById('output-lumpia');
|
|
52
|
+
if(el) el.innerText = txt;
|
|
53
|
+
}
|
|
54
|
+
${clientScript}
|
|
55
|
+
</script>
|
|
56
|
+
</body>
|
|
57
|
+
</html>`;
|
|
58
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumpiajs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Bahasa Pemrograman Semarangan",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
6
7
|
"bin": {
|
|
7
|
-
"lumpia": "./
|
|
8
|
+
"lumpia": "./bin/lumpia.js"
|
|
8
9
|
},
|
|
9
10
|
"dependencies": {
|
|
10
11
|
"fs-extra": "^11.1.1"
|