lapeh 2.2.5 → 2.2.7
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/api-testing-sepuluh/.env.example +19 -0
- package/api-testing-sepuluh/doc/ARCHITECTURE_GUIDE.md +73 -0
- package/api-testing-sepuluh/doc/CHANGELOG.md +77 -0
- package/api-testing-sepuluh/doc/CHEATSHEET.md +94 -0
- package/api-testing-sepuluh/doc/CLI.md +106 -0
- package/api-testing-sepuluh/doc/CONTRIBUTING.md +105 -0
- package/api-testing-sepuluh/doc/DEPLOYMENT.md +122 -0
- package/api-testing-sepuluh/doc/FAQ.md +81 -0
- package/api-testing-sepuluh/doc/FEATURES.md +165 -0
- package/api-testing-sepuluh/doc/GETTING_STARTED.md +108 -0
- package/api-testing-sepuluh/doc/INTRODUCTION.md +60 -0
- package/api-testing-sepuluh/doc/PACKAGES.md +66 -0
- package/api-testing-sepuluh/doc/PERFORMANCE.md +91 -0
- package/api-testing-sepuluh/doc/ROADMAP.md +93 -0
- package/api-testing-sepuluh/doc/SECURITY.md +93 -0
- package/api-testing-sepuluh/doc/STRUCTURE.md +90 -0
- package/api-testing-sepuluh/doc/TUTORIAL.md +192 -0
- package/api-testing-sepuluh/docker-compose.yml +24 -0
- package/api-testing-sepuluh/eslint.config.mjs +26 -0
- package/api-testing-sepuluh/framework.md +168 -0
- package/api-testing-sepuluh/nodemon.json +6 -0
- package/api-testing-sepuluh/package-lock.json +5539 -0
- package/api-testing-sepuluh/package.json +103 -0
- package/api-testing-sepuluh/prisma/base.prisma.template +7 -0
- package/api-testing-sepuluh/prisma/migrations/20251227034737_init_setup/migration.sql +248 -0
- package/api-testing-sepuluh/prisma/migrations/migration_lock.toml +3 -0
- package/api-testing-sepuluh/prisma/schema.prisma +183 -0
- package/api-testing-sepuluh/prisma/seed.ts +411 -0
- package/api-testing-sepuluh/prisma.config.ts +15 -0
- package/api-testing-sepuluh/readme.md +414 -0
- package/api-testing-sepuluh/scripts/check-update.js +92 -0
- package/api-testing-sepuluh/scripts/compile-schema.js +29 -0
- package/api-testing-sepuluh/scripts/config-clear.js +45 -0
- package/api-testing-sepuluh/scripts/generate-jwt-secret.js +38 -0
- package/api-testing-sepuluh/scripts/init-project.js +178 -0
- package/api-testing-sepuluh/scripts/make-controller.js +205 -0
- package/api-testing-sepuluh/scripts/make-model.js +42 -0
- package/api-testing-sepuluh/scripts/make-module.js +158 -0
- package/api-testing-sepuluh/scripts/verify-rbac-functional.js +187 -0
- package/api-testing-sepuluh/src/controllers/authController.ts +469 -0
- package/api-testing-sepuluh/src/controllers/petController.ts +194 -0
- package/api-testing-sepuluh/src/controllers/rbacController.ts +478 -0
- package/api-testing-sepuluh/src/models/core.prisma +163 -0
- package/api-testing-sepuluh/src/models/pets.prisma +9 -0
- package/api-testing-sepuluh/src/routes/auth.ts +74 -0
- package/api-testing-sepuluh/src/routes/index.ts +10 -0
- package/api-testing-sepuluh/src/routes/pets.ts +13 -0
- package/api-testing-sepuluh/src/routes/rbac.ts +42 -0
- package/api-testing-sepuluh/storage/logs/.gitkeep +0 -0
- package/api-testing-sepuluh/tsconfig.json +39 -0
- package/bin/index.js +142 -18
- package/lib/bootstrap.ts +5 -0
- package/package.json +3 -2
- package/prisma/seed.ts +4 -1
- package/prisma.config.ts +1 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
model cache {
|
|
2
|
+
key String @id
|
|
3
|
+
value String
|
|
4
|
+
expiration Int
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
model cache_locks {
|
|
8
|
+
key String @id
|
|
9
|
+
owner String
|
|
10
|
+
expiration Int
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
model failed_jobs {
|
|
14
|
+
id BigInt @id @default(autoincrement())
|
|
15
|
+
uuid String @unique
|
|
16
|
+
connection String
|
|
17
|
+
queue String
|
|
18
|
+
payload String
|
|
19
|
+
exception String
|
|
20
|
+
failed_at DateTime @default(now())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
model job_batches {
|
|
24
|
+
id String @id
|
|
25
|
+
name String
|
|
26
|
+
total_jobs Int
|
|
27
|
+
pending_jobs Int
|
|
28
|
+
failed_jobs Int
|
|
29
|
+
failed_job_ids String
|
|
30
|
+
options String?
|
|
31
|
+
cancelled_at Int?
|
|
32
|
+
created_at Int
|
|
33
|
+
finished_at Int?
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
model jobs {
|
|
37
|
+
id BigInt @id @default(autoincrement())
|
|
38
|
+
queue String
|
|
39
|
+
payload String
|
|
40
|
+
attempts Int
|
|
41
|
+
reserved_at Int?
|
|
42
|
+
available_at Int
|
|
43
|
+
created_at Int
|
|
44
|
+
|
|
45
|
+
@@index([queue])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
model migrations {
|
|
49
|
+
id Int @id @default(autoincrement())
|
|
50
|
+
migration String
|
|
51
|
+
batch Int
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
model password_reset_tokens {
|
|
55
|
+
email String @id
|
|
56
|
+
token String
|
|
57
|
+
created_at DateTime?
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
model personal_access_tokens {
|
|
61
|
+
id BigInt @id @default(autoincrement())
|
|
62
|
+
tokenable_type String
|
|
63
|
+
tokenable_id BigInt
|
|
64
|
+
name String
|
|
65
|
+
token String @unique
|
|
66
|
+
abilities String?
|
|
67
|
+
last_used_at DateTime?
|
|
68
|
+
expires_at DateTime?
|
|
69
|
+
created_at DateTime?
|
|
70
|
+
updated_at DateTime?
|
|
71
|
+
|
|
72
|
+
@@index([expires_at])
|
|
73
|
+
@@index([tokenable_type, tokenable_id])
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
model sessions {
|
|
77
|
+
id String @id
|
|
78
|
+
user_id BigInt?
|
|
79
|
+
ip_address String?
|
|
80
|
+
user_agent String?
|
|
81
|
+
payload String
|
|
82
|
+
last_activity Int
|
|
83
|
+
|
|
84
|
+
@@index([last_activity])
|
|
85
|
+
@@index([user_id])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
model users {
|
|
89
|
+
id BigInt @id @default(autoincrement())
|
|
90
|
+
uuid String @unique
|
|
91
|
+
name String
|
|
92
|
+
email String @unique
|
|
93
|
+
avatar String?
|
|
94
|
+
avatar_url String?
|
|
95
|
+
email_verified_at DateTime?
|
|
96
|
+
password String
|
|
97
|
+
remember_token String?
|
|
98
|
+
created_at DateTime?
|
|
99
|
+
updated_at DateTime?
|
|
100
|
+
|
|
101
|
+
user_roles user_roles[]
|
|
102
|
+
user_permissions user_permissions[]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
model roles {
|
|
106
|
+
id BigInt @id @default(autoincrement())
|
|
107
|
+
name String
|
|
108
|
+
slug String @unique
|
|
109
|
+
description String?
|
|
110
|
+
created_at DateTime?
|
|
111
|
+
updated_at DateTime?
|
|
112
|
+
|
|
113
|
+
user_roles user_roles[]
|
|
114
|
+
role_permissions role_permissions[]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
model permissions {
|
|
118
|
+
id BigInt @id @default(autoincrement())
|
|
119
|
+
name String
|
|
120
|
+
slug String @unique
|
|
121
|
+
description String?
|
|
122
|
+
created_at DateTime?
|
|
123
|
+
updated_at DateTime?
|
|
124
|
+
|
|
125
|
+
role_permissions role_permissions[]
|
|
126
|
+
user_permissions user_permissions[]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
model user_roles {
|
|
130
|
+
id BigInt @id @default(autoincrement())
|
|
131
|
+
user_id BigInt
|
|
132
|
+
role_id BigInt
|
|
133
|
+
created_at DateTime?
|
|
134
|
+
|
|
135
|
+
user users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
|
136
|
+
role roles @relation(fields: [role_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
|
137
|
+
|
|
138
|
+
@@unique([user_id, role_id])
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
model role_permissions {
|
|
142
|
+
id BigInt @id @default(autoincrement())
|
|
143
|
+
role_id BigInt
|
|
144
|
+
permission_id BigInt
|
|
145
|
+
created_at DateTime?
|
|
146
|
+
|
|
147
|
+
role roles @relation(fields: [role_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
|
148
|
+
permission permissions @relation(fields: [permission_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
|
149
|
+
|
|
150
|
+
@@unique([role_id, permission_id])
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
model user_permissions {
|
|
154
|
+
id BigInt @id @default(autoincrement())
|
|
155
|
+
user_id BigInt
|
|
156
|
+
permission_id BigInt
|
|
157
|
+
created_at DateTime?
|
|
158
|
+
|
|
159
|
+
user users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
|
160
|
+
permission permissions @relation(fields: [permission_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
|
161
|
+
|
|
162
|
+
@@unique([user_id, permission_id])
|
|
163
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import rateLimit from "express-rate-limit";
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
4
|
+
const multer = require("multer");
|
|
5
|
+
import path from "path";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import {
|
|
8
|
+
register,
|
|
9
|
+
login,
|
|
10
|
+
me,
|
|
11
|
+
logout,
|
|
12
|
+
refreshToken,
|
|
13
|
+
updatePassword,
|
|
14
|
+
updateProfile,
|
|
15
|
+
updateAvatar,
|
|
16
|
+
} from "@/controllers/authController";
|
|
17
|
+
import { requireAuth } from "@lapeh/middleware/auth";
|
|
18
|
+
|
|
19
|
+
const authLimiter = rateLimit({
|
|
20
|
+
windowMs: 15 * 60 * 1000,
|
|
21
|
+
max: 50,
|
|
22
|
+
standardHeaders: true,
|
|
23
|
+
legacyHeaders: false,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const avatarUploadDir = process.env.AVATAR_UPLOAD_DIR || "uploads/avatars";
|
|
27
|
+
if (!fs.existsSync(avatarUploadDir)) {
|
|
28
|
+
fs.mkdirSync(avatarUploadDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const storage = (multer as any).diskStorage({
|
|
32
|
+
destination(
|
|
33
|
+
_req: any,
|
|
34
|
+
_file: any,
|
|
35
|
+
cb: (error: Error | null, destination: string) => void
|
|
36
|
+
) {
|
|
37
|
+
cb(null, avatarUploadDir);
|
|
38
|
+
},
|
|
39
|
+
filename(
|
|
40
|
+
_req: any,
|
|
41
|
+
file: any,
|
|
42
|
+
cb: (error: Error | null, filename: string) => void
|
|
43
|
+
) {
|
|
44
|
+
const ext = path.extname(file.originalname);
|
|
45
|
+
const base = path.basename(file.originalname, ext);
|
|
46
|
+
const unique = Date.now() + "-" + Math.round(Math.random() * 1e9);
|
|
47
|
+
cb(null, base + "-" + unique + ext);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const uploadAvatar = multer({ storage });
|
|
52
|
+
|
|
53
|
+
export const authRouter = Router();
|
|
54
|
+
|
|
55
|
+
authRouter.post("/register", authLimiter, register);
|
|
56
|
+
|
|
57
|
+
authRouter.post("/login", authLimiter, login);
|
|
58
|
+
|
|
59
|
+
authRouter.get("/me", requireAuth, me);
|
|
60
|
+
|
|
61
|
+
authRouter.post("/logout", requireAuth, logout);
|
|
62
|
+
|
|
63
|
+
authRouter.post("/refresh", authLimiter, refreshToken);
|
|
64
|
+
|
|
65
|
+
authRouter.put("/password", requireAuth, updatePassword);
|
|
66
|
+
|
|
67
|
+
authRouter.put("/profile", requireAuth, updateProfile);
|
|
68
|
+
|
|
69
|
+
authRouter.post(
|
|
70
|
+
"/avatar",
|
|
71
|
+
requireAuth,
|
|
72
|
+
uploadAvatar.single("avatar"),
|
|
73
|
+
updateAvatar
|
|
74
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { authRouter } from "@/routes/auth";
|
|
3
|
+
import { rbacRouter } from "@/routes/rbac";
|
|
4
|
+
import petRouter from "@/routes/pets";
|
|
5
|
+
|
|
6
|
+
export const apiRouter = Router();
|
|
7
|
+
|
|
8
|
+
apiRouter.use("/auth", authRouter);
|
|
9
|
+
apiRouter.use("/rbac", rbacRouter);
|
|
10
|
+
apiRouter.use("/pets", petRouter);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import * as PetController from "@/controllers/petController";
|
|
3
|
+
import { parseMultipart } from "@lapeh/middleware/multipart";
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
router.get("/", PetController.index);
|
|
8
|
+
router.get("/:id", PetController.show);
|
|
9
|
+
router.post("/", parseMultipart, PetController.store);
|
|
10
|
+
router.put("/:id", parseMultipart, PetController.update);
|
|
11
|
+
router.delete("/:id", PetController.destroy);
|
|
12
|
+
|
|
13
|
+
export default router;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { requireAdmin, requireAuth } from "@lapeh/middleware/auth";
|
|
3
|
+
import {
|
|
4
|
+
createRole,
|
|
5
|
+
listRoles,
|
|
6
|
+
updateRole,
|
|
7
|
+
deleteRole,
|
|
8
|
+
createPermission,
|
|
9
|
+
listPermissions,
|
|
10
|
+
updatePermission,
|
|
11
|
+
deletePermission,
|
|
12
|
+
assignRoleToUser,
|
|
13
|
+
removeRoleFromUser,
|
|
14
|
+
assignPermissionToRole,
|
|
15
|
+
removePermissionFromRole,
|
|
16
|
+
assignPermissionToUser,
|
|
17
|
+
removePermissionFromUser,
|
|
18
|
+
} from "@/controllers/rbacController";
|
|
19
|
+
|
|
20
|
+
export const rbacRouter = Router();
|
|
21
|
+
|
|
22
|
+
rbacRouter.use(requireAuth);
|
|
23
|
+
rbacRouter.use(requireAdmin);
|
|
24
|
+
|
|
25
|
+
rbacRouter.post("/roles", createRole);
|
|
26
|
+
rbacRouter.get("/roles", listRoles);
|
|
27
|
+
rbacRouter.put("/roles/:id", updateRole);
|
|
28
|
+
rbacRouter.delete("/roles/:id", deleteRole);
|
|
29
|
+
|
|
30
|
+
rbacRouter.post("/permissions", createPermission);
|
|
31
|
+
rbacRouter.get("/permissions", listPermissions);
|
|
32
|
+
rbacRouter.put("/permissions/:id", updatePermission);
|
|
33
|
+
rbacRouter.delete("/permissions/:id", deletePermission);
|
|
34
|
+
|
|
35
|
+
rbacRouter.post("/users/assign-role", assignRoleToUser);
|
|
36
|
+
rbacRouter.post("/users/remove-role", removeRoleFromUser);
|
|
37
|
+
|
|
38
|
+
rbacRouter.post("/roles/assign-permission", assignPermissionToRole);
|
|
39
|
+
rbacRouter.post("/roles/remove-permission", removePermissionFromRole);
|
|
40
|
+
|
|
41
|
+
rbacRouter.post("/users/assign-permission", assignPermissionToUser);
|
|
42
|
+
rbacRouter.post("/users/remove-permission", removePermissionFromUser);
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"rootDir": ".",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"noUnusedLocals": true,
|
|
14
|
+
"noUnusedParameters": true,
|
|
15
|
+
"paths": {
|
|
16
|
+
"@lapeh/*": [
|
|
17
|
+
"./node_modules/lapeh/lib/*"
|
|
18
|
+
],
|
|
19
|
+
"@/*": [
|
|
20
|
+
"./src/*"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"include": [
|
|
25
|
+
"lib",
|
|
26
|
+
"src",
|
|
27
|
+
"prisma",
|
|
28
|
+
"generated"
|
|
29
|
+
],
|
|
30
|
+
"exclude": [
|
|
31
|
+
"node_modules",
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"ts-node": {
|
|
35
|
+
"ignore": [
|
|
36
|
+
"node_modules/(?!lapeh)"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
package/bin/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
@@ -36,9 +36,14 @@ function runDev() {
|
|
|
36
36
|
try {
|
|
37
37
|
const tsNodePath = require.resolve('ts-node/register');
|
|
38
38
|
const tsConfigPathsPath = require.resolve('tsconfig-paths/register');
|
|
39
|
+
|
|
40
|
+
// Resolve bootstrap file relative to this script
|
|
41
|
+
// If run from node_modules, it will find ../lib/bootstrap.ts
|
|
42
|
+
// If run from source, it will find ../lib/bootstrap.ts
|
|
43
|
+
const bootstrapPath = path.resolve(__dirname, '../lib/bootstrap.ts');
|
|
44
|
+
|
|
39
45
|
// We execute a script that requires ts-node to run lib/bootstrap.ts
|
|
40
|
-
|
|
41
|
-
execSync(`npx nodemon --exec "node -r ${tsNodePath} -r ${tsConfigPathsPath}" -e "require('./lib/bootstrap').bootstrap()"`, { stdio: 'inherit' });
|
|
46
|
+
execSync(`npx nodemon --watch src --watch lib --ext ts,json --exec "node -r ${tsNodePath} -r ${tsConfigPathsPath} ${bootstrapPath}"`, { stdio: 'inherit' });
|
|
42
47
|
} catch (error) {
|
|
43
48
|
// Ignore error
|
|
44
49
|
}
|
|
@@ -77,6 +82,8 @@ async function upgradeProject() {
|
|
|
77
82
|
|
|
78
83
|
// Files/Folders to overwrite/copy
|
|
79
84
|
const filesToSync = [
|
|
85
|
+
'bin', // Ensure CLI script is updated
|
|
86
|
+
'lib', // Ensure core framework files are updated
|
|
80
87
|
'scripts',
|
|
81
88
|
'docker-compose.yml',
|
|
82
89
|
'.env.example',
|
|
@@ -141,7 +148,11 @@ async function upgradeProject() {
|
|
|
141
148
|
// Update scripts
|
|
142
149
|
currentPackageJson.scripts = {
|
|
143
150
|
...currentPackageJson.scripts,
|
|
144
|
-
...templatePackageJson.scripts
|
|
151
|
+
...templatePackageJson.scripts,
|
|
152
|
+
"dev": "lapeh dev",
|
|
153
|
+
"start": "lapeh start",
|
|
154
|
+
"build": "lapeh build",
|
|
155
|
+
"start:prod": "lapeh start"
|
|
145
156
|
};
|
|
146
157
|
|
|
147
158
|
// Update dependencies
|
|
@@ -157,10 +168,32 @@ async function upgradeProject() {
|
|
|
157
168
|
};
|
|
158
169
|
|
|
159
170
|
// Update Lapeh version tag
|
|
160
|
-
|
|
171
|
+
// For local development, we use file reference. For production publish, use version.
|
|
172
|
+
currentPackageJson.dependencies["lapeh"] = "file:../";
|
|
161
173
|
|
|
162
174
|
fs.writeFileSync(packageJsonPath, JSON.stringify(currentPackageJson, null, 2));
|
|
163
175
|
|
|
176
|
+
// Update tsconfig.json to support framework-as-dependency
|
|
177
|
+
console.log('🔧 Configuring tsconfig.json...');
|
|
178
|
+
const tsconfigPath = path.join(currentDir, 'tsconfig.json');
|
|
179
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
180
|
+
// Use comment-json or just basic parsing if no comments (standard JSON)
|
|
181
|
+
// Since our template tsconfig is standard JSON, require is fine or JSON.parse
|
|
182
|
+
const tsconfig = require(tsconfigPath);
|
|
183
|
+
|
|
184
|
+
// Update paths
|
|
185
|
+
if (tsconfig.compilerOptions && tsconfig.compilerOptions.paths) {
|
|
186
|
+
tsconfig.compilerOptions.paths["@lapeh/*"] = ["./node_modules/lapeh/lib/*"];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Add ts-node ignore configuration
|
|
190
|
+
tsconfig["ts-node"] = {
|
|
191
|
+
"ignore": ["node_modules/(?!lapeh)"]
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
195
|
+
}
|
|
196
|
+
|
|
164
197
|
// Run npm install
|
|
165
198
|
console.log('📦 Installing updated dependencies...');
|
|
166
199
|
try {
|
|
@@ -177,6 +210,7 @@ async function upgradeProject() {
|
|
|
177
210
|
function createProject() {
|
|
178
211
|
const projectName = args.find(arg => !arg.startsWith('-'));
|
|
179
212
|
const isFull = args.includes('--full');
|
|
213
|
+
const useDefaults = args.includes('--defaults');
|
|
180
214
|
|
|
181
215
|
if (!projectName) {
|
|
182
216
|
console.error('❌ Please specify the project name:');
|
|
@@ -233,27 +267,45 @@ function createProject() {
|
|
|
233
267
|
|
|
234
268
|
// --- DATABASE SELECTION ---
|
|
235
269
|
console.log("\n--- Database Configuration ---");
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
270
|
+
let dbType, host, port, user, password, dbName;
|
|
271
|
+
|
|
272
|
+
if (useDefaults) {
|
|
273
|
+
console.log("ℹ️ Using default configuration (--defaults)...");
|
|
274
|
+
dbType = { key: "pgsql", label: "PostgreSQL", provider: "postgresql", defaultPort: "5432" };
|
|
275
|
+
host = "localhost";
|
|
276
|
+
port = "5432";
|
|
277
|
+
user = "postgres"; // Default postgres user is usually postgres, not root
|
|
278
|
+
password = "password"; // Default password
|
|
279
|
+
dbName = projectName.replace(/-/g, '_');
|
|
280
|
+
} else {
|
|
281
|
+
dbType = await selectOption("Database apa yang akan digunakan?", [
|
|
282
|
+
{ key: "pgsql", label: "PostgreSQL", provider: "postgresql", defaultPort: "5432" },
|
|
283
|
+
{ key: "mysql", label: "MySQL", provider: "mysql", defaultPort: "3306" },
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
host = await ask("Database Host", "localhost");
|
|
287
|
+
port = await ask("Database Port", dbType.defaultPort);
|
|
288
|
+
user = await ask("Database User", "root");
|
|
289
|
+
password = await ask("Database Password", "");
|
|
290
|
+
dbName = await ask("Database Name", projectName.replace(/-/g, '_')); // Default db name based on project name
|
|
291
|
+
}
|
|
240
292
|
|
|
241
293
|
let dbUrl = "";
|
|
242
294
|
let dbProvider = dbType.provider;
|
|
243
295
|
|
|
244
|
-
const host = await ask("Database Host", "localhost");
|
|
245
|
-
const port = await ask("Database Port", dbType.defaultPort);
|
|
246
|
-
const user = await ask("Database User", "root");
|
|
247
|
-
const password = await ask("Database Password", "");
|
|
248
|
-
const dbName = await ask("Database Name", projectName.replace(/-/g, '_')); // Default db name based on project name
|
|
249
|
-
|
|
250
296
|
if (dbType.key === "pgsql") {
|
|
251
297
|
dbUrl = `postgresql://${user}:${password}@${host}:${port}/${dbName}?schema=public`;
|
|
252
298
|
} else {
|
|
253
299
|
dbUrl = `mysql://${user}:${password}@${host}:${port}/${dbName}`;
|
|
254
300
|
}
|
|
255
301
|
|
|
256
|
-
|
|
302
|
+
if (!useDefaults) {
|
|
303
|
+
rl.close();
|
|
304
|
+
} else {
|
|
305
|
+
// If we didn't use rl, we might not need to close it if we didn't open it?
|
|
306
|
+
// Actually rl is created at the top. We should close it.
|
|
307
|
+
rl.close();
|
|
308
|
+
}
|
|
257
309
|
|
|
258
310
|
// List of files/folders to exclude
|
|
259
311
|
const ignoreList = [
|
|
@@ -261,7 +313,8 @@ function createProject() {
|
|
|
261
313
|
'dist',
|
|
262
314
|
'.git',
|
|
263
315
|
'.env',
|
|
264
|
-
'bin', //
|
|
316
|
+
'bin', // Exclude bin folder, using dependency instead
|
|
317
|
+
'lib', // Exclude lib folder, using dependency instead
|
|
265
318
|
'package-lock.json',
|
|
266
319
|
'.DS_Store',
|
|
267
320
|
'prisma/migrations', // Exclude existing migrations
|
|
@@ -306,15 +359,86 @@ function createProject() {
|
|
|
306
359
|
packageJson.name = projectName;
|
|
307
360
|
// Add lapeh framework version to dependencies to track it like react-router
|
|
308
361
|
packageJson.dependencies = packageJson.dependencies || {};
|
|
309
|
-
|
|
362
|
+
// For local development, we use file reference. For production publish, use version.
|
|
363
|
+
packageJson.dependencies["lapeh"] = "file:../";
|
|
364
|
+
|
|
365
|
+
// Ensure prisma CLI is available in devDependencies for the new project
|
|
366
|
+
packageJson.devDependencies = packageJson.devDependencies || {};
|
|
367
|
+
packageJson.devDependencies["prisma"] = "7.2.0";
|
|
310
368
|
|
|
369
|
+
// Add missing types for dev
|
|
370
|
+
packageJson.devDependencies["@types/express"] = "^5.0.0";
|
|
371
|
+
packageJson.devDependencies["@types/compression"] = "^1.7.5";
|
|
372
|
+
|
|
311
373
|
packageJson.version = '1.0.0';
|
|
312
374
|
packageJson.description = 'Generated by lapeh';
|
|
313
375
|
delete packageJson.bin; // Remove the bin entry from the generated project
|
|
314
376
|
delete packageJson.repository; // Remove repository info if specific to the template
|
|
315
377
|
|
|
378
|
+
// Update scripts to use lapeh binary
|
|
379
|
+
packageJson.scripts = {
|
|
380
|
+
...packageJson.scripts,
|
|
381
|
+
"dev": "lapeh dev",
|
|
382
|
+
"start": "lapeh start",
|
|
383
|
+
"build": "lapeh build",
|
|
384
|
+
"start:prod": "lapeh start"
|
|
385
|
+
};
|
|
386
|
+
|
|
316
387
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
317
388
|
|
|
389
|
+
// Update tsconfig.json to support framework-as-dependency
|
|
390
|
+
console.log('🔧 Configuring tsconfig.json...');
|
|
391
|
+
const tsconfigPath = path.join(projectDir, 'tsconfig.json');
|
|
392
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
393
|
+
// Use comment-json or just basic parsing if no comments (standard JSON)
|
|
394
|
+
// Since our template tsconfig is standard JSON, require is fine or JSON.parse
|
|
395
|
+
const tsconfig = require(tsconfigPath);
|
|
396
|
+
|
|
397
|
+
// Update paths
|
|
398
|
+
if (tsconfig.compilerOptions && tsconfig.compilerOptions.paths) {
|
|
399
|
+
tsconfig.compilerOptions.paths["@lapeh/*"] = ["./node_modules/lapeh/lib/*"];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Add ts-node ignore configuration
|
|
403
|
+
tsconfig["ts-node"] = {
|
|
404
|
+
"ignore": ["node_modules/(?!lapeh)"]
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Configure prisma.config.ts to use tsconfig-paths
|
|
411
|
+
const prismaConfigPath = path.join(projectDir, 'prisma.config.ts');
|
|
412
|
+
if (fs.existsSync(prismaConfigPath)) {
|
|
413
|
+
console.log('🔧 Configuring prisma.config.ts...');
|
|
414
|
+
let prismaConfigContent = fs.readFileSync(prismaConfigPath, 'utf8');
|
|
415
|
+
prismaConfigContent = prismaConfigContent.replace(
|
|
416
|
+
/seed:\s*"ts-node\s+prisma\/seed\.ts"/g,
|
|
417
|
+
'seed: "ts-node -r tsconfig-paths/register prisma/seed.ts"'
|
|
418
|
+
);
|
|
419
|
+
fs.writeFileSync(prismaConfigPath, prismaConfigContent);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Configure prisma/seed.ts imports
|
|
423
|
+
const prismaSeedPath = path.join(projectDir, 'prisma', 'seed.ts');
|
|
424
|
+
if (fs.existsSync(prismaSeedPath)) {
|
|
425
|
+
console.log('🔧 Configuring prisma/seed.ts...');
|
|
426
|
+
let seedContent = fs.readFileSync(prismaSeedPath, 'utf8');
|
|
427
|
+
|
|
428
|
+
// Add dotenv config if missing
|
|
429
|
+
if (!seedContent.includes('dotenv.config()')) {
|
|
430
|
+
seedContent = 'import dotenv from "dotenv";\ndotenv.config();\n\n' + seedContent;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Update import path
|
|
434
|
+
seedContent = seedContent.replace(
|
|
435
|
+
/import\s+{\s*prisma\s*}\s+from\s+["']@lapeh\/core\/database["']/,
|
|
436
|
+
'import { prisma } from "@lapeh/core/database"'
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
fs.writeFileSync(prismaSeedPath, seedContent);
|
|
440
|
+
}
|
|
441
|
+
|
|
318
442
|
// Create .env from .env.example with correct DB config
|
|
319
443
|
console.log('⚙️ Configuring environment...');
|
|
320
444
|
const envExamplePath = path.join(projectDir, '.env.example');
|
package/lib/bootstrap.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lapeh",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.7",
|
|
4
4
|
"description": "Framework API Express yang siap pakai (Standardized)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -87,7 +87,8 @@
|
|
|
87
87
|
"uuid": "13.0.0",
|
|
88
88
|
"winston": "^3.19.0",
|
|
89
89
|
"winston-daily-rotate-file": "^5.0.0",
|
|
90
|
-
"zod": "3.23.8"
|
|
90
|
+
"zod": "3.23.8",
|
|
91
|
+
"prisma": "7.2.0"
|
|
91
92
|
},
|
|
92
93
|
"devDependencies": {
|
|
93
94
|
"@eslint/js": "^9.39.2",
|
package/prisma/seed.ts
CHANGED
package/prisma.config.ts
CHANGED