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.
- package/README.md +36 -159
- package/bin/lumpia.js +8 -7
- package/index.js +4 -1
- package/lib/commands/build.js +246 -0
- package/lib/commands/create.js +151 -97
- package/lib/commands/serve.js +154 -27
- package/lib/core/Config.js +20 -0
- package/lib/core/DB.js +139 -0
- package/lib/core/Env.js +23 -0
- package/lib/core/Router.js +10 -3
- package/lib/core/View.js +21 -8
- package/package.json +4 -2
- package/templates/.env.example +6 -0
- package/templates/gitignore.txt +3 -0
package/lib/core/DB.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
import { loadEnv } from './Env.js';
|
|
3
|
+
|
|
4
|
+
let pool = null;
|
|
5
|
+
|
|
6
|
+
export class DB {
|
|
7
|
+
static async connect() {
|
|
8
|
+
if (pool) return pool;
|
|
9
|
+
|
|
10
|
+
const env = loadEnv(process.cwd());
|
|
11
|
+
|
|
12
|
+
if (!env.DB_HOST || !env.DB_USER || !env.DB_NAME) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
pool = mysql.createPool({
|
|
18
|
+
host: env.DB_HOST,
|
|
19
|
+
user: env.DB_USER,
|
|
20
|
+
password: env.DB_PASSWORD || '',
|
|
21
|
+
database: env.DB_NAME,
|
|
22
|
+
waitForConnections: true,
|
|
23
|
+
connectionLimit: 10,
|
|
24
|
+
queueLimit: 0
|
|
25
|
+
});
|
|
26
|
+
return pool;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error("❌ Gagal connect database:", e.message);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static async query(sql, params = []) {
|
|
34
|
+
const p = await this.connect();
|
|
35
|
+
if (!p) throw new Error("Database durung disetting nang .env!");
|
|
36
|
+
try {
|
|
37
|
+
const [rows, fields] = await p.execute(sql, params);
|
|
38
|
+
return rows;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error("❌ SQL Error:", e.message);
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static table(tableName) {
|
|
46
|
+
return new QueryBuilder(tableName);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class QueryBuilder {
|
|
51
|
+
constructor(table) {
|
|
52
|
+
this.tableName = table;
|
|
53
|
+
this.conditions = [];
|
|
54
|
+
this.bindings = [];
|
|
55
|
+
this.selects = '*';
|
|
56
|
+
this.limitVal = null;
|
|
57
|
+
this.orderByRaw = null;
|
|
58
|
+
this.executed = false; // Flag to prevent double execution if method chaining is weird
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// SELECT logic
|
|
62
|
+
select(fields) {
|
|
63
|
+
this.selects = fields;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// WHERE logic
|
|
68
|
+
where(col, op, val) {
|
|
69
|
+
if (val === undefined) { val = op; op = '='; }
|
|
70
|
+
this.conditions.push(`${col} ${op} ?`);
|
|
71
|
+
this.bindings.push(val);
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
orderBy(col, direction = 'ASC') {
|
|
76
|
+
this.orderByRaw = `${col} ${direction}`;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
take(n) { // Alias for limit, Laravel style
|
|
81
|
+
this.limitVal = n;
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// EKSEKUTOR (Disini logic query dijalankan)
|
|
86
|
+
// Ubah nama method 'get' jadi 'get()' yang mengembalikan Promise<Array>
|
|
87
|
+
async get() {
|
|
88
|
+
let sql = `SELECT ${this.selects} FROM ${this.tableName}`;
|
|
89
|
+
|
|
90
|
+
if (this.conditions.length > 0) {
|
|
91
|
+
sql += ' WHERE ' + this.conditions.join(' AND ');
|
|
92
|
+
}
|
|
93
|
+
if (this.orderByRaw) {
|
|
94
|
+
sql += ' ORDER BY ' + this.orderByRaw;
|
|
95
|
+
}
|
|
96
|
+
if (this.limitVal) {
|
|
97
|
+
sql += ' LIMIT ' + this.limitVal;
|
|
98
|
+
}
|
|
99
|
+
return await DB.query(sql, this.bindings);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Alias first() -> Ambil 1 data
|
|
103
|
+
async first() {
|
|
104
|
+
this.take(1);
|
|
105
|
+
const rows = await this.get();
|
|
106
|
+
return rows.length > 0 ? rows[0] : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Insert
|
|
110
|
+
async insert(data) {
|
|
111
|
+
const keys = Object.keys(data);
|
|
112
|
+
const values = Object.values(data);
|
|
113
|
+
const placeholders = keys.map(() => '?').join(', ');
|
|
114
|
+
const sql = `INSERT INTO ${this.tableName} (${keys.join(', ')}) VALUES (${placeholders})`;
|
|
115
|
+
return await DB.query(sql, values);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Update
|
|
119
|
+
async update(data) {
|
|
120
|
+
const keys = Object.keys(data);
|
|
121
|
+
const values = Object.values(data);
|
|
122
|
+
const setClause = keys.map(k => `${k} = ?`).join(', ');
|
|
123
|
+
|
|
124
|
+
let sql = `UPDATE ${this.tableName} SET ${setClause}`;
|
|
125
|
+
if (this.conditions.length > 0) {
|
|
126
|
+
sql += ' WHERE ' + this.conditions.join(' AND ');
|
|
127
|
+
}
|
|
128
|
+
return await DB.query(sql, [...values, ...this.bindings]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Delete
|
|
132
|
+
async delete() {
|
|
133
|
+
let sql = `DELETE FROM ${this.tableName}`;
|
|
134
|
+
if (this.conditions.length > 0) {
|
|
135
|
+
sql += ' WHERE ' + this.conditions.join(' AND ');
|
|
136
|
+
}
|
|
137
|
+
return await DB.query(sql, this.bindings);
|
|
138
|
+
}
|
|
139
|
+
}
|
package/lib/core/Env.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export function loadEnv(root) {
|
|
5
|
+
const envPath = path.join(root, '.env');
|
|
6
|
+
const env = {};
|
|
7
|
+
|
|
8
|
+
if (fs.existsSync(envPath)) {
|
|
9
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
10
|
+
content.split('\n').forEach(line => {
|
|
11
|
+
if(line.includes('=')) {
|
|
12
|
+
const [key, val] = line.split('=');
|
|
13
|
+
env[key.trim()] = val.trim().replace(/"/g, '');
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
} else {
|
|
17
|
+
// Default values if .env not found
|
|
18
|
+
env.BASE_URL = "http://localhost:3000";
|
|
19
|
+
env.APP_ENV = "local";
|
|
20
|
+
env.APP_DEBUG = "true";
|
|
21
|
+
}
|
|
22
|
+
return env;
|
|
23
|
+
}
|
package/lib/core/Router.js
CHANGED
|
@@ -3,12 +3,19 @@
|
|
|
3
3
|
export const routes = [];
|
|
4
4
|
|
|
5
5
|
export const Jalan = {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// Semarangan
|
|
7
|
+
gawe: (path, action) => routes.push({ path, action, method: 'GET' }),
|
|
8
|
+
jupuk: (path, action) => routes.push({ path, action, method: 'GET' }),
|
|
8
9
|
kirim: (path, action) => routes.push({ path, action, method: 'POST' }),
|
|
9
10
|
anyar: (path, action) => routes.push({ path, action, method: 'PUT' }),
|
|
10
11
|
bucak: (path, action) => routes.push({ path, action, method: 'DELETE' }),
|
|
12
|
+
|
|
13
|
+
// Internasional (Laravel-like)
|
|
14
|
+
get: (path, action) => routes.push({ path, action, method: 'GET' }),
|
|
15
|
+
post: (path, action) => routes.push({ path, action, method: 'POST' }),
|
|
16
|
+
put: (path, action) => routes.push({ path, action, method: 'PUT' }),
|
|
17
|
+
delete: (path, action) => routes.push({ path, action, method: 'DELETE' }),
|
|
11
18
|
};
|
|
12
19
|
|
|
13
|
-
// Global Router Container
|
|
20
|
+
// Global Router Container
|
|
14
21
|
global.LumpiaRouter = routes;
|
package/lib/core/View.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { loadConfig } from './Config.js';
|
|
2
4
|
|
|
3
5
|
// --- KAMUS BAHASA SEMARANG (Blade-like Template Engine) ---
|
|
4
|
-
// Mengubah sintaks .lmp ke HTML siap render dengan interpolasi data
|
|
5
6
|
export function renderLumpia(viewPath, data = {}) {
|
|
6
7
|
let content = fs.readFileSync(viewPath, 'utf-8');
|
|
7
8
|
|
|
8
9
|
// 1. Ekstrak bagian <klambi>, <kulit>, <isi>
|
|
9
10
|
const matchKulit = content.match(/<kulit>([\s\S]*?)<\/kulit>/);
|
|
10
|
-
const matchIsi = content.match(/<isi>([\s\S]*?)<\/isi>/);
|
|
11
|
+
const matchIsi = content.match(/<isi>([\s\S]*?)<\/isi>/);
|
|
11
12
|
const matchKlambi = content.match(/<klambi>([\s\S]*?)<\/klambi>/);
|
|
12
13
|
|
|
13
14
|
let htmlBody = matchKulit ? matchKulit[1] : '';
|
|
14
15
|
let clientScript = matchIsi ? matchIsi[1] : '';
|
|
15
16
|
let cssStyle = matchKlambi ? matchKlambi[1] : '';
|
|
16
17
|
|
|
17
|
-
// 2. Transpile Client-side JS
|
|
18
|
+
// 2. Transpile Client-side JS
|
|
18
19
|
const dictionary = [
|
|
19
20
|
{ asal: /ono\s/g, jadi: 'let ' },
|
|
20
21
|
{ asal: /paten\s/g, jadi: 'const ' },
|
|
@@ -27,26 +28,38 @@ export function renderLumpia(viewPath, data = {}) {
|
|
|
27
28
|
];
|
|
28
29
|
dictionary.forEach(kata => clientScript = clientScript.replace(kata.asal, kata.jadi));
|
|
29
30
|
|
|
30
|
-
// 3. Templating Engine
|
|
31
|
-
// Mengganti {{ variable }} dengan value dari `data`
|
|
31
|
+
// 3. Templating Engine
|
|
32
32
|
for (const [key, value] of Object.entries(data)) {
|
|
33
33
|
const regex = new RegExp(`{{\\s*${key}\\s*}}`, 'g');
|
|
34
|
-
// Simple XSS protection could be added here
|
|
35
34
|
htmlBody = htmlBody.replace(regex, value);
|
|
36
35
|
}
|
|
37
36
|
|
|
37
|
+
// --- 4. AUTO INJECT ASSETS BASED ON CONFIG ---
|
|
38
|
+
// Kita baca config dari PROJECT_ROOT (asumsi process.cwd())
|
|
39
|
+
let headInjection = '';
|
|
40
|
+
const config = loadConfig(process.cwd());
|
|
41
|
+
|
|
42
|
+
// Inject main.css (Tailwind build result or Custom CSS)
|
|
43
|
+
headInjection += `<link rel="stylesheet" href="/css/style.css">`;
|
|
44
|
+
|
|
45
|
+
// Inject Vendor Specific
|
|
46
|
+
if (config.klambi === 'bootstrap') {
|
|
47
|
+
headInjection += `<link rel="stylesheet" href="/vendor/bootstrap/css/bootstrap.min.css">`;
|
|
48
|
+
// Optional JS injection could be added here
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
// 4. Rakit Akhir
|
|
39
52
|
return `<!DOCTYPE html>
|
|
40
53
|
<html>
|
|
41
54
|
<head>
|
|
42
55
|
<title>Lumpia App</title>
|
|
43
|
-
|
|
56
|
+
${headInjection}
|
|
57
|
+
<style> ${cssStyle}</style>
|
|
44
58
|
</head>
|
|
45
59
|
<body>
|
|
46
60
|
${htmlBody}
|
|
47
61
|
<div id="output-lumpia"></div>
|
|
48
62
|
<script>
|
|
49
|
-
// Runtime Helper
|
|
50
63
|
function tampil(txt) {
|
|
51
64
|
let el = document.getElementById('output-lumpia');
|
|
52
65
|
if(el) el.innerText = txt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumpiajs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Bahasa Pemrograman Semarangan",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"lumpia": "./bin/lumpia.js"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"fs-extra": "^11.1.1"
|
|
11
|
+
"fs-extra": "^11.1.1",
|
|
12
|
+
"mysql2": "^3.16.0",
|
|
13
|
+
"prompts": "^2.4.2"
|
|
12
14
|
}
|
|
13
15
|
}
|