lapeeh 1.0.0
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 +14 -0
- package/LICENSE +21 -0
- package/bin/index.js +934 -0
- package/doc/en/ARCHITECTURE_GUIDE.md +79 -0
- package/doc/en/CHANGELOG.md +203 -0
- package/doc/en/CHEATSHEET.md +90 -0
- package/doc/en/CLI.md +111 -0
- package/doc/en/CONTRIBUTING.md +119 -0
- package/doc/en/DEPLOYMENT.md +171 -0
- package/doc/en/FAQ.md +69 -0
- package/doc/en/FEATURES.md +99 -0
- package/doc/en/GETTING_STARTED.md +84 -0
- package/doc/en/INTRODUCTION.md +62 -0
- package/doc/en/PACKAGES.md +63 -0
- package/doc/en/PERFORMANCE.md +98 -0
- package/doc/en/ROADMAP.md +104 -0
- package/doc/en/SECURITY.md +95 -0
- package/doc/en/STRUCTURE.md +79 -0
- package/doc/en/TUTORIAL.md +145 -0
- package/doc/id/ARCHITECTURE_GUIDE.md +76 -0
- package/doc/id/CHANGELOG.md +203 -0
- package/doc/id/CHEATSHEET.md +90 -0
- package/doc/id/CLI.md +139 -0
- package/doc/id/CONTRIBUTING.md +119 -0
- package/doc/id/DEPLOYMENT.md +171 -0
- package/doc/id/FAQ.md +69 -0
- package/doc/id/FEATURES.md +169 -0
- package/doc/id/GETTING_STARTED.md +91 -0
- package/doc/id/INTRODUCTION.md +62 -0
- package/doc/id/PACKAGES.md +63 -0
- package/doc/id/PERFORMANCE.md +100 -0
- package/doc/id/ROADMAP.md +107 -0
- package/doc/id/SECURITY.md +94 -0
- package/doc/id/STRUCTURE.md +79 -0
- package/doc/id/TUTORIAL.md +145 -0
- package/docker-compose.yml +24 -0
- package/ecosystem.config.js +17 -0
- package/eslint.config.mjs +26 -0
- package/gitignore.template +30 -0
- package/lib/bootstrap.ts +210 -0
- package/lib/core/realtime.ts +34 -0
- package/lib/core/redis.ts +139 -0
- package/lib/core/serializer.ts +63 -0
- package/lib/core/server.ts +70 -0
- package/lib/core/store.ts +116 -0
- package/lib/middleware/auth.ts +63 -0
- package/lib/middleware/error.ts +50 -0
- package/lib/middleware/multipart.ts +13 -0
- package/lib/middleware/rateLimit.ts +14 -0
- package/lib/middleware/requestLogger.ts +27 -0
- package/lib/middleware/visitor.ts +178 -0
- package/lib/utils/logger.ts +100 -0
- package/lib/utils/pagination.ts +56 -0
- package/lib/utils/response.ts +88 -0
- package/lib/utils/validator.ts +394 -0
- package/nodemon.json +6 -0
- package/package.json +126 -0
- package/readme.md +357 -0
- package/scripts/check-update.js +92 -0
- package/scripts/config-clear.js +45 -0
- package/scripts/generate-jwt-secret.js +38 -0
- package/scripts/init-project.js +84 -0
- package/scripts/make-module.js +89 -0
- package/scripts/release.js +494 -0
- package/scripts/seed-json.js +158 -0
- package/scripts/verify-rbac-functional.js +187 -0
- package/src/config/app.ts +9 -0
- package/src/config/cors.ts +5 -0
- package/src/modules/Auth/auth.controller.ts +519 -0
- package/src/modules/Rbac/rbac.controller.ts +533 -0
- package/src/routes/auth.ts +74 -0
- package/src/routes/index.ts +7 -0
- package/src/routes/rbac.ts +42 -0
- package/storage/logs/.gitkeep +0 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +30 -0
package/bin/index.js
ADDED
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync, spawn } = require('child_process');
|
|
6
|
+
const readline = require('readline');
|
|
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
|
+
|
|
51
|
+
const args = process.argv.slice(2);
|
|
52
|
+
const command = args[0];
|
|
53
|
+
|
|
54
|
+
// Telemetry Logic
|
|
55
|
+
async function sendTelemetry(cmd, errorInfo = null) {
|
|
56
|
+
try {
|
|
57
|
+
const os = require('os');
|
|
58
|
+
|
|
59
|
+
const payload = {
|
|
60
|
+
command: cmd,
|
|
61
|
+
nodeVersion: process.version,
|
|
62
|
+
osPlatform: os.platform(),
|
|
63
|
+
osRelease: os.release(),
|
|
64
|
+
timestamp: new Date().toISOString()
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (errorInfo) {
|
|
68
|
+
payload.error = errorInfo.message;
|
|
69
|
+
payload.stack = errorInfo.stack;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Add version to payload
|
|
73
|
+
try {
|
|
74
|
+
const pkg = require(path.join(__dirname, '../package.json'));
|
|
75
|
+
payload.cliVersion = pkg.version;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
payload.cliVersion = 'unknown';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const data = JSON.stringify(payload);
|
|
81
|
+
|
|
82
|
+
// Parse URL from env or use default
|
|
83
|
+
const apiUrl = process.env.lapeeh_API_URL || 'https://lapeeh.vercel.app/api/telemetry';
|
|
84
|
+
const url = new URL(apiUrl);
|
|
85
|
+
const isHttps = url.protocol === 'https:';
|
|
86
|
+
const client = isHttps ? require('https') : require('http');
|
|
87
|
+
|
|
88
|
+
const options = {
|
|
89
|
+
hostname: url.hostname,
|
|
90
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
91
|
+
path: url.pathname,
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
'Content-Length': Buffer.byteLength(data)
|
|
96
|
+
},
|
|
97
|
+
timeout: 2000 // Slightly longer for crash reports
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const req = client.request(options, (res) => {
|
|
101
|
+
res.resume();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
req.on('error', (e) => {
|
|
105
|
+
// Silent fail
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
req.write(data);
|
|
109
|
+
req.end();
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// Silent fail
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Global Error Handler for Crash Reporting
|
|
116
|
+
process.on('uncaughtException', async (err) => {
|
|
117
|
+
console.error('❌ Unexpected Error:', err);
|
|
118
|
+
console.log('📝 Sending crash report...');
|
|
119
|
+
try {
|
|
120
|
+
sendTelemetry(command || 'unknown', err);
|
|
121
|
+
|
|
122
|
+
// Give it a moment to send
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}, 1000);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
function showHelp() {
|
|
132
|
+
console.log('\n\x1b[36m L A P E H F R A M E W O R K C L I\x1b[0m\n');
|
|
133
|
+
console.log('Usage: npx lapeeh <command> [options]\n');
|
|
134
|
+
console.log('Commands:');
|
|
135
|
+
console.log(' create <name> Create a new lapeeh project');
|
|
136
|
+
console.log(' dev Start development server (with update check)');
|
|
137
|
+
console.log(' start Start production server');
|
|
138
|
+
console.log(' build Build the project for production');
|
|
139
|
+
console.log(' upgrade Upgrade project files to match framework version');
|
|
140
|
+
console.log(' module <name> Create a new module (controller, routes, etc.)');
|
|
141
|
+
console.log(' help Show this help message');
|
|
142
|
+
console.log('\nOptions:');
|
|
143
|
+
console.log(' --full Create project with full example (auth, users, etc)');
|
|
144
|
+
console.log(' -y, --defaults Skip prompts and use defaults');
|
|
145
|
+
console.log(' -h, --help Show this help message');
|
|
146
|
+
console.log('\nExamples:');
|
|
147
|
+
console.log(' npx lapeeh my-app');
|
|
148
|
+
console.log(' npx lapeeh create my-app --full');
|
|
149
|
+
console.log(' npx lapeeh dev');
|
|
150
|
+
console.log('\n');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Handle Help or No Args
|
|
154
|
+
if (!command || ['help', '--help', '-h'].includes(command)) {
|
|
155
|
+
showHelp();
|
|
156
|
+
sendTelemetry('help');
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Send telemetry for every command (only if not crashing immediately)
|
|
161
|
+
sendTelemetry(command);
|
|
162
|
+
|
|
163
|
+
switch (command) {
|
|
164
|
+
case 'dev':
|
|
165
|
+
(async () => { await runDev(); })();
|
|
166
|
+
break;
|
|
167
|
+
case 'start':
|
|
168
|
+
(async () => { await runStart(); })();
|
|
169
|
+
break;
|
|
170
|
+
case 'build':
|
|
171
|
+
(async () => { await runBuild(); })();
|
|
172
|
+
break;
|
|
173
|
+
case 'upgrade':
|
|
174
|
+
(async () => {
|
|
175
|
+
await upgradeProject();
|
|
176
|
+
})();
|
|
177
|
+
break;
|
|
178
|
+
case 'make:module':
|
|
179
|
+
case 'module':
|
|
180
|
+
const moduleName = args[1];
|
|
181
|
+
if (!moduleName) {
|
|
182
|
+
console.error('❌ Please specify the module name.');
|
|
183
|
+
console.error(' Usage: npx lapeeh module <ModuleName>');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
createModule(moduleName);
|
|
187
|
+
break;
|
|
188
|
+
case 'init':
|
|
189
|
+
case 'create':
|
|
190
|
+
createProject(true);
|
|
191
|
+
break;
|
|
192
|
+
default:
|
|
193
|
+
createProject(false);
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function checkUpdate() {
|
|
198
|
+
try {
|
|
199
|
+
const pkg = require(path.join(__dirname, '../package.json'));
|
|
200
|
+
const currentVersion = pkg.version;
|
|
201
|
+
|
|
202
|
+
// Fetch latest version from npm registry
|
|
203
|
+
const latestVersion = await new Promise((resolve) => {
|
|
204
|
+
const https = require('https');
|
|
205
|
+
const req = https.get('https://registry.npmjs.org/lapeeh/latest', {
|
|
206
|
+
headers: { 'User-Agent': 'lapeeh-CLI' },
|
|
207
|
+
timeout: 1500 // 1.5s timeout
|
|
208
|
+
}, (res) => {
|
|
209
|
+
let data = '';
|
|
210
|
+
res.on('data', chunk => data += chunk);
|
|
211
|
+
res.on('end', () => {
|
|
212
|
+
try {
|
|
213
|
+
const json = JSON.parse(data);
|
|
214
|
+
resolve(json.version);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
resolve(null);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
req.on('error', () => resolve(null));
|
|
222
|
+
req.on('timeout', () => {
|
|
223
|
+
req.destroy();
|
|
224
|
+
resolve(null);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
if (latestVersion && latestVersion !== currentVersion) {
|
|
229
|
+
const currentParts = currentVersion.split('.').map(Number);
|
|
230
|
+
const latestParts = latestVersion.split('.').map(Number);
|
|
231
|
+
|
|
232
|
+
let isOutdated = false;
|
|
233
|
+
for(let i=0; i<3; i++) {
|
|
234
|
+
if (latestParts[i] > currentParts[i]) {
|
|
235
|
+
isOutdated = true;
|
|
236
|
+
break;
|
|
237
|
+
} else if (latestParts[i] < currentParts[i]) {
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (isOutdated) {
|
|
243
|
+
console.log('\n');
|
|
244
|
+
console.log('\x1b[33m┌────────────────────────────────────────────────────────────┐\x1b[0m');
|
|
245
|
+
console.log(`\x1b[33m│\x1b[0m \x1b[1mUpdate available!\x1b[0m \x1b[31m${currentVersion}\x1b[0m → \x1b[32m${latestVersion}\x1b[0m \x1b[33m│\x1b[0m`);
|
|
246
|
+
console.log(`\x1b[33m│\x1b[0m Run \x1b[36mnpm install @lapeeh/lapeeh@latest\x1b[0m to update \x1b[33m│\x1b[0m`);
|
|
247
|
+
console.log(`\x1b[33m│\x1b[0m Then run \x1b[36mnpx lapeeh upgrade\x1b[0m to sync files \x1b[33m│\x1b[0m`);
|
|
248
|
+
console.log('\x1b[33m└────────────────────────────────────────────────────────────┘\x1b[0m');
|
|
249
|
+
console.log('\n');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch (e) {
|
|
253
|
+
// Ignore errors during update check
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function runDev() {
|
|
258
|
+
console.log('🚀 Starting lapeeh in development mode...');
|
|
259
|
+
await checkUpdate();
|
|
260
|
+
try {
|
|
261
|
+
const tsNodePath = require.resolve('ts-node/register');
|
|
262
|
+
const tsConfigPathsPath = require.resolve('tsconfig-paths/register');
|
|
263
|
+
|
|
264
|
+
// Resolve bootstrap file
|
|
265
|
+
// 1. Try to find it in the current project's node_modules (preferred)
|
|
266
|
+
const localBootstrapPath = path.join(process.cwd(), 'node_modules/lapeeh/lib/bootstrap.ts');
|
|
267
|
+
|
|
268
|
+
// 2. Fallback to relative to this script (if running from source or global cache without local install)
|
|
269
|
+
const fallbackBootstrapPath = path.resolve(__dirname, '../lib/bootstrap.ts');
|
|
270
|
+
|
|
271
|
+
const bootstrapPath = fs.existsSync(localBootstrapPath) ? localBootstrapPath : fallbackBootstrapPath;
|
|
272
|
+
|
|
273
|
+
// We execute a script that requires ts-node to run lib/bootstrap.ts
|
|
274
|
+
// Use JSON.stringify to properly escape paths for the shell command
|
|
275
|
+
const nodeArgs = `-r ${JSON.stringify(tsNodePath)} -r ${JSON.stringify(tsConfigPathsPath)} ${JSON.stringify(bootstrapPath)}`;
|
|
276
|
+
const isWin = process.platform === 'win32';
|
|
277
|
+
|
|
278
|
+
let cmd;
|
|
279
|
+
if (isWin) {
|
|
280
|
+
// On Windows, escape inner quotes
|
|
281
|
+
const escapedArgs = nodeArgs.replace(/"/g, '\\"');
|
|
282
|
+
cmd = `npx nodemon --watch src --watch lib --ext ts,json --exec "node ${escapedArgs}"`;
|
|
283
|
+
} else {
|
|
284
|
+
// On Linux/Mac, use single quotes for the outer wrapper
|
|
285
|
+
cmd = `npx nodemon --watch src --watch lib --ext ts,json --exec 'node ${nodeArgs}'`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
289
|
+
} catch (error) {
|
|
290
|
+
// Ignore error
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function runStart() {
|
|
295
|
+
await spin('Starting lapeeh production server...', async () => {
|
|
296
|
+
await new Promise(r => setTimeout(r, 1500)); // Simulate startup checks animation
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
let bootstrapPath;
|
|
300
|
+
try {
|
|
301
|
+
const projectNodeModules = path.join(process.cwd(), 'node_modules');
|
|
302
|
+
const lapeehDist = path.join(projectNodeModules, 'lapeeh', 'dist', 'lib', 'bootstrap.js');
|
|
303
|
+
const lapeehLib = path.join(projectNodeModules, 'lapeeh', 'lib', 'bootstrap.js');
|
|
304
|
+
|
|
305
|
+
if (fs.existsSync(lapeehDist)) {
|
|
306
|
+
bootstrapPath = lapeehDist;
|
|
307
|
+
} else if (fs.existsSync(lapeehLib)) {
|
|
308
|
+
bootstrapPath = path.resolve(__dirname, '../lib/bootstrap.js');
|
|
309
|
+
if (!fs.existsSync(bootstrapPath)) {
|
|
310
|
+
bootstrapPath = path.resolve(__dirname, '../dist/lib/bootstrap.js');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const frameworkBootstrap = require('../lib/bootstrap');
|
|
315
|
+
frameworkBootstrap.bootstrap();
|
|
316
|
+
return;
|
|
317
|
+
|
|
318
|
+
} catch (e) {
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const possiblePaths = [
|
|
322
|
+
path.join(__dirname, '../lib/bootstrap.js'),
|
|
323
|
+
path.join(__dirname, '../dist/lib/bootstrap.js'),
|
|
324
|
+
path.join(process.cwd(), 'node_modules/@lapeeh/lapeeh/lib/bootstrap.js')
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
bootstrapPath = possiblePaths.find(p => fs.existsSync(p));
|
|
328
|
+
|
|
329
|
+
if (!bootstrapPath) {
|
|
330
|
+
console.error('❌ Could not find lapeeh bootstrap file.');
|
|
331
|
+
console.error(' Searched in:', possiblePaths);
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let cmd;
|
|
336
|
+
if (bootstrapPath.endsWith('.ts')) {
|
|
337
|
+
let tsNodePath;
|
|
338
|
+
let tsConfigPathsPath;
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const projectNodeModules = path.join(process.cwd(), 'node_modules');
|
|
342
|
+
tsNodePath = require.resolve('ts-node/register', { paths: [projectNodeModules, __dirname] });
|
|
343
|
+
tsConfigPathsPath = require.resolve('tsconfig-paths/register', { paths: [projectNodeModules, __dirname] });
|
|
344
|
+
} catch (e) {
|
|
345
|
+
try {
|
|
346
|
+
tsNodePath = require.resolve('ts-node/register');
|
|
347
|
+
tsConfigPathsPath = require.resolve('tsconfig-paths/register');
|
|
348
|
+
} catch (e2) {
|
|
349
|
+
console.warn('⚠️ Could not resolve ts-node/register. Trying npx...');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (tsNodePath && tsConfigPathsPath) {
|
|
354
|
+
const script = `require(${JSON.stringify(bootstrapPath)}).bootstrap()`;
|
|
355
|
+
cmd = `node -r ${JSON.stringify(tsNodePath)} -r ${JSON.stringify(tsConfigPathsPath)} -e ${JSON.stringify(script)}`;
|
|
356
|
+
} else {
|
|
357
|
+
const script = `require(${JSON.stringify(bootstrapPath)}).bootstrap()`;
|
|
358
|
+
cmd = `npx ts-node -r tsconfig-paths/register -e ${JSON.stringify(script)}`;
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
const script = `require(${JSON.stringify(bootstrapPath)}).bootstrap()`;
|
|
362
|
+
cmd = `node -e ${JSON.stringify(script)}`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
execSync(cmd, {
|
|
366
|
+
stdio: 'inherit',
|
|
367
|
+
env: { ...process.env, NODE_ENV: 'production' }
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function runBuild() {
|
|
372
|
+
console.log('🛠️ Building lapeeh project...');
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
execSync('npx tsc -p tsconfig.build.json && npx tsc-alias -p tsconfig.build.json', { stdio: 'inherit' });
|
|
376
|
+
} catch (e) {
|
|
377
|
+
console.error('❌ Build failed.');
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
console.log('✅ Build complete.');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function upgradeProject() {
|
|
385
|
+
const currentDir = process.cwd();
|
|
386
|
+
const templateDir = path.join(__dirname, '..');
|
|
387
|
+
|
|
388
|
+
console.log(`🚀 Upgrading lapeeh project in ${currentDir}...`);
|
|
389
|
+
|
|
390
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
391
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
392
|
+
console.error('❌ No package.json found. Are you in the root of a lapeeh project?');
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const filesToSync = [
|
|
397
|
+
'lib',
|
|
398
|
+
'docker-compose.yml',
|
|
399
|
+
'.env.example',
|
|
400
|
+
'.vscode',
|
|
401
|
+
'tsconfig.json',
|
|
402
|
+
'README.md',
|
|
403
|
+
'ecosystem.config.js',
|
|
404
|
+
'src/redis.ts'
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
const scriptsDir = path.join(currentDir, 'scripts');
|
|
408
|
+
if (fs.existsSync(scriptsDir)) {
|
|
409
|
+
console.log(`🗑️ Removing obsolete directory: ${scriptsDir}`);
|
|
410
|
+
fs.rmSync(scriptsDir, { recursive: true, force: true });
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const updateStats = {
|
|
414
|
+
updated: [],
|
|
415
|
+
created: [],
|
|
416
|
+
removed: []
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
function syncDirectory(src, dest, clean = false) {
|
|
420
|
+
if (!fs.existsSync(src)) return;
|
|
421
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
422
|
+
|
|
423
|
+
const srcEntries = fs.readdirSync(src, { withFileTypes: true });
|
|
424
|
+
const srcEntryNames = new Set();
|
|
425
|
+
|
|
426
|
+
for (const entry of srcEntries) {
|
|
427
|
+
srcEntryNames.add(entry.name);
|
|
428
|
+
const srcPath = path.join(src, entry.name);
|
|
429
|
+
const destPath = path.join(dest, entry.name);
|
|
430
|
+
const relativePath = path.relative(currentDir, destPath);
|
|
431
|
+
|
|
432
|
+
if (entry.isDirectory()) {
|
|
433
|
+
syncDirectory(srcPath, destPath, clean);
|
|
434
|
+
} else {
|
|
435
|
+
let shouldCopy = true;
|
|
436
|
+
|
|
437
|
+
if (fs.existsSync(destPath)) {
|
|
438
|
+
const srcContent = fs.readFileSync(srcPath);
|
|
439
|
+
const destContent = fs.readFileSync(destPath);
|
|
440
|
+
if (srcContent.equals(destContent)) {
|
|
441
|
+
shouldCopy = false;
|
|
442
|
+
} else {
|
|
443
|
+
updateStats.updated.push(relativePath);
|
|
444
|
+
}
|
|
445
|
+
} else {
|
|
446
|
+
updateStats.created.push(relativePath);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (shouldCopy) {
|
|
450
|
+
fs.copyFileSync(srcPath, destPath);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (clean) {
|
|
456
|
+
const destEntries = fs.readdirSync(dest, { withFileTypes: true });
|
|
457
|
+
for (const entry of destEntries) {
|
|
458
|
+
if (!srcEntryNames.has(entry.name)) {
|
|
459
|
+
const destPath = path.join(dest, entry.name);
|
|
460
|
+
const relativePath = path.relative(currentDir, destPath);
|
|
461
|
+
|
|
462
|
+
console.log(`🗑️ Removing obsolete file/directory: ${destPath}`);
|
|
463
|
+
updateStats.removed.push(relativePath);
|
|
464
|
+
|
|
465
|
+
if (entry.isDirectory()) {
|
|
466
|
+
fs.rmSync(destPath, { recursive: true, force: true });
|
|
467
|
+
} else {
|
|
468
|
+
fs.unlinkSync(destPath);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
for (const item of filesToSync) {
|
|
476
|
+
const srcPath = path.join(templateDir, item);
|
|
477
|
+
const destPath = path.join(currentDir, item);
|
|
478
|
+
const relativePath = item; // Since item is relative to templateDir/currentDir
|
|
479
|
+
|
|
480
|
+
if (fs.existsSync(srcPath)) {
|
|
481
|
+
const stats = fs.statSync(srcPath);
|
|
482
|
+
if (stats.isDirectory()) {
|
|
483
|
+
console.log(`🔄 Syncing directory ${item}...`);
|
|
484
|
+
syncDirectory(srcPath, destPath, item === 'lib');
|
|
485
|
+
} else {
|
|
486
|
+
console.log(`🔄 Checking file ${item}...`);
|
|
487
|
+
const destDir = path.dirname(destPath);
|
|
488
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
489
|
+
|
|
490
|
+
let shouldCopy = true;
|
|
491
|
+
if (fs.existsSync(destPath)) {
|
|
492
|
+
const srcContent = fs.readFileSync(srcPath);
|
|
493
|
+
const destContent = fs.readFileSync(destPath);
|
|
494
|
+
if (srcContent.equals(destContent)) {
|
|
495
|
+
shouldCopy = false;
|
|
496
|
+
} else {
|
|
497
|
+
updateStats.updated.push(relativePath);
|
|
498
|
+
}
|
|
499
|
+
} else {
|
|
500
|
+
updateStats.created.push(relativePath);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (shouldCopy) {
|
|
504
|
+
fs.copyFileSync(srcPath, destPath);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
console.log('📝 Updating package.json...');
|
|
511
|
+
const currentPackageJson = require(packageJsonPath);
|
|
512
|
+
|
|
513
|
+
// Capture original dependency before merging
|
|
514
|
+
const originallapeehDep = currentPackageJson.dependencies && currentPackageJson.dependencies["lapeeh"];
|
|
515
|
+
|
|
516
|
+
const templatePackageJson = require(path.join(templateDir, 'package.json'));
|
|
517
|
+
|
|
518
|
+
// Define scripts to remove (those that depend on the scripts folder)
|
|
519
|
+
const scriptsToRemove = ['first', 'generate:jwt', 'make:module', 'make:modul', 'config:clear', 'release'];
|
|
520
|
+
|
|
521
|
+
// Filter template scripts
|
|
522
|
+
const filteredTemplateScripts = Object.keys(templatePackageJson.scripts)
|
|
523
|
+
.filter(key => !scriptsToRemove.includes(key))
|
|
524
|
+
.reduce((obj, key) => {
|
|
525
|
+
obj[key] = templatePackageJson.scripts[key];
|
|
526
|
+
return obj;
|
|
527
|
+
}, {});
|
|
528
|
+
|
|
529
|
+
currentPackageJson.scripts = {
|
|
530
|
+
...currentPackageJson.scripts,
|
|
531
|
+
...filteredTemplateScripts,
|
|
532
|
+
"dev": "lapeeh dev",
|
|
533
|
+
"start": "lapeeh start",
|
|
534
|
+
"build": "lapeeh build",
|
|
535
|
+
"start:prod": "lapeeh start"
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// Clean up existing scripts that we want to remove
|
|
539
|
+
scriptsToRemove.forEach(script => {
|
|
540
|
+
if (currentPackageJson.scripts[script]) {
|
|
541
|
+
delete currentPackageJson.scripts[script];
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
currentPackageJson.dependencies = {
|
|
546
|
+
...currentPackageJson.dependencies,
|
|
547
|
+
...templatePackageJson.dependencies
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
currentPackageJson.devDependencies = {
|
|
551
|
+
...currentPackageJson.devDependencies,
|
|
552
|
+
...templatePackageJson.devDependencies
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
const frameworkPackageJson = require(path.join(templateDir, 'package.json'));
|
|
556
|
+
|
|
557
|
+
if (originallapeehDep && originallapeehDep.startsWith('file:')) {
|
|
558
|
+
console.log(`ℹ️ Preserving local 'lapeeh' dependency: ${originallapeehDep}`);
|
|
559
|
+
currentPackageJson.dependencies["lapeeh"] = originallapeehDep;
|
|
560
|
+
} else {
|
|
561
|
+
if (__dirname.includes('node_modules')) {
|
|
562
|
+
currentPackageJson.dependencies["lapeeh"] = `^${frameworkPackageJson.version}`;
|
|
563
|
+
} else {
|
|
564
|
+
const lapeehPath = path.resolve(__dirname, '..').replace(/\\/g, '/');
|
|
565
|
+
currentPackageJson.dependencies["lapeeh"] = `file:${lapeehPath}`;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Ensure prisma config exists for seed
|
|
570
|
+
if (!currentPackageJson.prisma) {
|
|
571
|
+
currentPackageJson.prisma = {
|
|
572
|
+
seed: "npx ts-node -r tsconfig-paths/register prisma/seed.ts"
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(currentPackageJson, null, 2));
|
|
577
|
+
|
|
578
|
+
console.log('🔧 Configuring tsconfig.json...');
|
|
579
|
+
const tsconfigPath = path.join(currentDir, 'tsconfig.json');
|
|
580
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
581
|
+
const tsconfig = require(tsconfigPath);
|
|
582
|
+
if (tsconfig.compilerOptions && tsconfig.compilerOptions.paths) {
|
|
583
|
+
tsconfig.compilerOptions.paths["@lapeeh/*"] = ["./node_modules/@lapeeh/lapeeh/dist/lib/*"];
|
|
584
|
+
}
|
|
585
|
+
tsconfig["ts-node"] = {
|
|
586
|
+
"ignore": ["node_modules/(?!@lapeeh)"]
|
|
587
|
+
};
|
|
588
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
console.log('📦 Installing updated dependencies...');
|
|
592
|
+
try {
|
|
593
|
+
execSync('npm install', { cwd: currentDir, stdio: 'inherit' });
|
|
594
|
+
} catch (error) {
|
|
595
|
+
console.error('❌ Error installing dependencies.');
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
console.log('\n✅ Upgrade completed successfully!');
|
|
600
|
+
|
|
601
|
+
if (updateStats.created.length > 0) {
|
|
602
|
+
console.log('\n✨ Created files:');
|
|
603
|
+
updateStats.created.forEach(f => console.log(` \x1b[32m+ ${f}\x1b[0m`));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (updateStats.updated.length > 0) {
|
|
607
|
+
console.log('\n📝 Updated files:');
|
|
608
|
+
updateStats.updated.forEach(f => console.log(` \x1b[33m~ ${f}\x1b[0m`));
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (updateStats.removed.length > 0) {
|
|
612
|
+
console.log('\n🗑️ Removed files:');
|
|
613
|
+
updateStats.removed.forEach(f => console.log(` \x1b[31m- ${f}\x1b[0m`));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (updateStats.created.length === 0 && updateStats.updated.length === 0 && updateStats.removed.length === 0) {
|
|
617
|
+
console.log(' No files were changed.');
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
console.log('\n Please check your .env file against .env.example for any new required variables.');
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function createModule(moduleName) {
|
|
624
|
+
// Capitalize first letter
|
|
625
|
+
const name = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
|
|
626
|
+
const lowerName = moduleName.toLowerCase();
|
|
627
|
+
|
|
628
|
+
const currentDir = process.cwd();
|
|
629
|
+
// Support both src/modules (default) and just modules if user changed structure
|
|
630
|
+
const srcModulesDir = path.join(currentDir, 'src', 'modules');
|
|
631
|
+
const modulesDir = fs.existsSync(srcModulesDir) ? srcModulesDir : path.join(currentDir, 'modules');
|
|
632
|
+
|
|
633
|
+
if (!fs.existsSync(path.join(currentDir, 'src')) && !fs.existsSync(modulesDir)) {
|
|
634
|
+
console.error('❌ Could not find src directory. Are you in a lapeeh project root?');
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const targetDir = path.join(modulesDir, name);
|
|
639
|
+
|
|
640
|
+
if (fs.existsSync(targetDir)) {
|
|
641
|
+
console.error(`❌ Module ${name} already exists at ${targetDir}`);
|
|
642
|
+
process.exit(1);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
646
|
+
|
|
647
|
+
// Controller
|
|
648
|
+
const controllerContent = `import { Request, Response } from "express";
|
|
649
|
+
import { sendSuccess } from "@lapeeh/utils/response";
|
|
650
|
+
// import * as ${name}Service from "./${lowerName}.service";
|
|
651
|
+
|
|
652
|
+
export async function index(_req: Request, res: Response) {
|
|
653
|
+
sendSuccess(res, 200, "Index ${name}");
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export async function show(req: Request, res: Response) {
|
|
657
|
+
const { id } = req.params;
|
|
658
|
+
sendSuccess(res, 200, "Show ${name} " + id);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
export async function create(_req: Request, res: Response) {
|
|
662
|
+
sendSuccess(res, 201, "Create ${name}");
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export async function update(req: Request, res: Response) {
|
|
666
|
+
const { id } = req.params;
|
|
667
|
+
sendSuccess(res, 200, "Update ${name} " + id);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
export async function destroy(req: Request, res: Response) {
|
|
671
|
+
const { id } = req.params;
|
|
672
|
+
sendSuccess(res, 200, "Delete ${name} " + id);
|
|
673
|
+
}
|
|
674
|
+
`;
|
|
675
|
+
|
|
676
|
+
fs.writeFileSync(path.join(targetDir, `${lowerName}.controller.ts`), controllerContent);
|
|
677
|
+
|
|
678
|
+
// Service
|
|
679
|
+
const serviceContent = `
|
|
680
|
+
export async function findAll() {
|
|
681
|
+
return [];
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export async function findOne(_id: number) {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
`;
|
|
688
|
+
fs.writeFileSync(path.join(targetDir, `${lowerName}.service.ts`), serviceContent);
|
|
689
|
+
|
|
690
|
+
// Route Stub
|
|
691
|
+
const routeContent = `import { Router } from "express";
|
|
692
|
+
import * as ${name}Controller from "./${lowerName}.controller";
|
|
693
|
+
|
|
694
|
+
const router = Router();
|
|
695
|
+
|
|
696
|
+
router.get("/", ${name}Controller.index);
|
|
697
|
+
router.get("/:id", ${name}Controller.show);
|
|
698
|
+
router.post("/", ${name}Controller.create);
|
|
699
|
+
router.put("/:id", ${name}Controller.update);
|
|
700
|
+
router.delete("/:id", ${name}Controller.destroy);
|
|
701
|
+
|
|
702
|
+
export default router;
|
|
703
|
+
`;
|
|
704
|
+
fs.writeFileSync(path.join(targetDir, `${lowerName}.routes.ts`), routeContent);
|
|
705
|
+
|
|
706
|
+
console.log(`✅ Module ${name} created successfully at src/modules/${name}`);
|
|
707
|
+
console.log(` - ${lowerName}.controller.ts`);
|
|
708
|
+
console.log(` - ${lowerName}.service.ts`);
|
|
709
|
+
console.log(` - ${lowerName}.routes.ts`);
|
|
710
|
+
console.log(`\n👉 Don't forget to register the route in src/routes/index.ts!`);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function createProject(skipFirstArg = false) {
|
|
714
|
+
const searchArgs = skipFirstArg ? args.slice(1) : args;
|
|
715
|
+
const projectName = searchArgs.find(arg => !arg.startsWith('-'));
|
|
716
|
+
const isFull = args.includes('--full');
|
|
717
|
+
const useDefaults = args.includes('--defaults') || args.includes('--default') || args.includes('-y');
|
|
718
|
+
|
|
719
|
+
if (!projectName) {
|
|
720
|
+
console.error('❌ Please specify the project name:');
|
|
721
|
+
console.error(' npx lapeeh-cli <project-name> [--full] [--defaults|-y]');
|
|
722
|
+
process.exit(1);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const currentDir = process.cwd();
|
|
726
|
+
const projectDir = path.join(currentDir, projectName);
|
|
727
|
+
const templateDir = path.join(__dirname, '..');
|
|
728
|
+
|
|
729
|
+
if (fs.existsSync(projectDir)) {
|
|
730
|
+
console.error(`❌ Directory ${projectName} already exists.`);
|
|
731
|
+
process.exit(1);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const rl = readline.createInterface({
|
|
735
|
+
input: process.stdin,
|
|
736
|
+
output: process.stdout,
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
const ask = (query, defaultVal) => {
|
|
740
|
+
return new Promise((resolve) => {
|
|
741
|
+
rl.question(`${query} ${defaultVal ? `[${defaultVal}]` : ""}: `, (answer) => {
|
|
742
|
+
resolve(answer.trim() || defaultVal);
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
const selectOption = async (query, options) => {
|
|
748
|
+
console.log(query);
|
|
749
|
+
options.forEach((opt, idx) => {
|
|
750
|
+
console.log(` [${opt.key}] ${opt.label}`);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
while (true) {
|
|
754
|
+
const answer = await ask(">", options[0].key);
|
|
755
|
+
const selected = options.find(o => o.key.toLowerCase() === answer.toLowerCase());
|
|
756
|
+
if (selected) return selected;
|
|
757
|
+
|
|
758
|
+
const byLabel = options.find(o => o.label.toLowerCase().includes(answer.toLowerCase()));
|
|
759
|
+
if (byLabel) return byLabel;
|
|
760
|
+
|
|
761
|
+
console.log("Pilihan tidak valid. Silakan coba lagi.");
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
(async () => {
|
|
766
|
+
// Animation lapeeh "L A P E H"
|
|
767
|
+
const frames = [
|
|
768
|
+
"██╗ █████╗ ██████╗ ███████╗██╗ ██╗",
|
|
769
|
+
"██║ ██╔══██╗██╔══██╗██╔════╝██║ ██║",
|
|
770
|
+
"██║ ███████║██████╔╝█████╗ ███████║",
|
|
771
|
+
"██║ ██╔══██║██╔═══╝ ██╔══╝ ██╔══██║",
|
|
772
|
+
"███████╗██║ ██║██║ ███████╗██║ ██║",
|
|
773
|
+
"╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝"
|
|
774
|
+
];
|
|
775
|
+
|
|
776
|
+
console.clear();
|
|
777
|
+
console.log('\n');
|
|
778
|
+
for (let i = 0; i < frames.length; i++) {
|
|
779
|
+
await new Promise(r => setTimeout(r, 100));
|
|
780
|
+
console.log(`\x1b[36m ${frames[i]}\x1b[0m`);
|
|
781
|
+
}
|
|
782
|
+
console.log('\n\x1b[36m L A P E H F R A M E W O R K\x1b[0m\n');
|
|
783
|
+
await new Promise(r => setTimeout(r, 800));
|
|
784
|
+
|
|
785
|
+
console.log(`🚀 Creating a new API lapeeh project in ${projectDir}...`);
|
|
786
|
+
fs.mkdirSync(projectDir);
|
|
787
|
+
|
|
788
|
+
const ignoreList = [
|
|
789
|
+
'node_modules', 'dist', '.git', '.env', 'bin', 'scripts',
|
|
790
|
+
'package-lock.json', '.DS_Store', 'prisma', 'website',
|
|
791
|
+
'init', 'test-local-run', 'coverage', 'doc', projectName
|
|
792
|
+
];
|
|
793
|
+
|
|
794
|
+
function copyDir(src, dest) {
|
|
795
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
796
|
+
for (const entry of entries) {
|
|
797
|
+
if (ignoreList.includes(entry.name)) continue;
|
|
798
|
+
const srcPath = path.join(src, entry.name);
|
|
799
|
+
const destPath = path.join(dest, entry.name);
|
|
800
|
+
|
|
801
|
+
// Clean storage/logs: skip everything except .gitkeep
|
|
802
|
+
// Check if we are inside storage/logs
|
|
803
|
+
const relPath = path.relative(templateDir, srcPath);
|
|
804
|
+
const isInLogs = relPath.includes(path.join('storage', 'logs')) || relPath.includes('storage/logs') || relPath.includes('storage\\logs');
|
|
805
|
+
|
|
806
|
+
if (isInLogs && !entry.isDirectory() && entry.name !== '.gitkeep') {
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (entry.isDirectory()) {
|
|
811
|
+
fs.mkdirSync(destPath);
|
|
812
|
+
copyDir(srcPath, destPath);
|
|
813
|
+
} else {
|
|
814
|
+
fs.copyFileSync(srcPath, destPath);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
console.log('\n📂 Copying template files...');
|
|
820
|
+
copyDir(templateDir, projectDir);
|
|
821
|
+
|
|
822
|
+
const gitignoreTemplate = path.join(projectDir, 'gitignore.template');
|
|
823
|
+
if (fs.existsSync(gitignoreTemplate)) {
|
|
824
|
+
fs.renameSync(gitignoreTemplate, path.join(projectDir, '.gitignore'));
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
console.log('⚙️ Configuring environment...');
|
|
828
|
+
const envExamplePath = path.join(projectDir, '.env.example');
|
|
829
|
+
const envPath = path.join(projectDir, '.env');
|
|
830
|
+
|
|
831
|
+
if (fs.existsSync(envExamplePath)) {
|
|
832
|
+
let envContent = fs.readFileSync(envExamplePath, 'utf8');
|
|
833
|
+
fs.writeFileSync(envPath, envContent);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
console.log('📝 Updating package.json...');
|
|
837
|
+
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
838
|
+
const packageJson = require(packageJsonPath);
|
|
839
|
+
packageJson.name = projectName;
|
|
840
|
+
|
|
841
|
+
const frameworkPackageJson = require(path.join(__dirname, '../package.json'));
|
|
842
|
+
if (__dirname.includes('node_modules')) {
|
|
843
|
+
packageJson.dependencies["lapeeh"] = `^${frameworkPackageJson.version}`;
|
|
844
|
+
} else {
|
|
845
|
+
const lapeehPath = path.resolve(__dirname, '..').replace(/\\/g, '/');
|
|
846
|
+
packageJson.dependencies["lapeeh"] = `file:${lapeehPath}`;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
packageJson.version = '1.0.0';
|
|
851
|
+
delete packageJson.bin;
|
|
852
|
+
delete packageJson.peerDependencies;
|
|
853
|
+
|
|
854
|
+
packageJson.scripts = {
|
|
855
|
+
...packageJson.scripts,
|
|
856
|
+
"dev": "lapeeh dev",
|
|
857
|
+
"start": "lapeeh start",
|
|
858
|
+
"build": "lapeeh build",
|
|
859
|
+
"start:prod": "lapeeh start"
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
// Remove scripts that depend on the scripts folder
|
|
863
|
+
const scriptsToRemove = ['first', 'generate:jwt', 'make:module', 'make:modul', 'config:clear', 'release'];
|
|
864
|
+
scriptsToRemove.forEach(script => {
|
|
865
|
+
delete packageJson.scripts[script];
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
869
|
+
|
|
870
|
+
// Update tsconfig.json for aliases
|
|
871
|
+
const tsconfigPath = path.join(projectDir, 'tsconfig.json');
|
|
872
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
873
|
+
try {
|
|
874
|
+
const tsconfig = require(tsconfigPath);
|
|
875
|
+
if (!tsconfig.compilerOptions) tsconfig.compilerOptions = {};
|
|
876
|
+
if (!tsconfig.compilerOptions.paths) tsconfig.compilerOptions.paths = {};
|
|
877
|
+
|
|
878
|
+
// Ensure @lapeeh/* points to the installed package
|
|
879
|
+
tsconfig.compilerOptions.paths["@lapeeh/*"] = ["./node_modules/@lapeeh/lapeeh/dist/lib/*"];
|
|
880
|
+
|
|
881
|
+
// Add ts-node configuration to allow compiling lapeeh in node_modules
|
|
882
|
+
tsconfig["ts-node"] = {
|
|
883
|
+
"ignore": ["node_modules/(?!@lapeeh)"]
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
887
|
+
} catch (e) {
|
|
888
|
+
console.warn('⚠️ Failed to update tsconfig.json aliases.');
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Removed Prisma base file handling
|
|
893
|
+
|
|
894
|
+
try {
|
|
895
|
+
await spin('Installing dependencies...', async () => {
|
|
896
|
+
await runCommand('npm install', projectDir);
|
|
897
|
+
});
|
|
898
|
+
} catch (e) {
|
|
899
|
+
console.error('❌ Error installing dependencies.');
|
|
900
|
+
console.error(e.message);
|
|
901
|
+
process.exit(1);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
try {
|
|
905
|
+
// Inline JWT Generation Logic
|
|
906
|
+
const crypto = require('crypto');
|
|
907
|
+
const secret = crypto.randomBytes(64).toString('hex');
|
|
908
|
+
|
|
909
|
+
let envContent = '';
|
|
910
|
+
if (fs.existsSync(envPath)) {
|
|
911
|
+
envContent = fs.readFileSync(envPath, 'utf8');
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (envContent.match(/^JWT_SECRET=/m)) {
|
|
915
|
+
envContent = envContent.replace(/^JWT_SECRET=.*/m, `JWT_SECRET="${secret}"`);
|
|
916
|
+
} else {
|
|
917
|
+
if (envContent && !envContent.endsWith('\n')) {
|
|
918
|
+
envContent += '\n';
|
|
919
|
+
}
|
|
920
|
+
envContent += `JWT_SECRET="${secret}"\n`;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
fs.writeFileSync(envPath, envContent);
|
|
924
|
+
console.log('✅ JWT Secret generated.');
|
|
925
|
+
} catch (e) {
|
|
926
|
+
console.warn('⚠️ Failed to generate JWT secret automatically.');
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Removed Prisma setup steps
|
|
930
|
+
|
|
931
|
+
console.log(`\n✅ Project ${projectName} created successfully!`);
|
|
932
|
+
rl.close();
|
|
933
|
+
})();
|
|
934
|
+
}
|