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