lapeh 1.0.5 ā 1.0.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/.env.example +1 -1
- package/bin/index.js +199 -75
- package/package.json +1 -1
- package/prisma/base.prisma +1 -1
- package/readme.md +10 -2
- package/scripts/init-project.js +5 -2
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,89 +24,210 @@ 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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
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`;
|
|
53
86
|
} else {
|
|
54
|
-
|
|
87
|
+
dbUrl = `mysql://${user}:${password}@${host}:${port}/${dbName}`;
|
|
55
88
|
}
|
|
56
89
|
}
|
|
57
|
-
}
|
|
58
90
|
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
61
124
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
}
|
|
66
161
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
+
}
|
|
71
168
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
delete packageJson.bin; // Remove the bin entry from the generated project
|
|
75
|
-
delete packageJson.repository; // Remove repository info if specific to the template
|
|
169
|
+
fs.writeFileSync(envPath, envContent);
|
|
170
|
+
}
|
|
76
171
|
|
|
77
|
-
|
|
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 in datasource block
|
|
177
|
+
baseContent = baseContent.replace(
|
|
178
|
+
/(datasource\s+db\s+\{[\s\S]*?provider\s*=\s*")([^"]+)(")/,
|
|
179
|
+
`$1${dbProvider}$3`
|
|
180
|
+
);
|
|
181
|
+
fs.writeFileSync(prismaBaseFile, baseContent);
|
|
182
|
+
}
|
|
78
183
|
|
|
79
|
-
//
|
|
80
|
-
console.log('
|
|
81
|
-
|
|
82
|
-
|
|
184
|
+
// Install dependencies
|
|
185
|
+
console.log('š¦ Installing dependencies (this might take a while)...');
|
|
186
|
+
try {
|
|
187
|
+
execSync('npm install', { cwd: projectDir, stdio: 'inherit' });
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error('ā Error installing dependencies.');
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
83
192
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
193
|
+
// Generate JWT Secret
|
|
194
|
+
console.log('š Generating JWT Secret...');
|
|
195
|
+
try {
|
|
196
|
+
execSync('npm run generate:jwt', { cwd: projectDir, stdio: 'inherit' });
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.warn('ā ļø Could not generate JWT secret automatically.');
|
|
199
|
+
}
|
|
87
200
|
|
|
88
|
-
//
|
|
89
|
-
console.log('
|
|
90
|
-
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
201
|
+
// Generate Prisma Client & Migrate
|
|
202
|
+
console.log('šļø Setting up database...');
|
|
203
|
+
try {
|
|
204
|
+
console.log(' Compiling schema...');
|
|
205
|
+
execSync('node scripts/compile-schema.js', { cwd: projectDir, stdio: 'inherit' });
|
|
206
|
+
|
|
207
|
+
console.log(' Generating Prisma Client...');
|
|
208
|
+
execSync('npx prisma generate', { cwd: projectDir, stdio: 'inherit' });
|
|
209
|
+
|
|
210
|
+
// Try to migrate (this will create the DB if it doesn't exist)
|
|
211
|
+
console.log(' Running migration (creates DB if missing)...');
|
|
212
|
+
execSync('npx prisma migrate dev --name init_setup', { cwd: projectDir, stdio: 'inherit' });
|
|
213
|
+
|
|
214
|
+
// Seed
|
|
215
|
+
if (isFull) {
|
|
216
|
+
console.log(' Seeding database...');
|
|
217
|
+
execSync('npm run db:seed', { cwd: projectDir, stdio: 'inherit' });
|
|
218
|
+
} else {
|
|
219
|
+
console.log(' ā¹ļø Skipping database seeding (use --full to seed default data)...');
|
|
220
|
+
}
|
|
96
221
|
|
|
97
|
-
|
|
98
|
-
console.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.warn('ā ļø Database setup encountered an issue.');
|
|
224
|
+
console.warn(' You may need to check your .env credentials and run:');
|
|
225
|
+
console.warn(' cd ' + projectName);
|
|
226
|
+
console.warn(' npm run prisma:migrate');
|
|
227
|
+
}
|
|
104
228
|
|
|
105
|
-
console.log(
|
|
106
|
-
console.log(`\nNext steps
|
|
107
|
-
console.log(` cd ${projectName}`);
|
|
108
|
-
console.log(` npm run dev`);
|
|
109
|
-
|
|
229
|
+
console.log(`\nā
Project ${projectName} created successfully!`);
|
|
230
|
+
console.log(`\nNext steps:`);
|
|
231
|
+
console.log(` cd ${projectName}`);
|
|
232
|
+
console.log(` npm run dev`);
|
|
233
|
+
})();
|
package/package.json
CHANGED
package/prisma/base.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/init-project.js
CHANGED
|
@@ -108,8 +108,11 @@ const selectOption = async (query, options) => {
|
|
|
108
108
|
console.log("š Updating prisma/base.prisma...");
|
|
109
109
|
if (fs.existsSync(prismaBaseFile)) {
|
|
110
110
|
let baseContent = fs.readFileSync(prismaBaseFile, "utf8");
|
|
111
|
-
// Replace provider
|
|
112
|
-
baseContent = baseContent.replace(
|
|
111
|
+
// Replace provider in datasource block
|
|
112
|
+
baseContent = baseContent.replace(
|
|
113
|
+
/(datasource\s+db\s+\{[\s\S]*?provider\s*=\s*")([^"]+)(")/,
|
|
114
|
+
`$1${dbProvider}$3`
|
|
115
|
+
);
|
|
113
116
|
fs.writeFileSync(prismaBaseFile, baseContent);
|
|
114
117
|
} else {
|
|
115
118
|
console.warn("ā ļø prisma/base.prisma not found. Skipping.");
|