lapeh 2.6.17 ā 3.0.2
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/.env.example +1 -6
- package/README.md +19 -85
- package/bin/index.js +84 -180
- package/dist/lib/bootstrap.d.ts.map +1 -1
- package/dist/lib/bootstrap.js +17 -16
- package/dist/lib/core/store.d.ts +55 -0
- package/dist/lib/core/store.d.ts.map +1 -0
- package/dist/lib/core/store.js +66 -0
- package/dist/lib/middleware/error.d.ts.map +1 -1
- package/dist/lib/middleware/error.js +1 -20
- package/dist/lib/utils/validator.d.ts.map +1 -1
- package/dist/lib/utils/validator.js +3 -32
- package/dist/src/modules/Auth/auth.controller.d.ts.map +1 -1
- package/dist/src/modules/Auth/auth.controller.js +118 -105
- package/dist/src/modules/Rbac/rbac.controller.d.ts.map +1 -1
- package/dist/src/modules/Rbac/rbac.controller.js +141 -140
- package/dist/src/routes/index.d.ts.map +1 -1
- package/dist/src/routes/index.js +0 -5
- package/doc/en/CHEATSHEET.md +3 -7
- package/doc/en/CLI.md +16 -41
- package/doc/en/DEPLOYMENT.md +171 -245
- package/doc/en/GETTING_STARTED.md +1 -25
- package/doc/en/PACKAGES.md +2 -3
- package/doc/en/STRUCTURE.md +1 -11
- package/doc/en/TUTORIAL.md +61 -119
- package/doc/id/CHANGELOG.md +16 -0
- package/doc/id/CHEATSHEET.md +0 -4
- package/doc/id/CLI.md +19 -54
- package/doc/id/DEPLOYMENT.md +171 -245
- package/doc/id/GETTING_STARTED.md +91 -115
- package/doc/id/PACKAGES.md +0 -1
- package/doc/id/STRUCTURE.md +1 -11
- package/doc/id/TUTORIAL.md +51 -109
- package/gitignore.template +0 -10
- package/lib/bootstrap.ts +39 -38
- package/lib/core/store.ts +116 -0
- package/lib/middleware/error.ts +1 -21
- package/lib/utils/validator.ts +3 -39
- package/package.json +4 -18
- package/scripts/init-project.js +2 -108
- package/scripts/make-module.js +1 -12
- package/scripts/seed-json.js +158 -0
- package/src/modules/Auth/auth.controller.ts +156 -106
- package/src/modules/Rbac/rbac.controller.ts +193 -138
- package/src/routes/index.ts +0 -3
- package/src/routes/rbac.ts +42 -42
- package/storage/logs/.0337f5062fe676994d1dc340156e089444e3d6e0-audit.json +5 -10
- package/storage/logs/lapeh-2025-12-30.log +1093 -0
- package/tsconfig.build.json +1 -3
- package/tsconfig.json +0 -1
- package/lib/core/database.ts +0 -5
- package/prisma/base.prisma.template +0 -8
- package/prisma/migrations/20251225163737_init/migration.sql +0 -236
- package/prisma/migrations/20251226000329_create_pets_table/migration.sql +0 -11
- package/prisma/migrations/20251226001249_create_pets_table/migration.sql +0 -82
- package/prisma/migrations/20251226001717_restore_core_models/migration.sql +0 -236
- package/prisma/migrations/migration_lock.toml +0 -3
- package/prisma/schema.prisma +0 -197
- package/prisma/seed.ts +0 -411
- package/scripts/compile-schema.js +0 -64
- package/src/modules/Auth/auth.prisma +0 -106
- package/src/modules/Pets/pets.controller.ts +0 -238
- package/src/modules/Pets/pets.prisma +0 -9
- package/src/modules/Rbac/rbac.prisma +0 -68
- package/src/routes/pets.ts +0 -13
- package/storage/logs/lapeh-2025-12-26.log +0 -88
- package/storage/logs/lapeh-2025-12-27.log +0 -217
package/.env.example
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
PORT=8000
|
|
2
|
-
DATABASE_PROVIDER="postgresql"
|
|
3
|
-
DATABASE_URL="postgresql://sianu:12341234@localhost:5432/db_example_test?schema=public"
|
|
4
2
|
|
|
5
3
|
# Used for all encryption-related tasks in the framework (JWT, etc.)
|
|
6
4
|
JWT_SECRET="replace_this_with_a_secure_random_string"
|
|
@@ -11,9 +9,6 @@ TZ="Asia/Jakarta"
|
|
|
11
9
|
# Redis Configuration (Optional)
|
|
12
10
|
# If REDIS_URL is not set or connection fails, the framework will automatically
|
|
13
11
|
# switch to an in-memory Redis mock (bundled). No installation required for development.
|
|
14
|
-
# REDIS_URL="redis://
|
|
15
|
-
# NO_REDIS="true"
|
|
16
|
-
|
|
17
|
-
# To force disable Redis and use in-memory mock even if Redis is available:
|
|
12
|
+
# REDIS_URL="redis://localhost:6379"
|
|
18
13
|
# NO_REDIS="true"
|
|
19
14
|
|
package/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
**Lapeh** adalah framework **Node.js** berbasis **Express** dan **TypeScript** yang dirancang untuk kecepatan dan skalabilitas. Menggabungkan fleksibilitas Express dengan struktur solid ala **Laravel** dan **NestJS**, Lapeh memberikan pengalaman development **REST API** yang cepat, terstandarisasi, dan siap produksi.
|
|
4
4
|
|
|
5
|
-
Cocok untuk developer yang mencari **Express boilerplate** dengan fitur lengkap:
|
|
5
|
+
Cocok untuk developer yang mencari **Express boilerplate** dengan fitur lengkap: Authentication, dan Zero-Config Redis.
|
|
6
6
|
|
|
7
7
|
## š Fitur Utama
|
|
8
8
|
|
|
9
9
|
- **Production Ready**: Struktur folder modular (MVC) yang mudah dikembangkan.
|
|
10
10
|
- **TypeScript First**: Full type-safety untuk mengurangi runtime error.
|
|
11
|
-
- **
|
|
11
|
+
- **Database Agnostic**: Bebas pilih database dan ORM (Prisma, TypeORM, Drizzle, dll).
|
|
12
12
|
- **Standardized Structure**: Controller, Service, dan Route yang terpisah rapi.
|
|
13
|
-
- **Auto CLI Generator**: Buat modul
|
|
13
|
+
- **Auto CLI Generator**: Buat modul dan controller dengan satu perintah.
|
|
14
14
|
- **Smart Caching**: Otomatis menggunakan Redis jika tersedia, fallback ke in-memory jika tidak.
|
|
15
15
|
- **Secure by Default**: Dilengkapi Helmet, Rate Limiting, CORS, dan JWT Auth.
|
|
16
16
|
- **Robust Validation**: Validasi request otomatis menggunakan Zod.
|
|
@@ -57,9 +57,7 @@ Kami menyusun "Learning Path" agar Anda bisa memahami framework ini dari nol hin
|
|
|
57
57
|
|
|
58
58
|
## š¦ Instalasi & Penggunaan
|
|
59
59
|
|
|
60
|
-
Anda dapat menginstall framework ini menggunakan versi terbaru
|
|
61
|
-
|
|
62
|
-
### 1. Menggunakan Versi Terbaru (Recommended)
|
|
60
|
+
Anda dapat menginstall framework ini menggunakan versi terbaru:
|
|
63
61
|
|
|
64
62
|
```bash
|
|
65
63
|
npx lapeh@latest nama-project-anda
|
|
@@ -69,30 +67,13 @@ Perintah di atas akan membuat proyek **bersih** (clean slate):
|
|
|
69
67
|
|
|
70
68
|
- Struktur folder dibuat.
|
|
71
69
|
- Dependensi diinstall.
|
|
72
|
-
- Database dikonfigurasi & dimigrasi (hanya Schema, **tanpa data**).
|
|
73
70
|
- Folder `bin` dan `lib` framework tersembunyi di `node_modules` agar root proyek Anda tetap rapi.
|
|
74
71
|
|
|
75
|
-
### 2. Setup Lengkap dengan Demo Data (`--full`)
|
|
76
|
-
|
|
77
|
-
Jika Anda ingin mencoba fitur lengkap dengan data demo (Users, Roles, Pets), gunakan flag `--full`:
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
npx lapeh@latest nama-project-anda --full
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Apa bedanya?
|
|
84
|
-
|
|
85
|
-
- **Tanpa `--full`**: Database kosong (hanya tabel). Cocok untuk memulai proyek baru dari nol.
|
|
86
|
-
- **Dengan `--full`**: Database otomatis di-seed dengan data User (Super Admin), Roles, Permissions, dan 50.000 data demo Pets.
|
|
87
|
-
|
|
88
72
|
### Apa yang terjadi otomatis?
|
|
89
73
|
|
|
90
74
|
1. Struktur project dibuat (Core framework tersembunyi sebagai dependency).
|
|
91
75
|
2. Dependencies diinstall.
|
|
92
|
-
3.
|
|
93
|
-
4. **Database** dibuat dan dimigrasi otomatis.
|
|
94
|
-
5. **JWT Secret** di-generate otomatis.
|
|
95
|
-
6. **Seeding Data** (Hanya jika menggunakan `--full`).
|
|
76
|
+
3. **JWT Secret** di-generate otomatis.
|
|
96
77
|
|
|
97
78
|
Masuk ke folder project dan jalankan:
|
|
98
79
|
|
|
@@ -112,18 +93,6 @@ Framework ini didesain dengan memprioritaskan keamanan:
|
|
|
112
93
|
- **Zero-Vulnerability Policy**: Kami secara rutin melakukan audit dependensi (`npm audit`) untuk memastikan tidak ada celah keamanan.
|
|
113
94
|
- **Framework-as-Dependency**: Dengan menyembunyikan core logic di `node_modules`, pembaruan framework menjadi lebih mudah (cukup update versi `lapeh` di `package.json`) tanpa merusak kode aplikasi Anda.
|
|
114
95
|
|
|
115
|
-
### š Akun Default (Jika menggunakan `--full` atau `npm run db:seed`)
|
|
116
|
-
|
|
117
|
-
Jika Anda melakukan setup dengan flag `--full`, database akan terisi dengan akun default berikut:
|
|
118
|
-
|
|
119
|
-
| Role | Email | Password |
|
|
120
|
-
| :-------------- | :---------- | :------- |
|
|
121
|
-
| **Super Admin** | `sa@sa.com` | `string` |
|
|
122
|
-
| **Admin** | `a@a.com` | `string` |
|
|
123
|
-
| **User** | `u@u.com` | `string` |
|
|
124
|
-
|
|
125
|
-
> **Catatan:** Segera ubah password akun-akun ini jika Anda mendeploy ke production!
|
|
126
|
-
|
|
127
96
|
---
|
|
128
97
|
|
|
129
98
|
## š Upgrade Project
|
|
@@ -202,18 +171,7 @@ Command ini akan membuat:
|
|
|
202
171
|
- `src/services/product.service.ts`
|
|
203
172
|
- `src/routes/product.route.ts` (dan otomatis didaftarkan di `src/routes/index.ts` jika memungkinkan)
|
|
204
173
|
|
|
205
|
-
### 2. Membuat
|
|
206
|
-
|
|
207
|
-
Membuat file model Prisma baru di dalam folder `src/models/` (atau `prisma/models` tergantung konfigurasi).
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
npm run make:model NamaModel
|
|
211
|
-
# Contoh: npm run make:model User
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
Ini akan membuat file `src/models/User.prisma`.
|
|
215
|
-
|
|
216
|
-
### 3. Membuat Controller
|
|
174
|
+
### 2. Membuat Controller
|
|
217
175
|
|
|
218
176
|
Membuat file Controller baru. Gunakan flag `-r` untuk membuat controller lengkap dengan method CRUD (index, show, store, update, destroy).
|
|
219
177
|
|
|
@@ -225,33 +183,21 @@ npm run make:controller NamaController
|
|
|
225
183
|
npm run make:controller PaymentController -r
|
|
226
184
|
```
|
|
227
185
|
|
|
228
|
-
###
|
|
186
|
+
### 3. Database (No-ORM)
|
|
229
187
|
|
|
230
|
-
|
|
188
|
+
Since v3.0.0, Lapeh Framework **does not include a default ORM** (like Prisma). We believe in giving you full control over your database stack.
|
|
231
189
|
|
|
232
|
-
|
|
233
|
-
- **Apply Changes**: Jalankan perintah migrasi standar, sistem akan otomatis menggabungkan (compile) schema Anda.
|
|
190
|
+
You can freely choose to use:
|
|
234
191
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
npm run prisma:migrate
|
|
192
|
+
- **Prisma** (Manual installation)
|
|
193
|
+
- **TypeORM**
|
|
194
|
+
- **Drizzle ORM**
|
|
195
|
+
- **Mongoose**
|
|
196
|
+
- **Raw SQL** (pg, mysql2)
|
|
241
197
|
|
|
242
|
-
|
|
243
|
-
npm run db:studio
|
|
198
|
+
The framework provides a `Validator` class for request validation and a `Serializer` for response formatting, but data persistence is up to you.
|
|
244
199
|
|
|
245
|
-
|
|
246
|
-
npm run db:reset
|
|
247
|
-
|
|
248
|
-
# Deploy ke Production
|
|
249
|
-
npm run prisma:deploy
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
> **Catatan:** Script `compile-schema.js` akan otomatis berjalan sebelum perintah prisma di atas dieksekusi.
|
|
253
|
-
|
|
254
|
-
### 5. Generate JWT Secret
|
|
200
|
+
### 4. Generate JWT Secret
|
|
255
201
|
|
|
256
202
|
Jika Anda perlu me-refresh secret key JWT:
|
|
257
203
|
|
|
@@ -259,7 +205,7 @@ Jika Anda perlu me-refresh secret key JWT:
|
|
|
259
205
|
npm run generate:jwt
|
|
260
206
|
```
|
|
261
207
|
|
|
262
|
-
###
|
|
208
|
+
### 5. Maintenance (Clear Config)
|
|
263
209
|
|
|
264
210
|
Membersihkan cache framework, NPM, build artifacts, dan temporary files (sangat berguna jika mengalami isu cache aneh atau ingin reset environment development).
|
|
265
211
|
|
|
@@ -281,14 +227,10 @@ src/
|
|
|
281
227
|
āāā controllers/ # Logika Request & Response
|
|
282
228
|
āāā services/ # Business Logic
|
|
283
229
|
āāā routes/ # Definisi Route API
|
|
284
|
-
āāā models/ # Definisi Schema Prisma per Model
|
|
285
230
|
āāā middleware/ # Auth, Validation, Error Handling
|
|
286
231
|
āāā schema/ # Zod Validation Schemas
|
|
287
232
|
āāā utils/ # Helper Functions
|
|
288
233
|
āāā index.ts # App Entry Point
|
|
289
|
-
prisma/
|
|
290
|
-
āāā schema.prisma # [GENERATED] Jangan edit file ini
|
|
291
|
-
āāā base.prisma.template # Konfigurasi Datasource & Generator
|
|
292
234
|
```
|
|
293
235
|
|
|
294
236
|
## š Lisensi
|
|
@@ -299,24 +241,17 @@ MIT
|
|
|
299
241
|
|
|
300
242
|
## š Deployment Guide
|
|
301
243
|
|
|
302
|
-
### 1) Build
|
|
244
|
+
### 1) Build
|
|
303
245
|
|
|
304
246
|
- Build: `npm run build`
|
|
305
247
|
- Start (dev): `npm run start`
|
|
306
248
|
- Start (prod): `npm run start:prod`
|
|
307
|
-
- Hooks otomatis:
|
|
308
|
-
- `prebuild`, `prestart`, dan `prestart:prod` akan memanggil `npm run prisma:generate` sehingga Prisma Client selalu tersedia tanpa error.
|
|
309
249
|
|
|
310
250
|
### 2) Production Environment
|
|
311
251
|
|
|
312
252
|
- Pastikan `.env` berisi kredensial production:
|
|
313
|
-
- `DATABASE_URL` dan `DATABASE_PROVIDER` (mysql/postgresql)
|
|
314
253
|
- `JWT_SECRET` (gunakan `npm run generate:jwt` untuk mengganti)
|
|
315
|
-
-
|
|
316
|
-
|
|
317
|
-
```bash
|
|
318
|
-
npm run prisma:deploy
|
|
319
|
-
```
|
|
254
|
+
- Database credentials (sesuai pilihan ORM/DB Anda)
|
|
320
255
|
|
|
321
256
|
### 3) Menjalankan dengan PM2
|
|
322
257
|
|
|
@@ -417,7 +352,6 @@ sudo systemctl reload apache2
|
|
|
417
352
|
|
|
418
353
|
### 6) Checklist Produksi
|
|
419
354
|
|
|
420
|
-
- `npm run prisma:deploy` sukses dan tabel terbentuk
|
|
421
355
|
- `pm2 status` menunjukkan proses hidup
|
|
422
356
|
- Proxy (Nginx/Apache) menuju port aplikasi (default 4000)
|
|
423
357
|
- `.env` aman dan tidak di-commit ke repository
|
package/bin/index.js
CHANGED
|
@@ -2,9 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { execSync } = require('child_process');
|
|
5
|
+
const { execSync, spawn } = require('child_process');
|
|
6
6
|
const readline = require('readline');
|
|
7
7
|
|
|
8
|
+
// --- Helper Functions for Animation ---
|
|
9
|
+
|
|
10
|
+
async function spin(text, fn) {
|
|
11
|
+
const frames = ['ā ', 'ā ', 'ā ¹', 'ā ø', 'ā ¼', 'ā “', 'ā ¦', 'ā §', 'ā ', 'ā '];
|
|
12
|
+
let i = 0;
|
|
13
|
+
process.stdout.write(`\x1b[?25l`); // Hide cursor
|
|
14
|
+
|
|
15
|
+
const interval = setInterval(() => {
|
|
16
|
+
process.stdout.write(`\r\x1b[36m${frames[i]} ${text}\x1b[0m`);
|
|
17
|
+
i = (i + 1) % frames.length;
|
|
18
|
+
}, 80);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const result = await fn();
|
|
22
|
+
clearInterval(interval);
|
|
23
|
+
process.stdout.write(`\r\x1b[32mā ${text}\x1b[0m\n`);
|
|
24
|
+
return result;
|
|
25
|
+
} catch (e) {
|
|
26
|
+
clearInterval(interval);
|
|
27
|
+
process.stdout.write(`\r\x1b[31mā ${text}\x1b[0m\n`);
|
|
28
|
+
throw e;
|
|
29
|
+
} finally {
|
|
30
|
+
process.stdout.write(`\x1b[?25h`); // Show cursor
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function runCommand(cmd, cwd) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
// Use spawn to capture output or run silently
|
|
37
|
+
// Using shell: true to handle cross-platform command execution
|
|
38
|
+
const child = spawn(cmd, { cwd, shell: true, stdio: 'pipe' });
|
|
39
|
+
let output = '';
|
|
40
|
+
|
|
41
|
+
child.stdout.on('data', (data) => { output += data.toString(); });
|
|
42
|
+
child.stderr.on('data', (data) => { output += data.toString(); });
|
|
43
|
+
|
|
44
|
+
child.on('close', (code) => {
|
|
45
|
+
if (code === 0) resolve(output);
|
|
46
|
+
else reject(new Error(`Command failed with code ${code}\n${output}`));
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
8
51
|
const args = process.argv.slice(2);
|
|
9
52
|
const command = args[0];
|
|
10
53
|
|
|
@@ -93,10 +136,10 @@ switch (command) {
|
|
|
93
136
|
runDev();
|
|
94
137
|
break;
|
|
95
138
|
case 'start':
|
|
96
|
-
runStart();
|
|
139
|
+
(async () => { await runStart(); })();
|
|
97
140
|
break;
|
|
98
141
|
case 'build':
|
|
99
|
-
runBuild();
|
|
142
|
+
(async () => { await runBuild(); })();
|
|
100
143
|
break;
|
|
101
144
|
case 'upgrade':
|
|
102
145
|
(async () => {
|
|
@@ -115,23 +158,6 @@ switch (command) {
|
|
|
115
158
|
function runDev() {
|
|
116
159
|
console.log('š Starting Lapeh in development mode...');
|
|
117
160
|
try {
|
|
118
|
-
// Generate Prisma Client before starting
|
|
119
|
-
console.log('š Generating Prisma Client...');
|
|
120
|
-
const compileSchemaPath = path.join(process.cwd(), 'scripts/compile-schema.js');
|
|
121
|
-
if (fs.existsSync(compileSchemaPath)) {
|
|
122
|
-
try {
|
|
123
|
-
execSync('node scripts/compile-schema.js', { stdio: 'inherit' });
|
|
124
|
-
} catch (e) {
|
|
125
|
-
console.warn('ā ļø Failed to run compile-schema.js', e.message);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
execSync('npx prisma generate', { stdio: 'inherit' });
|
|
131
|
-
} catch (e) {
|
|
132
|
-
console.warn('ā ļø Failed to run prisma generate. Continuing...', e.message);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
161
|
const tsNodePath = require.resolve('ts-node/register');
|
|
136
162
|
const tsConfigPathsPath = require.resolve('tsconfig-paths/register');
|
|
137
163
|
|
|
@@ -165,8 +191,10 @@ function runDev() {
|
|
|
165
191
|
}
|
|
166
192
|
}
|
|
167
193
|
|
|
168
|
-
function runStart() {
|
|
169
|
-
|
|
194
|
+
async function runStart() {
|
|
195
|
+
await spin('Starting Lapeh production server...', async () => {
|
|
196
|
+
await new Promise(r => setTimeout(r, 1500)); // Simulate startup checks animation
|
|
197
|
+
});
|
|
170
198
|
|
|
171
199
|
let bootstrapPath;
|
|
172
200
|
try {
|
|
@@ -243,23 +271,6 @@ function runStart() {
|
|
|
243
271
|
function runBuild() {
|
|
244
272
|
console.log('š ļø Building Lapeh project...');
|
|
245
273
|
|
|
246
|
-
const compileSchemaPath = path.join(process.cwd(), 'scripts/compile-schema.js');
|
|
247
|
-
if (fs.existsSync(compileSchemaPath)) {
|
|
248
|
-
try {
|
|
249
|
-
execSync('node scripts/compile-schema.js', { stdio: 'inherit' });
|
|
250
|
-
} catch (e) {
|
|
251
|
-
console.error('ā Failed to compile schema.');
|
|
252
|
-
process.exit(1);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
execSync('npx prisma generate', { stdio: 'inherit' });
|
|
258
|
-
} catch (e) {
|
|
259
|
-
console.error('ā Failed to generate prisma client.');
|
|
260
|
-
process.exit(1);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
274
|
try {
|
|
264
275
|
execSync('npx tsc -p tsconfig.build.json && npx tsc-alias -p tsconfig.build.json', { stdio: 'inherit' });
|
|
265
276
|
} catch (e) {
|
|
@@ -291,10 +302,7 @@ async function upgradeProject() {
|
|
|
291
302
|
'tsconfig.json',
|
|
292
303
|
'README.md',
|
|
293
304
|
'ecosystem.config.js',
|
|
294
|
-
'src/redis.ts'
|
|
295
|
-
'src/prisma.ts',
|
|
296
|
-
'prisma/base.prisma.template', // Sync base template for upgrade
|
|
297
|
-
'prisma.config.ts' // Sync prisma config for upgrade
|
|
305
|
+
'src/redis.ts'
|
|
298
306
|
];
|
|
299
307
|
|
|
300
308
|
function syncDirectory(src, dest, clean = false) {
|
|
@@ -332,25 +340,6 @@ async function upgradeProject() {
|
|
|
332
340
|
}
|
|
333
341
|
}
|
|
334
342
|
|
|
335
|
-
// Rename .model -> .prisma (Legacy migration)
|
|
336
|
-
const modelsDir = path.join(currentDir, 'src', 'models');
|
|
337
|
-
if (fs.existsSync(modelsDir)) {
|
|
338
|
-
console.log('š Checking for legacy .model files...');
|
|
339
|
-
const files = fs.readdirSync(modelsDir);
|
|
340
|
-
let renamedCount = 0;
|
|
341
|
-
files.forEach(file => {
|
|
342
|
-
if (file.endsWith('.model')) {
|
|
343
|
-
const oldPath = path.join(modelsDir, file);
|
|
344
|
-
const newPath = path.join(modelsDir, file.replace('.model', '.prisma'));
|
|
345
|
-
fs.renameSync(oldPath, newPath);
|
|
346
|
-
renamedCount++;
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
if (renamedCount > 0) {
|
|
350
|
-
console.log(`ā
Migrated ${renamedCount} files from .model to .prisma`);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
343
|
for (const item of filesToSync) {
|
|
355
344
|
const srcPath = path.join(templateDir, item);
|
|
356
345
|
const destPath = path.join(currentDir, item);
|
|
@@ -497,61 +486,31 @@ function createProject(skipFirstArg = false) {
|
|
|
497
486
|
};
|
|
498
487
|
|
|
499
488
|
(async () => {
|
|
500
|
-
|
|
501
|
-
|
|
489
|
+
// Animation Lapeh "L"
|
|
490
|
+
const lFrames = [
|
|
491
|
+
"āāā ",
|
|
492
|
+
"āāā ",
|
|
493
|
+
"āāā ",
|
|
494
|
+
"āāā ",
|
|
495
|
+
"āāāāāāāā",
|
|
496
|
+
"āāāāāāāā"
|
|
497
|
+
];
|
|
502
498
|
|
|
503
|
-
console.
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
{ key: "Y", label: "Ya (Disarankan)" },
|
|
509
|
-
{ key: "T", label: "Tidak (Setup Manual)" }
|
|
510
|
-
]);
|
|
511
|
-
usePrisma = ormChoice.key === "Y";
|
|
499
|
+
console.clear();
|
|
500
|
+
console.log('\n');
|
|
501
|
+
for (let i = 0; i < lFrames.length; i++) {
|
|
502
|
+
await new Promise(r => setTimeout(r, 100));
|
|
503
|
+
console.log(`\x1b[36m ${lFrames[i]}\x1b[0m`);
|
|
512
504
|
}
|
|
505
|
+
console.log('\n\x1b[36m L A P E H F R A M E W O R K\x1b[0m\n');
|
|
506
|
+
await new Promise(r => setTimeout(r, 800));
|
|
507
|
+
|
|
508
|
+
console.log(`š Creating a new API Lapeh project in ${projectDir}...`);
|
|
509
|
+
fs.mkdirSync(projectDir);
|
|
513
510
|
|
|
514
|
-
let dbType, host, port, user, password, dbName;
|
|
515
|
-
let dbUrl = "";
|
|
516
|
-
let dbProvider = "postgresql";
|
|
517
|
-
|
|
518
|
-
if (usePrisma) {
|
|
519
|
-
if (useDefaults) {
|
|
520
|
-
console.log("ā¹ļø Using default database configuration (PostgreSQL)...");
|
|
521
|
-
dbType = { key: "pgsql", label: "PostgreSQL", provider: "postgresql", defaultPort: "5432" };
|
|
522
|
-
host = "localhost";
|
|
523
|
-
port = "5432";
|
|
524
|
-
user = "postgres";
|
|
525
|
-
password = "password";
|
|
526
|
-
dbName = projectName.replace(/-/g, '_');
|
|
527
|
-
} else {
|
|
528
|
-
console.log("\n--- Database Configuration ---");
|
|
529
|
-
dbType = await selectOption("Database apa yang akan digunakan?", [
|
|
530
|
-
{ key: "pgsql", label: "PostgreSQL", provider: "postgresql", defaultPort: "5432" },
|
|
531
|
-
{ key: "mysql", label: "MySQL", provider: "mysql", defaultPort: "3306" },
|
|
532
|
-
]);
|
|
533
|
-
|
|
534
|
-
host = await ask("Database Host", "localhost");
|
|
535
|
-
port = await ask("Database Port", dbType.defaultPort);
|
|
536
|
-
user = await ask("Database User", "root");
|
|
537
|
-
password = await ask("Database Password", "");
|
|
538
|
-
dbName = await ask("Database Name", projectName.replace(/-/g, '_'));
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
dbProvider = dbType.provider;
|
|
542
|
-
if (dbType.key === "pgsql") {
|
|
543
|
-
dbUrl = `postgresql://${user}:${password}@${host}:${port}/${dbName}?schema=public`;
|
|
544
|
-
} else if (dbType.key === "mysql") {
|
|
545
|
-
dbUrl = `mysql://${user}:${password}@${host}:${port}/${dbName}`;
|
|
546
|
-
}
|
|
547
|
-
} else {
|
|
548
|
-
console.log("ā¹ļø Skipping ORM setup. You will need to configure your own database access.");
|
|
549
|
-
}
|
|
550
|
-
|
|
551
511
|
const ignoreList = [
|
|
552
512
|
'node_modules', 'dist', '.git', '.env', 'bin', 'lib',
|
|
553
|
-
'package-lock.json', '.DS_Store', 'prisma
|
|
554
|
-
'prisma/dev.db', 'prisma/dev.db-journal', 'website',
|
|
513
|
+
'package-lock.json', '.DS_Store', 'prisma', 'website',
|
|
555
514
|
'init', 'test-local-run', 'coverage', 'doc', projectName
|
|
556
515
|
];
|
|
557
516
|
|
|
@@ -562,7 +521,6 @@ function createProject(skipFirstArg = false) {
|
|
|
562
521
|
const srcPath = path.join(src, entry.name);
|
|
563
522
|
const destPath = path.join(dest, entry.name);
|
|
564
523
|
|
|
565
|
-
if (entry.name === 'migrations' && srcPath.includes('prisma')) continue;
|
|
566
524
|
|
|
567
525
|
if (entry.isDirectory()) {
|
|
568
526
|
fs.mkdirSync(destPath);
|
|
@@ -587,17 +545,6 @@ function createProject(skipFirstArg = false) {
|
|
|
587
545
|
|
|
588
546
|
if (fs.existsSync(envExamplePath)) {
|
|
589
547
|
let envContent = fs.readFileSync(envExamplePath, 'utf8');
|
|
590
|
-
if (usePrisma) {
|
|
591
|
-
envContent = envContent.replace(/DATABASE_URL=".+"/g, `DATABASE_URL="${dbUrl}"`);
|
|
592
|
-
envContent = envContent.replace(/DATABASE_URL=.+/g, `DATABASE_URL="${dbUrl}"`);
|
|
593
|
-
envContent = envContent.replace(/DATABASE_PROVIDER=".+"/g, `DATABASE_PROVIDER="${dbProvider}"`);
|
|
594
|
-
envContent = envContent.replace(/DATABASE_PROVIDER=.+/g, `DATABASE_PROVIDER="${dbProvider}"`);
|
|
595
|
-
} else {
|
|
596
|
-
envContent = envContent.replace(/DATABASE_URL=".+"/g, `DATABASE_URL=""`);
|
|
597
|
-
envContent = envContent.replace(/DATABASE_URL=.+/g, `DATABASE_URL=""`);
|
|
598
|
-
envContent = envContent.replace(/DATABASE_PROVIDER=".+"/g, `DATABASE_PROVIDER="none"`);
|
|
599
|
-
envContent = envContent.replace(/DATABASE_PROVIDER=.+/g, `DATABASE_PROVIDER="none"`);
|
|
600
|
-
}
|
|
601
548
|
fs.writeFileSync(envPath, envContent);
|
|
602
549
|
}
|
|
603
550
|
|
|
@@ -614,14 +561,7 @@ function createProject(skipFirstArg = false) {
|
|
|
614
561
|
packageJson.dependencies["lapeh"] = `file:${lapehPath}`;
|
|
615
562
|
}
|
|
616
563
|
|
|
617
|
-
|
|
618
|
-
if (usePrisma) {
|
|
619
|
-
packageJson.dependencies["@prisma/client"] = "^6.0.0";
|
|
620
|
-
packageJson.prisma = {
|
|
621
|
-
seed: "npx ts-node -r tsconfig-paths/register prisma/seed.ts"
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
|
|
564
|
+
|
|
625
565
|
packageJson.version = '1.0.0';
|
|
626
566
|
delete packageJson.bin;
|
|
627
567
|
delete packageJson.peerDependencies;
|
|
@@ -654,64 +594,28 @@ function createProject(skipFirstArg = false) {
|
|
|
654
594
|
}
|
|
655
595
|
|
|
656
596
|
const prismaBaseFile = path.join(projectDir, "prisma", "base.prisma.template");
|
|
657
|
-
|
|
658
|
-
let baseContent = fs.readFileSync(prismaBaseFile, "utf8");
|
|
659
|
-
// Update provider
|
|
660
|
-
baseContent = baseContent.replace(
|
|
661
|
-
/(datasource\s+db\s+\{[\s\S]*?provider\s*=\s*")[^"]+(")/,
|
|
662
|
-
`$1${dbProvider}$2`
|
|
663
|
-
);
|
|
664
|
-
fs.writeFileSync(prismaBaseFile, baseContent);
|
|
665
|
-
}
|
|
597
|
+
// Removed Prisma base file handling
|
|
666
598
|
|
|
667
|
-
console.log('š¦ Installing dependencies...');
|
|
668
599
|
try {
|
|
669
|
-
|
|
600
|
+
await spin('Installing dependencies...', async () => {
|
|
601
|
+
await runCommand('npm install', projectDir);
|
|
602
|
+
});
|
|
670
603
|
} catch (e) {
|
|
671
604
|
console.error('ā Error installing dependencies.');
|
|
605
|
+
console.error(e.message);
|
|
672
606
|
process.exit(1);
|
|
673
607
|
}
|
|
674
608
|
|
|
675
609
|
try {
|
|
676
|
-
|
|
610
|
+
// Also silence the JWT generation output or animate it if needed, but for now just silence/pipe
|
|
611
|
+
// Or keep inherit if user wants to see the key.
|
|
612
|
+
// The original code used 'inherit', and printed "ā
JWT Secret generated..."
|
|
613
|
+
// Let's keep it simple or use runCommand to just do it silently.
|
|
614
|
+
await runCommand('npm run generate:jwt', projectDir);
|
|
615
|
+
console.log('ā
JWT Secret generated.');
|
|
677
616
|
} catch (e) {}
|
|
678
617
|
|
|
679
|
-
|
|
680
|
-
console.log('šļø Setting up database...');
|
|
681
|
-
try {
|
|
682
|
-
execSync('node scripts/compile-schema.js', { cwd: projectDir, stdio: 'inherit' });
|
|
683
|
-
|
|
684
|
-
console.log(' Running migration...');
|
|
685
|
-
if (dbProvider === 'mongodb') {
|
|
686
|
-
execSync('npx prisma db push', { cwd: projectDir, stdio: 'inherit' });
|
|
687
|
-
} else {
|
|
688
|
-
// For Prisma v7, ensure prisma.config.ts is used/detected
|
|
689
|
-
execSync('npx prisma migrate dev --name init_setup', { cwd: projectDir, stdio: 'inherit' });
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Explicitly generate Prisma Client to ensure .prisma/client/default exists
|
|
693
|
-
console.log(' Generating Prisma Client...');
|
|
694
|
-
execSync('npx prisma generate', { cwd: projectDir, stdio: 'inherit' });
|
|
695
|
-
|
|
696
|
-
let runSeed = false;
|
|
697
|
-
if (!useDefaults) {
|
|
698
|
-
const seedChoice = await selectOption("Jalankan seeder?", [
|
|
699
|
-
{ key: "Y", label: "Ya" },
|
|
700
|
-
{ key: "T", label: "Tidak" }
|
|
701
|
-
]);
|
|
702
|
-
runSeed = seedChoice.key === "Y";
|
|
703
|
-
} else {
|
|
704
|
-
runSeed = isFull;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
if (runSeed) {
|
|
708
|
-
console.log(' Seeding database...');
|
|
709
|
-
execSync('npm run db:seed', { cwd: projectDir, stdio: 'inherit' });
|
|
710
|
-
}
|
|
711
|
-
} catch (e) {
|
|
712
|
-
console.warn('ā ļø Database setup failed. Check .env and run manually.');
|
|
713
|
-
}
|
|
714
|
-
}
|
|
618
|
+
// Removed Prisma setup steps
|
|
715
619
|
|
|
716
620
|
console.log(`\nā
Project ${projectName} created successfully!`);
|
|
717
621
|
rl.close();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../lib/bootstrap.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../lib/bootstrap.ts"],"names":[],"mappings":"AAkBA,wBAAsB,SAAS,yDAiI9B;AAED,wBAAsB,SAAS,kBAuD9B"}
|
package/dist/lib/bootstrap.js
CHANGED
|
@@ -16,7 +16,6 @@ const http_1 = __importDefault(require("http"));
|
|
|
16
16
|
const path_1 = __importDefault(require("path"));
|
|
17
17
|
const realtime_1 = require("./core/realtime");
|
|
18
18
|
const redis_1 = require("./core/redis");
|
|
19
|
-
const database_1 = require("./core/database");
|
|
20
19
|
const visitor_1 = require("./middleware/visitor");
|
|
21
20
|
const error_1 = require("./middleware/error");
|
|
22
21
|
const rateLimit_1 = require("./middleware/rateLimit");
|
|
@@ -27,8 +26,12 @@ async function createApp() {
|
|
|
27
26
|
// Since user code (compiled JS) uses require('@lapeh/...')
|
|
28
27
|
// We map '@lapeh' to the directory containing this file (lib/ or dist/lib/)
|
|
29
28
|
module_alias_1.default.addAlias("@lapeh", __dirname);
|
|
30
|
-
//
|
|
29
|
+
// Register alias for src directory (@/) to support imports in controllers/routes
|
|
31
30
|
const isProduction = process.env.NODE_ENV === "production";
|
|
31
|
+
module_alias_1.default.addAlias("@", isProduction
|
|
32
|
+
? path_1.default.join(process.cwd(), "dist", "src")
|
|
33
|
+
: path_1.default.join(process.cwd(), "src"));
|
|
34
|
+
// LOAD USER CONFIG
|
|
32
35
|
const configPath = isProduction
|
|
33
36
|
? path_1.default.join(process.cwd(), "dist", "src", "config")
|
|
34
37
|
: path_1.default.join(process.cwd(), "src", "config");
|
|
@@ -88,26 +91,22 @@ async function createApp() {
|
|
|
88
91
|
});
|
|
89
92
|
// DYNAMIC ROUTE LOADING
|
|
90
93
|
try {
|
|
94
|
+
console.log("BOOTSTRAP: Loading routes. NODE_ENV=", process.env.NODE_ENV);
|
|
91
95
|
const isProduction = process.env.NODE_ENV === "production";
|
|
92
|
-
|
|
96
|
+
let userRoutesPath = isProduction
|
|
93
97
|
? path_1.default.join(process.cwd(), "dist", "src", "routes")
|
|
94
98
|
: path_1.default.join(process.cwd(), "src", "routes");
|
|
99
|
+
// In test environment, explicitly point to index to ensure resolution
|
|
100
|
+
if (process.env.NODE_ENV === "test") {
|
|
101
|
+
// In test environment (ts-jest), we need to point to the TS file
|
|
102
|
+
// And we might need to use the full path with extension
|
|
103
|
+
userRoutesPath = path_1.default.join(process.cwd(), "src", "routes", "index.ts");
|
|
104
|
+
}
|
|
95
105
|
// Gunakan require agar sinkron dan mudah dicatch
|
|
96
106
|
// Check if file exists before requiring to avoid crash in tests/clean env
|
|
97
107
|
try {
|
|
98
|
-
// In test environment, we might need to point to src/routes explicitly if not compiled
|
|
99
|
-
// const routesPath = process.env.NODE_ENV === 'test'
|
|
100
|
-
// ? path.join(process.cwd(), "src", "routes", "index.ts")
|
|
101
|
-
// : userRoutesPath;
|
|
102
|
-
// Note: For TS files in jest, we rely on ts-jest handling 'require' if it points to .ts or we need to use 'import'
|
|
103
|
-
// But 'require' in jest with ts-jest should work if configured.
|
|
104
|
-
// However, require(path) with .ts extension might be tricky.
|
|
105
|
-
// Let's stick to userRoutesPath but maybe adjust for test env.
|
|
106
|
-
// Check if we are in test environment and using ts-jest
|
|
107
|
-
// If so, we might need to import the TS file directly via relative path if alias is not working for require
|
|
108
108
|
const { apiRouter } = require(userRoutesPath);
|
|
109
109
|
app.use("/api", apiRouter);
|
|
110
|
-
console.log(`ā
User routes loaded successfully from ${isProduction ? "dist/" : ""}src/routes`);
|
|
111
110
|
}
|
|
112
111
|
catch (e) {
|
|
113
112
|
// If it's just missing module, maybe we are in test mode or fresh install
|
|
@@ -117,18 +116,21 @@ async function createApp() {
|
|
|
117
116
|
else {
|
|
118
117
|
// In test mode, we really want to know if it failed to load
|
|
119
118
|
console.error(`Error loading routes in test mode from ${userRoutesPath}:`, e);
|
|
119
|
+
throw e;
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
catch (error) {
|
|
124
124
|
console.error(error);
|
|
125
|
+
if (process.env.NODE_ENV === "test")
|
|
126
|
+
throw error;
|
|
125
127
|
}
|
|
126
128
|
app.use(error_1.errorHandler);
|
|
127
129
|
return app;
|
|
128
130
|
}
|
|
129
131
|
async function bootstrap() {
|
|
130
132
|
// Validasi Environment Variables
|
|
131
|
-
const requiredEnvs = ["
|
|
133
|
+
const requiredEnvs = ["JWT_SECRET"];
|
|
132
134
|
const missingEnvs = requiredEnvs.filter((key) => !process.env[key]);
|
|
133
135
|
if (missingEnvs.length > 0) {
|
|
134
136
|
console.error(`ā Missing required environment variables: ${missingEnvs.join(", ")}`);
|
|
@@ -160,7 +162,6 @@ async function bootstrap() {
|
|
|
160
162
|
console.log(`\nš ${signal} received. Closing resources...`);
|
|
161
163
|
server.close(() => console.log("Http server closed."));
|
|
162
164
|
try {
|
|
163
|
-
await database_1.prisma.$disconnect();
|
|
164
165
|
if (redis_1.redis && redis_1.redis.status === "ready")
|
|
165
166
|
await redis_1.redis.quit();
|
|
166
167
|
process.exit(0);
|