lapeh 2.0.1 → 2.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/bin/index.js +292 -190
- package/package.json +1 -1
- package/readme.md +19 -0
package/bin/index.js
CHANGED
|
@@ -6,228 +6,330 @@ const { execSync } = require('child_process');
|
|
|
6
6
|
const readline = require('readline');
|
|
7
7
|
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
|
-
const projectName = args.find(arg => !arg.startsWith('-'));
|
|
10
|
-
const isFull = args.includes('--full');
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
// --- UPGRADE MODE ---
|
|
11
|
+
if (args[0] === 'upgrade') {
|
|
12
|
+
(async () => {
|
|
13
|
+
await upgradeProject();
|
|
14
|
+
})();
|
|
15
|
+
} else {
|
|
16
|
+
// --- CREATE MODE ---
|
|
17
|
+
createProject();
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const templateDir = path.join(__dirname, '..');
|
|
21
|
-
|
|
22
|
-
if (fs.existsSync(projectDir)) {
|
|
23
|
-
console.error(`❌ Directory ${projectName} already exists.`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
|
|
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
|
-
});
|
|
20
|
+
async function upgradeProject() {
|
|
21
|
+
const currentDir = process.cwd();
|
|
22
|
+
const templateDir = path.join(__dirname, '..');
|
|
46
23
|
|
|
47
|
-
|
|
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;
|
|
24
|
+
console.log(`🚀 Upgrading Lapeh project in ${currentDir}...`);
|
|
54
25
|
|
|
55
|
-
|
|
26
|
+
// Check if package.json exists
|
|
27
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
28
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
29
|
+
console.error('❌ No package.json found. Are you in the root of a Lapeh project?');
|
|
30
|
+
process.exit(1);
|
|
56
31
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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`;
|
|
32
|
+
|
|
33
|
+
// Files/Folders to overwrite/copy
|
|
34
|
+
const filesToSync = [
|
|
35
|
+
'scripts',
|
|
36
|
+
'docker-compose.yml',
|
|
37
|
+
'.env.example',
|
|
38
|
+
'.vscode',
|
|
39
|
+
'tsconfig.json',
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Helper to copy recursive
|
|
43
|
+
function copyRecursive(src, dest) {
|
|
44
|
+
if (!fs.existsSync(src)) return;
|
|
45
|
+
const stats = fs.statSync(src);
|
|
46
|
+
if (stats.isDirectory()) {
|
|
47
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest);
|
|
48
|
+
fs.readdirSync(src).forEach(childItemName => {
|
|
49
|
+
copyRecursive(path.join(src, childItemName), path.join(dest, childItemName));
|
|
50
|
+
});
|
|
86
51
|
} else {
|
|
87
|
-
|
|
52
|
+
fs.copyFileSync(src, dest);
|
|
88
53
|
}
|
|
89
54
|
}
|
|
90
55
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
'package-lock.json',
|
|
101
|
-
'.DS_Store',
|
|
102
|
-
projectName // Don't copy the destination folder itself if creating inside the template
|
|
103
|
-
];
|
|
56
|
+
for (const item of filesToSync) {
|
|
57
|
+
const srcPath = path.join(templateDir, item);
|
|
58
|
+
const destPath = path.join(currentDir, item);
|
|
59
|
+
|
|
60
|
+
if (fs.existsSync(srcPath)) {
|
|
61
|
+
console.log(`🔄 Updating ${item}...`);
|
|
62
|
+
copyRecursive(srcPath, destPath);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
104
65
|
|
|
105
|
-
|
|
106
|
-
|
|
66
|
+
// Merge package.json
|
|
67
|
+
console.log('📝 Updating package.json...');
|
|
68
|
+
const currentPackageJson = require(packageJsonPath);
|
|
69
|
+
const templatePackageJson = require(path.join(templateDir, 'package.json'));
|
|
70
|
+
|
|
71
|
+
// Update scripts
|
|
72
|
+
currentPackageJson.scripts = {
|
|
73
|
+
...currentPackageJson.scripts,
|
|
74
|
+
...templatePackageJson.scripts
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Update dependencies
|
|
78
|
+
currentPackageJson.dependencies = {
|
|
79
|
+
...currentPackageJson.dependencies,
|
|
80
|
+
...templatePackageJson.dependencies
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Update devDependencies
|
|
84
|
+
currentPackageJson.devDependencies = {
|
|
85
|
+
...currentPackageJson.devDependencies,
|
|
86
|
+
...templatePackageJson.devDependencies
|
|
87
|
+
};
|
|
107
88
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const destPath = path.join(dest, entry.name);
|
|
89
|
+
// Update Lapeh version tag
|
|
90
|
+
currentPackageJson.dependencies["lapeh"] = templatePackageJson.version;
|
|
111
91
|
|
|
112
|
-
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
92
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(currentPackageJson, null, 2));
|
|
115
93
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
94
|
+
// Run npm install
|
|
95
|
+
console.log('📦 Installing updated dependencies...');
|
|
96
|
+
try {
|
|
97
|
+
execSync('npm install', { cwd: currentDir, stdio: 'inherit' });
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('❌ Error installing dependencies.');
|
|
100
|
+
process.exit(1);
|
|
123
101
|
}
|
|
124
102
|
|
|
125
|
-
console.log('\n
|
|
126
|
-
|
|
103
|
+
console.log('\n✅ Upgrade completed successfully!');
|
|
104
|
+
console.log(' Please check your .env file against .env.example for any new required variables.');
|
|
105
|
+
}
|
|
127
106
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
const packageJson = require(packageJsonPath);
|
|
107
|
+
function createProject() {
|
|
108
|
+
const projectName = args.find(arg => !arg.startsWith('-'));
|
|
109
|
+
const isFull = args.includes('--full');
|
|
132
110
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
delete packageJson.bin; // Remove the bin entry from the generated project
|
|
141
|
-
delete packageJson.repository; // Remove repository info if specific to the template
|
|
111
|
+
if (!projectName) {
|
|
112
|
+
console.error('❌ Please specify the project name:');
|
|
113
|
+
console.error(' npx lapeh-cli <project-name> [--full]');
|
|
114
|
+
console.error(' OR');
|
|
115
|
+
console.error(' npx lapeh-cli upgrade (inside existing project)');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
142
118
|
|
|
143
|
-
|
|
119
|
+
const currentDir = process.cwd();
|
|
120
|
+
const projectDir = path.join(currentDir, projectName);
|
|
121
|
+
const templateDir = path.join(__dirname, '..');
|
|
144
122
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
123
|
+
if (fs.existsSync(projectDir)) {
|
|
124
|
+
console.error(`❌ Directory ${projectName} already exists.`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Setup readline interface
|
|
129
|
+
const rl = readline.createInterface({
|
|
130
|
+
input: process.stdin,
|
|
131
|
+
output: process.stdout,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const ask = (query, defaultVal) => {
|
|
135
|
+
return new Promise((resolve) => {
|
|
136
|
+
rl.question(`${query} ${defaultVal ? `[${defaultVal}]` : ""}: `, (answer) => {
|
|
137
|
+
resolve(answer.trim() || defaultVal);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
};
|
|
150
141
|
|
|
151
|
-
|
|
152
|
-
|
|
142
|
+
const selectOption = async (query, options) => {
|
|
143
|
+
console.log(query);
|
|
144
|
+
options.forEach((opt, idx) => {
|
|
145
|
+
console.log(` [${opt.key}] ${opt.label}`);
|
|
146
|
+
});
|
|
153
147
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
148
|
+
while (true) {
|
|
149
|
+
const answer = await ask(">", options[0].key);
|
|
150
|
+
const selected = options.find(o => o.key.toLowerCase() === answer.toLowerCase());
|
|
151
|
+
if (selected) return selected;
|
|
152
|
+
|
|
153
|
+
const byLabel = options.find(o => o.label.toLowerCase().includes(answer.toLowerCase()));
|
|
154
|
+
if (byLabel) return byLabel;
|
|
155
|
+
|
|
156
|
+
console.log("Pilihan tidak valid. Silakan coba lagi.");
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
(async () => {
|
|
161
|
+
console.log(`🚀 Creating a new API Lapeh project in ${projectDir}...`);
|
|
162
|
+
fs.mkdirSync(projectDir);
|
|
163
|
+
|
|
164
|
+
// --- DATABASE SELECTION ---
|
|
165
|
+
console.log("\n--- Database Configuration ---");
|
|
166
|
+
const dbType = await selectOption("Database apa yang akan digunakan?", [
|
|
167
|
+
{ key: "pgsql", label: "PostgreSQL", provider: "postgresql", defaultPort: "5432" },
|
|
168
|
+
{ key: "mysql", label: "MySQL", provider: "mysql", defaultPort: "3306" },
|
|
169
|
+
{ key: "mariadb", label: "MariaDB", provider: "mysql", defaultPort: "3306" },
|
|
170
|
+
{ key: "sqlite", label: "SQLite", provider: "sqlite", defaultPort: "" },
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
let dbUrl = "";
|
|
174
|
+
let dbProvider = dbType.provider;
|
|
175
|
+
|
|
176
|
+
if (dbType.key === "sqlite") {
|
|
177
|
+
dbUrl = "file:./dev.db";
|
|
158
178
|
} else {
|
|
159
|
-
|
|
179
|
+
const host = await ask("Database Host", "localhost");
|
|
180
|
+
const port = await ask("Database Port", dbType.defaultPort);
|
|
181
|
+
const user = await ask("Database User", "root");
|
|
182
|
+
const password = await ask("Database Password", "");
|
|
183
|
+
const dbName = await ask("Database Name", projectName.replace(/-/g, '_')); // Default db name based on project name
|
|
184
|
+
|
|
185
|
+
if (dbType.key === "pgsql") {
|
|
186
|
+
dbUrl = `postgresql://${user}:${password}@${host}:${port}/${dbName}?schema=public`;
|
|
187
|
+
} else {
|
|
188
|
+
dbUrl = `mysql://${user}:${password}@${host}:${port}/${dbName}`;
|
|
189
|
+
}
|
|
160
190
|
}
|
|
161
191
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
192
|
+
rl.close();
|
|
193
|
+
|
|
194
|
+
// List of files/folders to exclude
|
|
195
|
+
const ignoreList = [
|
|
196
|
+
'node_modules',
|
|
197
|
+
'dist',
|
|
198
|
+
'.git',
|
|
199
|
+
'.env',
|
|
200
|
+
'bin', // Don't copy the CLI script itself
|
|
201
|
+
'package-lock.json',
|
|
202
|
+
'.DS_Store',
|
|
203
|
+
projectName // Don't copy the destination folder itself if creating inside the template
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
function copyDir(src, dest) {
|
|
207
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
208
|
+
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
const srcPath = path.join(src, entry.name);
|
|
211
|
+
const destPath = path.join(dest, entry.name);
|
|
212
|
+
|
|
213
|
+
if (ignoreList.includes(entry.name)) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (entry.isDirectory()) {
|
|
218
|
+
fs.mkdirSync(destPath);
|
|
219
|
+
copyDir(srcPath, destPath);
|
|
220
|
+
} else {
|
|
221
|
+
fs.copyFileSync(srcPath, destPath);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
167
224
|
}
|
|
168
225
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// Update prisma/base.prisma.template
|
|
173
|
-
console.log("📄 Updating prisma/base.prisma.template...");
|
|
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
|
-
}
|
|
226
|
+
console.log('\n📂 Copying template files...');
|
|
227
|
+
copyDir(templateDir, projectDir);
|
|
183
228
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
} catch (error) {
|
|
189
|
-
console.error('❌ Error installing dependencies.');
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
229
|
+
// Update package.json
|
|
230
|
+
console.log('📝 Updating package.json...');
|
|
231
|
+
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
232
|
+
const packageJson = require(packageJsonPath);
|
|
192
233
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
} catch (error) {
|
|
198
|
-
console.warn('⚠️ Could not generate JWT secret automatically.');
|
|
199
|
-
}
|
|
200
|
-
|
|
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' });
|
|
234
|
+
packageJson.name = projectName;
|
|
235
|
+
// Add lapeh framework version to dependencies to track it like react-router
|
|
236
|
+
packageJson.dependencies = packageJson.dependencies || {};
|
|
237
|
+
packageJson.dependencies["lapeh"] = packageJson.version;
|
|
206
238
|
|
|
207
|
-
|
|
208
|
-
|
|
239
|
+
packageJson.version = '1.0.0';
|
|
240
|
+
packageJson.description = 'Generated by lapeh';
|
|
241
|
+
delete packageJson.bin; // Remove the bin entry from the generated project
|
|
242
|
+
delete packageJson.repository; // Remove repository info if specific to the template
|
|
243
|
+
|
|
244
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
245
|
+
|
|
246
|
+
// Create .env from .env.example with correct DB config
|
|
247
|
+
console.log('⚙️ Configuring environment...');
|
|
248
|
+
const envExamplePath = path.join(projectDir, '.env.example');
|
|
249
|
+
const envPath = path.join(projectDir, '.env');
|
|
250
|
+
const prismaBaseFile = path.join(projectDir, "prisma", "base.prisma.template");
|
|
251
|
+
|
|
252
|
+
if (fs.existsSync(envExamplePath)) {
|
|
253
|
+
let envContent = fs.readFileSync(envExamplePath, 'utf8');
|
|
254
|
+
|
|
255
|
+
// Replace DATABASE_URL and DATABASE_PROVIDER
|
|
256
|
+
if (envContent.includes("DATABASE_URL=")) {
|
|
257
|
+
envContent = envContent.replace(/DATABASE_URL=".+"/g, `DATABASE_URL="${dbUrl}"`);
|
|
258
|
+
envContent = envContent.replace(/DATABASE_URL=.+/g, `DATABASE_URL="${dbUrl}"`);
|
|
259
|
+
} else {
|
|
260
|
+
envContent += `\nDATABASE_URL="${dbUrl}"`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (envContent.includes("DATABASE_PROVIDER=")) {
|
|
264
|
+
envContent = envContent.replace(/DATABASE_PROVIDER=".+"/g, `DATABASE_PROVIDER="${dbProvider}"`);
|
|
265
|
+
envContent = envContent.replace(/DATABASE_PROVIDER=.+/g, `DATABASE_PROVIDER="${dbProvider}"`);
|
|
266
|
+
} else {
|
|
267
|
+
envContent += `\nDATABASE_PROVIDER="${dbProvider}"`;
|
|
268
|
+
}
|
|
209
269
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
execSync('npx prisma migrate dev --name init_setup', { cwd: projectDir, stdio: 'inherit' });
|
|
270
|
+
fs.writeFileSync(envPath, envContent);
|
|
271
|
+
}
|
|
213
272
|
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
273
|
+
// Update prisma/base.prisma.template
|
|
274
|
+
console.log("📄 Updating prisma/base.prisma.template...");
|
|
275
|
+
if (fs.existsSync(prismaBaseFile)) {
|
|
276
|
+
let baseContent = fs.readFileSync(prismaBaseFile, "utf8");
|
|
277
|
+
// Replace provider in datasource block
|
|
278
|
+
baseContent = baseContent.replace(
|
|
279
|
+
/(datasource\s+db\s+\{[\s\S]*?provider\s*=\s*")([^"]+)(")/,
|
|
280
|
+
`$1${dbProvider}$3`
|
|
281
|
+
);
|
|
282
|
+
fs.writeFileSync(prismaBaseFile, baseContent);
|
|
220
283
|
}
|
|
221
284
|
|
|
222
|
-
|
|
223
|
-
console.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
285
|
+
// Install dependencies
|
|
286
|
+
console.log('📦 Installing dependencies (this might take a while)...');
|
|
287
|
+
try {
|
|
288
|
+
execSync('npm install', { cwd: projectDir, stdio: 'inherit' });
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error('❌ Error installing dependencies.');
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Generate JWT Secret
|
|
295
|
+
console.log('🔑 Generating JWT Secret...');
|
|
296
|
+
try {
|
|
297
|
+
execSync('npm run generate:jwt', { cwd: projectDir, stdio: 'inherit' });
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.warn('⚠️ Could not generate JWT secret automatically.');
|
|
300
|
+
}
|
|
228
301
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
})
|
|
302
|
+
// Generate Prisma Client & Migrate
|
|
303
|
+
console.log('🗄️ Setting up database...');
|
|
304
|
+
try {
|
|
305
|
+
console.log(' Compiling schema...');
|
|
306
|
+
execSync('node scripts/compile-schema.js', { cwd: projectDir, stdio: 'inherit' });
|
|
307
|
+
|
|
308
|
+
console.log(' Generating Prisma Client...');
|
|
309
|
+
execSync('npx prisma generate', { cwd: projectDir, stdio: 'inherit' });
|
|
310
|
+
|
|
311
|
+
// Try to migrate (this will create the DB if it doesn't exist)
|
|
312
|
+
console.log(' Running migration (creates DB if missing)...');
|
|
313
|
+
execSync('npx prisma migrate dev --name init_setup', { cwd: projectDir, stdio: 'inherit' });
|
|
314
|
+
|
|
315
|
+
// Seed
|
|
316
|
+
if (isFull) {
|
|
317
|
+
console.log(' Seeding database...');
|
|
318
|
+
execSync('npm run db:seed', { cwd: projectDir, stdio: 'inherit' });
|
|
319
|
+
} else {
|
|
320
|
+
console.log(' ℹ️ Skipping database seeding (use --full to seed default data)...');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.warn('⚠️ Database setup encountered an issue.');
|
|
325
|
+
console.warn(' You may need to check your .env credentials and run:');
|
|
326
|
+
console.warn(' cd ' + projectName);
|
|
327
|
+
console.warn(' npm run prisma:migrate');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(`\n✅ Project ${projectName} created successfully!`);
|
|
331
|
+
console.log(`\nNext steps:`);
|
|
332
|
+
console.log(` cd ${projectName}`);
|
|
333
|
+
console.log(` npm run dev`);
|
|
334
|
+
})();
|
|
335
|
+
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -75,6 +75,25 @@ Jika Anda melakukan setup dengan flag `--full`, database akan terisi dengan akun
|
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
|
+
## 🔄 Upgrade Project
|
|
79
|
+
|
|
80
|
+
Jika Anda memiliki project lama yang dibuat dengan versi Lapeh sebelumnya dan ingin memperbarui struktur, scripts, dan konfigurasi ke standar terbaru (termasuk keamanan Redis baru), Anda tidak perlu membuat project ulang.
|
|
81
|
+
|
|
82
|
+
Cukup jalankan perintah ini di dalam folder project Anda:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx lapeh@latest upgrade
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Perintah ini akan secara otomatis:
|
|
89
|
+
|
|
90
|
+
1. Mengupdate `scripts/` (termasuk generator controller baru).
|
|
91
|
+
2. Mengupdate `docker-compose.yml` (keamanan Redis).
|
|
92
|
+
3. Mengupdate dependencies di `package.json`.
|
|
93
|
+
4. Menambahkan konfigurasi `.vscode` dan `tsconfig` terbaru.
|
|
94
|
+
|
|
95
|
+
> **Catatan:** File `.env` Anda **tidak akan ditimpa**, namun kami akan mengupdate `.env.example` sebagai referensi konfigurasi terbaru.
|
|
96
|
+
|
|
78
97
|
## 🧠 Zero-Config Redis
|
|
79
98
|
|
|
80
99
|
Lapeh otomatis mendeteksi ketersediaan Redis.
|