lapeh 1.0.4 ā 1.0.6
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 -1
- package/bin/index.js +196 -71
- package/package.json +30 -30
- package/prisma/base.prisma +1 -1
- package/prisma/schema.prisma +3 -1
- package/readme.md +10 -2
- package/scripts/check-update.js +3 -1
- package/scripts/init-project.js +166 -71
package/.env.example
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
PORT=4000
|
|
2
2
|
DATABASE_PROVIDER="postgresql"
|
|
3
|
-
DATABASE_URL="postgresql://roby:12341234@localhost:5432/
|
|
3
|
+
DATABASE_URL="postgresql://roby:12341234@localhost:5432/db_example_test?schema=public"
|
|
4
4
|
JWT_SECRET="replace_this_with_a_secure_random_string"
|
|
5
5
|
|
|
6
6
|
# redis example:
|
package/bin/index.js
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { execSync } = require('child_process');
|
|
6
|
+
const readline = require('readline');
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const projectName = args.find(arg => !arg.startsWith('-'));
|
|
10
|
+
const isFull = args.includes('--full');
|
|
8
11
|
|
|
9
12
|
if (!projectName) {
|
|
10
13
|
console.error('ā Please specify the project name:');
|
|
11
|
-
console.error(' npx lapeh-cli <project-name>');
|
|
14
|
+
console.error(' npx lapeh-cli <project-name> [--full]');
|
|
12
15
|
process.exit(1);
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -21,85 +24,207 @@ if (fs.existsSync(projectDir)) {
|
|
|
21
24
|
process.exit(1);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
27
|
+
// Setup readline interface
|
|
28
|
+
const rl = readline.createInterface({
|
|
29
|
+
input: process.stdin,
|
|
30
|
+
output: process.stdout,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const ask = (query, defaultVal) => {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
rl.question(`${query} ${defaultVal ? `[${defaultVal}]` : ""}: `, (answer) => {
|
|
36
|
+
resolve(answer.trim() || defaultVal);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const selectOption = async (query, options) => {
|
|
42
|
+
console.log(query);
|
|
43
|
+
options.forEach((opt, idx) => {
|
|
44
|
+
console.log(` [${opt.key}] ${opt.label}`);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
while (true) {
|
|
48
|
+
const answer = await ask(">", options[0].key);
|
|
49
|
+
const selected = options.find(o => o.key.toLowerCase() === answer.toLowerCase());
|
|
50
|
+
if (selected) return selected;
|
|
51
|
+
|
|
52
|
+
const byLabel = options.find(o => o.label.toLowerCase().includes(answer.toLowerCase()));
|
|
53
|
+
if (byLabel) return byLabel;
|
|
54
|
+
|
|
55
|
+
console.log("Pilihan tidak valid. Silakan coba lagi.");
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
(async () => {
|
|
60
|
+
console.log(`š Creating a new API Lapeh project in ${projectDir}...`);
|
|
61
|
+
fs.mkdirSync(projectDir);
|
|
62
|
+
|
|
63
|
+
// --- DATABASE SELECTION ---
|
|
64
|
+
console.log("\n--- Database Configuration ---");
|
|
65
|
+
const dbType = await selectOption("Database apa yang akan digunakan?", [
|
|
66
|
+
{ key: "pgsql", label: "PostgreSQL", provider: "postgresql", defaultPort: "5432" },
|
|
67
|
+
{ key: "mysql", label: "MySQL", provider: "mysql", defaultPort: "3306" },
|
|
68
|
+
{ key: "mariadb", label: "MariaDB", provider: "mysql", defaultPort: "3306" },
|
|
69
|
+
{ key: "sqlite", label: "SQLite", provider: "sqlite", defaultPort: "" },
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
let dbUrl = "";
|
|
73
|
+
let dbProvider = dbType.provider;
|
|
74
|
+
|
|
75
|
+
if (dbType.key === "sqlite") {
|
|
76
|
+
dbUrl = "file:./dev.db";
|
|
77
|
+
} else {
|
|
78
|
+
const host = await ask("Database Host", "localhost");
|
|
79
|
+
const port = await ask("Database Port", dbType.defaultPort);
|
|
80
|
+
const user = await ask("Database User", "root");
|
|
81
|
+
const password = await ask("Database Password", "");
|
|
82
|
+
const dbName = await ask("Database Name", projectName.replace(/-/g, '_')); // Default db name based on project name
|
|
83
|
+
|
|
84
|
+
if (dbType.key === "pgsql") {
|
|
85
|
+
dbUrl = `postgresql://${user}:${password}@${host}:${port}/${dbName}?schema=public`;
|
|
86
|
+
} else {
|
|
87
|
+
dbUrl = `mysql://${user}:${password}@${host}:${port}/${dbName}`;
|
|
48
88
|
}
|
|
89
|
+
}
|
|
49
90
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
91
|
+
rl.close();
|
|
92
|
+
|
|
93
|
+
// List of files/folders to exclude
|
|
94
|
+
const ignoreList = [
|
|
95
|
+
'node_modules',
|
|
96
|
+
'dist',
|
|
97
|
+
'.git',
|
|
98
|
+
'.env',
|
|
99
|
+
'bin', // Don't copy the CLI script itself
|
|
100
|
+
'package-lock.json',
|
|
101
|
+
'.DS_Store',
|
|
102
|
+
projectName // Don't copy the destination folder itself if creating inside the template
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
function copyDir(src, dest) {
|
|
106
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
107
|
+
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const srcPath = path.join(src, entry.name);
|
|
110
|
+
const destPath = path.join(dest, entry.name);
|
|
111
|
+
|
|
112
|
+
if (ignoreList.includes(entry.name)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (entry.isDirectory()) {
|
|
117
|
+
fs.mkdirSync(destPath);
|
|
118
|
+
copyDir(srcPath, destPath);
|
|
119
|
+
} else {
|
|
120
|
+
fs.copyFileSync(srcPath, destPath);
|
|
121
|
+
}
|
|
55
122
|
}
|
|
56
123
|
}
|
|
57
|
-
}
|
|
58
124
|
|
|
59
|
-
console.log('š Copying template files...');
|
|
60
|
-
copyDir(templateDir, projectDir);
|
|
125
|
+
console.log('\nš Copying template files...');
|
|
126
|
+
copyDir(templateDir, projectDir);
|
|
127
|
+
|
|
128
|
+
// Update package.json
|
|
129
|
+
console.log('š Updating package.json...');
|
|
130
|
+
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
131
|
+
const packageJson = require(packageJsonPath);
|
|
132
|
+
|
|
133
|
+
packageJson.name = projectName;
|
|
134
|
+
// Add lapeh framework version to dependencies to track it like react-router
|
|
135
|
+
packageJson.dependencies = packageJson.dependencies || {};
|
|
136
|
+
packageJson.dependencies["lapeh"] = packageJson.version;
|
|
137
|
+
|
|
138
|
+
packageJson.version = '1.0.0';
|
|
139
|
+
packageJson.description = 'Generated by lapeh';
|
|
140
|
+
delete packageJson.bin; // Remove the bin entry from the generated project
|
|
141
|
+
delete packageJson.repository; // Remove repository info if specific to the template
|
|
142
|
+
|
|
143
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
144
|
+
|
|
145
|
+
// Create .env from .env.example with correct DB config
|
|
146
|
+
console.log('āļø Configuring environment...');
|
|
147
|
+
const envExamplePath = path.join(projectDir, '.env.example');
|
|
148
|
+
const envPath = path.join(projectDir, '.env');
|
|
149
|
+
const prismaBaseFile = path.join(projectDir, "prisma", "base.prisma");
|
|
150
|
+
|
|
151
|
+
if (fs.existsSync(envExamplePath)) {
|
|
152
|
+
let envContent = fs.readFileSync(envExamplePath, 'utf8');
|
|
153
|
+
|
|
154
|
+
// Replace DATABASE_URL and DATABASE_PROVIDER
|
|
155
|
+
if (envContent.includes("DATABASE_URL=")) {
|
|
156
|
+
envContent = envContent.replace(/DATABASE_URL=".+"/g, `DATABASE_URL="${dbUrl}"`);
|
|
157
|
+
envContent = envContent.replace(/DATABASE_URL=.+/g, `DATABASE_URL="${dbUrl}"`);
|
|
158
|
+
} else {
|
|
159
|
+
envContent += `\nDATABASE_URL="${dbUrl}"`;
|
|
160
|
+
}
|
|
61
161
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
162
|
+
if (envContent.includes("DATABASE_PROVIDER=")) {
|
|
163
|
+
envContent = envContent.replace(/DATABASE_PROVIDER=".+"/g, `DATABASE_PROVIDER="${dbProvider}"`);
|
|
164
|
+
envContent = envContent.replace(/DATABASE_PROVIDER=.+/g, `DATABASE_PROVIDER="${dbProvider}"`);
|
|
165
|
+
} else {
|
|
166
|
+
envContent += `\nDATABASE_PROVIDER="${dbProvider}"`;
|
|
167
|
+
}
|
|
66
168
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
packageJson.description = 'Generated by lapeh';
|
|
70
|
-
delete packageJson.bin; // Remove the bin entry from the generated project
|
|
71
|
-
delete packageJson.repository; // Remove repository info if specific to the template
|
|
169
|
+
fs.writeFileSync(envPath, envContent);
|
|
170
|
+
}
|
|
72
171
|
|
|
73
|
-
|
|
172
|
+
// Update prisma/base.prisma
|
|
173
|
+
console.log("š Updating prisma/base.prisma...");
|
|
174
|
+
if (fs.existsSync(prismaBaseFile)) {
|
|
175
|
+
let baseContent = fs.readFileSync(prismaBaseFile, "utf8");
|
|
176
|
+
// Replace provider
|
|
177
|
+
baseContent = baseContent.replace(/provider\s*=\s*".*"/, `provider = "${dbProvider}"`);
|
|
178
|
+
fs.writeFileSync(prismaBaseFile, baseContent);
|
|
179
|
+
}
|
|
74
180
|
|
|
75
|
-
//
|
|
76
|
-
console.log('
|
|
77
|
-
|
|
78
|
-
|
|
181
|
+
// Install dependencies
|
|
182
|
+
console.log('š¦ Installing dependencies (this might take a while)...');
|
|
183
|
+
try {
|
|
184
|
+
execSync('npm install', { cwd: projectDir, stdio: 'inherit' });
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('ā Error installing dependencies.');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
79
189
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
190
|
+
// Generate JWT Secret
|
|
191
|
+
console.log('š Generating JWT Secret...');
|
|
192
|
+
try {
|
|
193
|
+
execSync('npm run generate:jwt', { cwd: projectDir, stdio: 'inherit' });
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.warn('ā ļø Could not generate JWT secret automatically.');
|
|
196
|
+
}
|
|
83
197
|
|
|
84
|
-
//
|
|
85
|
-
console.log('
|
|
86
|
-
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
198
|
+
// Generate Prisma Client & Migrate
|
|
199
|
+
console.log('šļø Setting up database...');
|
|
200
|
+
try {
|
|
201
|
+
console.log(' Compiling schema...');
|
|
202
|
+
execSync('node scripts/compile-schema.js', { cwd: projectDir, stdio: 'inherit' });
|
|
203
|
+
|
|
204
|
+
console.log(' Generating Prisma Client...');
|
|
205
|
+
execSync('npx prisma generate', { cwd: projectDir, stdio: 'inherit' });
|
|
206
|
+
|
|
207
|
+
// Try to migrate (this will create the DB if it doesn't exist)
|
|
208
|
+
console.log(' Running migration (creates DB if missing)...');
|
|
209
|
+
execSync('npx prisma migrate dev --name init_setup', { cwd: projectDir, stdio: 'inherit' });
|
|
210
|
+
|
|
211
|
+
// Seed
|
|
212
|
+
if (isFull) {
|
|
213
|
+
console.log(' Seeding database...');
|
|
214
|
+
execSync('npm run db:seed', { cwd: projectDir, stdio: 'inherit' });
|
|
215
|
+
} else {
|
|
216
|
+
console.log(' ā¹ļø Skipping database seeding (use --full to seed default data)...');
|
|
217
|
+
}
|
|
92
218
|
|
|
93
|
-
|
|
94
|
-
console.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.warn('ā ļø Database setup encountered an issue.');
|
|
221
|
+
console.warn(' You may need to check your .env credentials and run:');
|
|
222
|
+
console.warn(' cd ' + projectName);
|
|
223
|
+
console.warn(' npm run prisma:migrate');
|
|
224
|
+
}
|
|
100
225
|
|
|
101
|
-
console.log(
|
|
102
|
-
console.log(`\nNext steps
|
|
103
|
-
console.log(` cd ${projectName}`);
|
|
104
|
-
console.log(` npm run dev`);
|
|
105
|
-
|
|
226
|
+
console.log(`\nā
Project ${projectName} created successfully!`);
|
|
227
|
+
console.log(`\nNext steps:`);
|
|
228
|
+
console.log(` cd ${projectName}`);
|
|
229
|
+
console.log(` npm run dev`);
|
|
230
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lapeh",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Framework API Express yang siap pakai (Standardized)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -38,36 +38,36 @@
|
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"type": "commonjs",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@prisma/adapter-mariadb": "
|
|
42
|
-
"@prisma/adapter-pg": "
|
|
43
|
-
"@prisma/client": "
|
|
44
|
-
"bcryptjs": "
|
|
45
|
-
"cors": "
|
|
46
|
-
"dotenv": "
|
|
47
|
-
"express": "
|
|
48
|
-
"express-rate-limit": "
|
|
49
|
-
"helmet": "
|
|
50
|
-
"ioredis": "
|
|
51
|
-
"jsonwebtoken": "
|
|
52
|
-
"multer": "
|
|
53
|
-
"pg": "
|
|
54
|
-
"slugify": "
|
|
55
|
-
"socket.io": "
|
|
56
|
-
"uuid": "
|
|
57
|
-
"zod": "
|
|
41
|
+
"@prisma/adapter-mariadb": "7.2.0",
|
|
42
|
+
"@prisma/adapter-pg": "7.2.0",
|
|
43
|
+
"@prisma/client": "7.2.0",
|
|
44
|
+
"bcryptjs": "3.0.3",
|
|
45
|
+
"cors": "2.8.5",
|
|
46
|
+
"dotenv": "17.2.3",
|
|
47
|
+
"express": "5.2.1",
|
|
48
|
+
"express-rate-limit": "8.2.1",
|
|
49
|
+
"helmet": "8.1.0",
|
|
50
|
+
"ioredis": "5.8.2",
|
|
51
|
+
"jsonwebtoken": "9.0.3",
|
|
52
|
+
"multer": "2.0.2",
|
|
53
|
+
"pg": "8.16.3",
|
|
54
|
+
"slugify": "1.6.6",
|
|
55
|
+
"socket.io": "4.8.3",
|
|
56
|
+
"uuid": "13.0.0",
|
|
57
|
+
"zod": "3.23.8"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@types/bcryptjs": "
|
|
61
|
-
"@types/cors": "
|
|
62
|
-
"@types/express": "
|
|
63
|
-
"@types/jsonwebtoken": "
|
|
64
|
-
"@types/node": "
|
|
65
|
-
"@types/pg": "
|
|
66
|
-
"@types/uuid": "
|
|
67
|
-
"nodemon": "
|
|
68
|
-
"prisma": "
|
|
69
|
-
"ts-node": "
|
|
70
|
-
"ts-node-dev": "
|
|
71
|
-
"typescript": "
|
|
60
|
+
"@types/bcryptjs": "2.4.6",
|
|
61
|
+
"@types/cors": "2.8.19",
|
|
62
|
+
"@types/express": "5.0.6",
|
|
63
|
+
"@types/jsonwebtoken": "9.0.10",
|
|
64
|
+
"@types/node": "25.0.3",
|
|
65
|
+
"@types/pg": "8.16.0",
|
|
66
|
+
"@types/uuid": "10.0.0",
|
|
67
|
+
"nodemon": "3.1.11",
|
|
68
|
+
"prisma": "7.2.0",
|
|
69
|
+
"ts-node": "10.9.2",
|
|
70
|
+
"ts-node-dev": "2.0.0",
|
|
71
|
+
"typescript": "5.9.3"
|
|
72
72
|
}
|
|
73
73
|
}
|
package/prisma/base.prisma
CHANGED
package/prisma/schema.prisma
CHANGED
package/readme.md
CHANGED
|
@@ -20,12 +20,20 @@ Buat project baru cukup dengan satu perintah:
|
|
|
20
20
|
npx lapeh nama-project-anda
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
Atau gunakan flag `--full` untuk setup lengkap (termasuk seeding data default user & roles):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx lapeh nama-project-anda --full
|
|
27
|
+
```
|
|
28
|
+
|
|
23
29
|
### Apa yang terjadi otomatis?
|
|
24
30
|
|
|
25
31
|
1. Struktur project dibuat.
|
|
26
32
|
2. Dependencies diinstall.
|
|
27
|
-
3.
|
|
28
|
-
4. **
|
|
33
|
+
3. Database dipilih & dikonfigurasi secara interaktif.
|
|
34
|
+
4. **Database** dibuat dan dimigrasi otomatis.
|
|
35
|
+
5. **JWT Secret** di-generate otomatis.
|
|
36
|
+
6. **Seeding Data** (jika menggunakan `--full`).
|
|
29
37
|
|
|
30
38
|
Masuk ke folder project dan jalankan:
|
|
31
39
|
|
package/scripts/check-update.js
CHANGED
|
@@ -10,7 +10,9 @@ const REPO_VERSION_URL = 'https://registry.npmjs.org/lapeh/latest';
|
|
|
10
10
|
const TIMEOUT = 2000; // Timeout 2 detik agar tidak terlalu lama menunggu
|
|
11
11
|
|
|
12
12
|
const packageJson = require('../package.json');
|
|
13
|
-
|
|
13
|
+
// Cek apakah ada key "lapeh" di dependencies (project user)
|
|
14
|
+
// Jika tidak ada, fallback ke version package.json (mungkin ini repo framework itu sendiri)
|
|
15
|
+
const currentVersion = packageJson.dependencies?.['lapeh'] || packageJson.version;
|
|
14
16
|
|
|
15
17
|
function checkForUpdates() {
|
|
16
18
|
if (!REPO_VERSION_URL) return;
|
package/scripts/init-project.js
CHANGED
|
@@ -1,71 +1,166 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const { execSync } = require("child_process");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
console.log(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { execSync } = require("child_process");
|
|
4
|
+
const readline = require("readline");
|
|
5
|
+
|
|
6
|
+
const rootDir = path.join(__dirname, "..");
|
|
7
|
+
const envExample = path.join(rootDir, ".env.example");
|
|
8
|
+
const envFile = path.join(rootDir, ".env");
|
|
9
|
+
const prismaBaseFile = path.join(rootDir, "prisma", "base.prisma");
|
|
10
|
+
|
|
11
|
+
const rl = readline.createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stdout,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const ask = (query, defaultVal) => {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
rl.question(`${query} ${defaultVal ? `[${defaultVal}]` : ""}: `, (answer) => {
|
|
19
|
+
resolve(answer.trim() || defaultVal);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const selectOption = async (query, options) => {
|
|
25
|
+
console.log(query);
|
|
26
|
+
options.forEach((opt, idx) => {
|
|
27
|
+
console.log(` [${opt.key}] ${opt.label}`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
while (true) {
|
|
31
|
+
const answer = await ask(">", options[0].key); // Default to first option
|
|
32
|
+
const selected = options.find(o => o.key.toLowerCase() === answer.toLowerCase());
|
|
33
|
+
if (selected) return selected;
|
|
34
|
+
|
|
35
|
+
// Check if user entered the full name or label
|
|
36
|
+
const byLabel = options.find(o => o.label.toLowerCase().includes(answer.toLowerCase()));
|
|
37
|
+
if (byLabel) return byLabel;
|
|
38
|
+
|
|
39
|
+
console.log("Pilihan tidak valid. Silakan coba lagi.");
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
(async () => {
|
|
44
|
+
console.log("š Starting project initialization...");
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// --- DATABASE SELECTION ---
|
|
48
|
+
console.log("\n--- Database Configuration ---");
|
|
49
|
+
const dbType = await selectOption("Database apa yang akan digunakan?", [
|
|
50
|
+
{ key: "pgsql", label: "PostgreSQL", provider: "postgresql", defaultPort: "5432" },
|
|
51
|
+
{ key: "mysql", label: "MySQL", provider: "mysql", defaultPort: "3306" },
|
|
52
|
+
{ key: "mariadb", label: "MariaDB", provider: "mysql", defaultPort: "3306" },
|
|
53
|
+
{ key: "sqlite", label: "SQLite", provider: "sqlite", defaultPort: "" },
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
let dbUrl = "";
|
|
57
|
+
let dbProvider = dbType.provider;
|
|
58
|
+
|
|
59
|
+
if (dbType.key === "sqlite") {
|
|
60
|
+
dbUrl = "file:./dev.db";
|
|
61
|
+
} else {
|
|
62
|
+
const host = await ask("Database Host", "localhost");
|
|
63
|
+
const port = await ask("Database Port", dbType.defaultPort);
|
|
64
|
+
const user = await ask("Database User", "root");
|
|
65
|
+
const password = await ask("Database Password", "");
|
|
66
|
+
const dbName = await ask("Database Name", "lapeh");
|
|
67
|
+
|
|
68
|
+
if (dbType.key === "pgsql") {
|
|
69
|
+
dbUrl = `postgresql://${user}:${password}@${host}:${port}/${dbName}?schema=public`;
|
|
70
|
+
} else {
|
|
71
|
+
dbUrl = `mysql://${user}:${password}@${host}:${port}/${dbName}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Close readline as we are done with input
|
|
76
|
+
rl.close();
|
|
77
|
+
|
|
78
|
+
// 1. Setup .env
|
|
79
|
+
console.log("\nš Setting up .env...");
|
|
80
|
+
let envContent = "";
|
|
81
|
+
if (fs.existsSync(envExample)) {
|
|
82
|
+
envContent = fs.readFileSync(envExample, "utf8");
|
|
83
|
+
} else {
|
|
84
|
+
// Fallback minimal env if example missing
|
|
85
|
+
envContent = `PORT=4000\nDATABASE_PROVIDER="postgresql"\nDATABASE_URL=""\nJWT_SECRET="replace_this"\n`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Replace DATABASE_URL and DATABASE_PROVIDER
|
|
89
|
+
// Regex to replace existing values or append if missing (simplified)
|
|
90
|
+
if (envContent.includes("DATABASE_URL=")) {
|
|
91
|
+
envContent = envContent.replace(/DATABASE_URL=".+"/g, `DATABASE_URL="${dbUrl}"`);
|
|
92
|
+
envContent = envContent.replace(/DATABASE_URL=.+/g, `DATABASE_URL="${dbUrl}"`); // Handle unquoted
|
|
93
|
+
} else {
|
|
94
|
+
envContent += `\nDATABASE_URL="${dbUrl}"`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (envContent.includes("DATABASE_PROVIDER=")) {
|
|
98
|
+
envContent = envContent.replace(/DATABASE_PROVIDER=".+"/g, `DATABASE_PROVIDER="${dbProvider}"`);
|
|
99
|
+
envContent = envContent.replace(/DATABASE_PROVIDER=.+/g, `DATABASE_PROVIDER="${dbProvider}"`);
|
|
100
|
+
} else {
|
|
101
|
+
envContent += `\nDATABASE_PROVIDER="${dbProvider}"`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fs.writeFileSync(envFile, envContent);
|
|
105
|
+
console.log("ā
.env updated with database configuration.");
|
|
106
|
+
|
|
107
|
+
// 2. Update prisma/base.prisma
|
|
108
|
+
console.log("š Updating prisma/base.prisma...");
|
|
109
|
+
if (fs.existsSync(prismaBaseFile)) {
|
|
110
|
+
let baseContent = fs.readFileSync(prismaBaseFile, "utf8");
|
|
111
|
+
// Replace provider
|
|
112
|
+
baseContent = baseContent.replace(/provider\s*=\s*".*"/, `provider = "${dbProvider}"`);
|
|
113
|
+
fs.writeFileSync(prismaBaseFile, baseContent);
|
|
114
|
+
} else {
|
|
115
|
+
console.warn("ā ļø prisma/base.prisma not found. Skipping.");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 3. Install dependencies
|
|
119
|
+
console.log("\nš¦ Installing dependencies...");
|
|
120
|
+
execSync("npm install", { stdio: "inherit", cwd: rootDir });
|
|
121
|
+
|
|
122
|
+
// 4. Generate JWT Secret
|
|
123
|
+
console.log("\nš Generating JWT Secret...");
|
|
124
|
+
try {
|
|
125
|
+
execSync("node scripts/generate-jwt-secret.js", {
|
|
126
|
+
stdio: "inherit",
|
|
127
|
+
cwd: rootDir,
|
|
128
|
+
});
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.warn("ā ļø Failed to generate JWT secret automatically.");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 5. Setup Database (Migrate)
|
|
134
|
+
console.log("\nšļø Setting up database...");
|
|
135
|
+
try {
|
|
136
|
+
execSync("node scripts/compile-schema.js", { stdio: "inherit", cwd: rootDir });
|
|
137
|
+
console.log("āļø Generating Prisma Client...");
|
|
138
|
+
execSync("npx prisma generate", { stdio: "inherit", cwd: rootDir });
|
|
139
|
+
|
|
140
|
+
console.log("š Running Migration...");
|
|
141
|
+
execSync("npx prisma migrate dev --name init_setup", { stdio: "inherit", cwd: rootDir });
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.warn(
|
|
144
|
+
'ā ļø Database migration had an issue. Please check your database connection in .env and run "npm run prisma:migrate" manually.'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 6. Seed Database
|
|
149
|
+
console.log("\nš± Seeding database...");
|
|
150
|
+
try {
|
|
151
|
+
execSync("npm run db:seed", { stdio: "inherit", cwd: rootDir });
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.warn(
|
|
154
|
+
'ā ļø Database seeding had an issue. You might need to run "npm run db:seed" manually.'
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log("\nā
Setup complete! You can now run:");
|
|
159
|
+
console.log(" npm run dev");
|
|
160
|
+
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error("\nā Setup failed:", error.message);
|
|
163
|
+
rl.close();
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
})();
|