@avleon/cli 0.0.42 → 0.0.44
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/.github/workflows/publish.yml +69 -0
- package/dist/cli.js +53 -49
- package/dist/commands.js +10 -10
- package/dist/create.js +178 -148
- package/dist/scripts/serve.js +290 -0
- package/dist/serve.js +305 -0
- package/package.json +15 -8
- package/readme.md +66 -66
- package/stubs/app/app-controller.stub +11 -11
- package/stubs/app/app-minimal.stub +17 -17
- package/stubs/basic-controller.stub +7 -7
- package/stubs/config.stub +11 -11
- package/stubs/controllers/basic-controller.stub +12 -12
- package/stubs/controllers/orm-rest-controller.stub +52 -52
- package/stubs/controllers/rest-controller.stub +34 -34
- package/stubs/models/basic-model.stub +6 -6
- package/stubs/models/entity-model.stub +23 -23
- package/stubs/serve.stub +10 -10
- package/stubs/services/basic-service.stub +8 -8
- package/stubs/services/orm-rest-service.stub +73 -73
- package/stubs/services/rest-service.stub +71 -71
- package/tsconfig.json +16 -15
- package/pnpm-workspace.yaml +0 -2
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const chokidar = require("chokidar");
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const { transformFileSync } = require("@swc/core");
|
|
7
|
+
const SRC_DIR = path.resolve(process.cwd(), "../src");
|
|
8
|
+
const DIST_DIR = path.resolve(process.cwd(), "../dist");
|
|
9
|
+
const ENTRY_FILE = path.join(DIST_DIR, "server.js");
|
|
10
|
+
let nodeProcess = null;
|
|
11
|
+
let isRestarting = false;
|
|
12
|
+
// Unified logging with colors and timestamps
|
|
13
|
+
function log(category, message, color = '\x1b[37m') {
|
|
14
|
+
const timestamp = new Date().toTimeString().split(' ')[0];
|
|
15
|
+
console.log(`\x1b[90m[${timestamp}]\x1b[0m ${color}[${category}]\x1b[0m ${message}`);
|
|
16
|
+
}
|
|
17
|
+
function logCompile(message) { log('SWC', message, '\x1b[36m'); }
|
|
18
|
+
function logServer(message) { log('SERVER', message, '\x1b[32m'); }
|
|
19
|
+
function logWatch(message) { log('WATCH', message, '\x1b[33m'); }
|
|
20
|
+
function logError(category, message) { log(category, message, '\x1b[31m'); }
|
|
21
|
+
// compile single TS file
|
|
22
|
+
function compileFile(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
const rel = path.relative(SRC_DIR, filePath);
|
|
25
|
+
const outFile = path.join(DIST_DIR, rel).replace(/\.ts$/, ".js");
|
|
26
|
+
const { code } = transformFileSync(filePath, {
|
|
27
|
+
jsc: {
|
|
28
|
+
parser: { syntax: "typescript", decorators: true },
|
|
29
|
+
target: "es2022",
|
|
30
|
+
transform: { decoratorMetadata: true },
|
|
31
|
+
},
|
|
32
|
+
module: { type: "commonjs" },
|
|
33
|
+
sourceMaps: false,
|
|
34
|
+
});
|
|
35
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
36
|
+
fs.writeFileSync(outFile, code, "utf8");
|
|
37
|
+
logCompile(`✓ ${rel}`);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
logError('SWC', `✗ ${path.relative(SRC_DIR, filePath)} - ${error.message}`);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// compile all TS
|
|
46
|
+
function compileAll(dir = SRC_DIR) {
|
|
47
|
+
if (!fs.existsSync(dir)) {
|
|
48
|
+
logError('SWC', `Source directory ${dir} does not exist`);
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
let compiled = 0;
|
|
52
|
+
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
53
|
+
for (const f of files) {
|
|
54
|
+
const full = path.join(dir, f.name);
|
|
55
|
+
if (f.isDirectory()) {
|
|
56
|
+
compiled += compileAll(full);
|
|
57
|
+
}
|
|
58
|
+
else if (f.name.endsWith(".ts")) {
|
|
59
|
+
if (f.name.includes('.test.') || f.name.includes('.spec.')) {
|
|
60
|
+
logWatch(`⏭️ Skipping test file: ${path.relative(SRC_DIR, full)}`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (compileFile(full))
|
|
64
|
+
compiled++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return compiled;
|
|
68
|
+
}
|
|
69
|
+
// start Node with integrated output
|
|
70
|
+
function startNode() {
|
|
71
|
+
if (nodeProcess) {
|
|
72
|
+
logServer("⚠️ Process already running, skipping start");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (isRestarting) {
|
|
76
|
+
// Reset the flag when actually starting
|
|
77
|
+
isRestarting = false;
|
|
78
|
+
}
|
|
79
|
+
if (!fs.existsSync(ENTRY_FILE)) {
|
|
80
|
+
logError('SERVER', `Entry file ${ENTRY_FILE} does not exist`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
logServer("🚀 Starting server...");
|
|
84
|
+
try {
|
|
85
|
+
nodeProcess = spawn("node", [ENTRY_FILE], {
|
|
86
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
87
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
88
|
+
detached: false // Ensure process stays attached
|
|
89
|
+
});
|
|
90
|
+
// Forward stdout with server prefix
|
|
91
|
+
nodeProcess.stdout?.on('data', (data) => {
|
|
92
|
+
const lines = data.toString().split('\n').filter(line => line.trim());
|
|
93
|
+
lines.forEach(line => logServer(line));
|
|
94
|
+
});
|
|
95
|
+
// Forward stderr with server prefix
|
|
96
|
+
nodeProcess.stderr?.on('data', (data) => {
|
|
97
|
+
const lines = data.toString().split('\n').filter(line => line.trim());
|
|
98
|
+
lines.forEach(line => logError('SERVER', line));
|
|
99
|
+
});
|
|
100
|
+
nodeProcess.on("exit", (code, signal) => {
|
|
101
|
+
const wasRestarting = isRestarting;
|
|
102
|
+
nodeProcess = null;
|
|
103
|
+
if (wasRestarting) {
|
|
104
|
+
// This is expected during restart
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (code && code !== 0) {
|
|
108
|
+
logError('SERVER', `Process exited with code ${code}`);
|
|
109
|
+
}
|
|
110
|
+
else if (signal) {
|
|
111
|
+
logServer(`Process terminated by signal ${signal}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// logServer("Process exited normally");
|
|
115
|
+
}
|
|
116
|
+
isRestarting = false;
|
|
117
|
+
});
|
|
118
|
+
nodeProcess.on("error", (error) => {
|
|
119
|
+
logError('SERVER', `Process error: ${error.message}`);
|
|
120
|
+
nodeProcess = null;
|
|
121
|
+
isRestarting = false;
|
|
122
|
+
});
|
|
123
|
+
// Add a small delay to let the server start
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
if (nodeProcess) {
|
|
126
|
+
logServer("✅ Server started successfully");
|
|
127
|
+
}
|
|
128
|
+
}, 500);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
logError('SERVER', `Failed to start server: ${error.message}`);
|
|
132
|
+
nodeProcess = null;
|
|
133
|
+
isRestarting = false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// kill Node
|
|
137
|
+
function killNode() {
|
|
138
|
+
return new Promise((resolve) => {
|
|
139
|
+
if (!nodeProcess)
|
|
140
|
+
return resolve();
|
|
141
|
+
logServer("🛑 Stopping server...");
|
|
142
|
+
let resolved = false;
|
|
143
|
+
const cleanup = () => {
|
|
144
|
+
if (!resolved) {
|
|
145
|
+
resolved = true;
|
|
146
|
+
nodeProcess = null;
|
|
147
|
+
logServer("✓ Server stopped");
|
|
148
|
+
resolve();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
nodeProcess.once("exit", cleanup);
|
|
152
|
+
nodeProcess.once("close", cleanup);
|
|
153
|
+
// Try graceful shutdown first
|
|
154
|
+
try {
|
|
155
|
+
nodeProcess.kill("SIGTERM");
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
logError('SERVER', `Error sending SIGTERM: ${error.message}`);
|
|
159
|
+
}
|
|
160
|
+
// Force kill after 1 second if still running
|
|
161
|
+
const forceKillTimeout = setTimeout(() => {
|
|
162
|
+
if (nodeProcess && !resolved) {
|
|
163
|
+
logServer("⚠️ Force killing server...");
|
|
164
|
+
try {
|
|
165
|
+
nodeProcess.kill("SIGKILL");
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
logError('SERVER', `Error sending SIGKILL: ${error.message}`);
|
|
169
|
+
}
|
|
170
|
+
// Ensure cleanup happens even if SIGKILL fails
|
|
171
|
+
setTimeout(cleanup, 100);
|
|
172
|
+
}
|
|
173
|
+
}, 1000);
|
|
174
|
+
// Fallback timeout - always resolve within 3 seconds
|
|
175
|
+
setTimeout(() => {
|
|
176
|
+
clearTimeout(forceKillTimeout);
|
|
177
|
+
if (!resolved) {
|
|
178
|
+
logError('SERVER', 'Force cleanup - process may still be running');
|
|
179
|
+
nodeProcess = null;
|
|
180
|
+
resolved = true;
|
|
181
|
+
resolve();
|
|
182
|
+
}
|
|
183
|
+
}, 3000);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// restart Node
|
|
187
|
+
async function restartNode() {
|
|
188
|
+
if (isRestarting) {
|
|
189
|
+
logServer("⏳ Already restarting, skipping...");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
isRestarting = true;
|
|
193
|
+
logServer("🔄 Restarting server...");
|
|
194
|
+
try {
|
|
195
|
+
await killNode();
|
|
196
|
+
// Add delay to ensure process is fully cleaned up
|
|
197
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
198
|
+
startNode();
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
logError('SERVER', `Restart error: ${error.message}`);
|
|
202
|
+
isRestarting = false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// watch src
|
|
206
|
+
function watch() {
|
|
207
|
+
logWatch(`👀 Watching ${path.relative(process.cwd(), SRC_DIR)} for changes...`);
|
|
208
|
+
// Add debug logging
|
|
209
|
+
logWatch(`📂 Source directory: ${SRC_DIR}`);
|
|
210
|
+
logWatch(`📂 Output directory: ${DIST_DIR}`);
|
|
211
|
+
const watcher = chokidar.watch(SRC_DIR, {
|
|
212
|
+
ignoreInitial: true,
|
|
213
|
+
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
|
214
|
+
persistent: true,
|
|
215
|
+
followSymlinks: false,
|
|
216
|
+
awaitWriteFinish: {
|
|
217
|
+
stabilityThreshold: 100,
|
|
218
|
+
pollInterval: 50
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
// Debug: log when watcher is ready
|
|
222
|
+
watcher.on('ready', () => {
|
|
223
|
+
logWatch('✅ Initial scan complete. Ready for changes');
|
|
224
|
+
});
|
|
225
|
+
watcher.on("add", (file) => {
|
|
226
|
+
const rel = path.relative(SRC_DIR, file);
|
|
227
|
+
logWatch(`📄 File added: ${rel}`);
|
|
228
|
+
if (compileFile(file)) {
|
|
229
|
+
// For new files, restart if it's a .ts file
|
|
230
|
+
if (file.endsWith('.ts')) {
|
|
231
|
+
setTimeout(() => restartNode(), 100);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
watcher.on("change", async (file) => {
|
|
236
|
+
const rel = path.relative(SRC_DIR, file);
|
|
237
|
+
logWatch(`📝 File changed: ${rel}`);
|
|
238
|
+
if (file.endsWith('.ts') && compileFile(file)) {
|
|
239
|
+
await restartNode();
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
watcher.on("unlink", async (file) => {
|
|
243
|
+
const rel = path.relative(SRC_DIR, file);
|
|
244
|
+
logWatch(`🗑️ File removed: ${rel}`);
|
|
245
|
+
const outFile = path
|
|
246
|
+
.join(DIST_DIR, path.relative(SRC_DIR, file))
|
|
247
|
+
.replace(/\.ts$/, ".js");
|
|
248
|
+
if (fs.existsSync(outFile)) {
|
|
249
|
+
fs.unlinkSync(outFile);
|
|
250
|
+
logCompile(`✗ Removed ${path.relative(DIST_DIR, outFile)}`);
|
|
251
|
+
}
|
|
252
|
+
await restartNode();
|
|
253
|
+
});
|
|
254
|
+
// Debug: log all watcher events
|
|
255
|
+
watcher.on('all', (eventName, path) => {
|
|
256
|
+
if (!['add', 'change', 'unlink'].includes(eventName)) {
|
|
257
|
+
logWatch(`🔍 Debug: ${eventName} -> ${path}`);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
watcher.on("error", (error) => {
|
|
261
|
+
logError('WATCH', `Watcher error: ${error.message}`);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
// Graceful shutdown
|
|
265
|
+
process.on("SIGINT", async () => {
|
|
266
|
+
console.log();
|
|
267
|
+
logWatch("🛑 Shutting down...");
|
|
268
|
+
await killNode();
|
|
269
|
+
process.exit(0);
|
|
270
|
+
});
|
|
271
|
+
process.on("SIGTERM", async () => {
|
|
272
|
+
console.log();
|
|
273
|
+
logWatch("🛑 Shutting down...");
|
|
274
|
+
await killNode();
|
|
275
|
+
process.exit(0);
|
|
276
|
+
});
|
|
277
|
+
// Main execution
|
|
278
|
+
(async () => {
|
|
279
|
+
console.log('\x1b[1m\x1b[36m🔧 Development Server\x1b[0m');
|
|
280
|
+
console.log('━'.repeat(50));
|
|
281
|
+
logWatch("🚀 Starting development server...");
|
|
282
|
+
const compiled = compileAll();
|
|
283
|
+
if (compiled > 0) {
|
|
284
|
+
logCompile(`✅ Compiled ${compiled} file${compiled !== 1 ? 's' : ''}`);
|
|
285
|
+
}
|
|
286
|
+
startNode();
|
|
287
|
+
watch();
|
|
288
|
+
console.log('━'.repeat(50));
|
|
289
|
+
logWatch("✨ Ready! Press Ctrl+C to stop");
|
|
290
|
+
})();
|
package/dist/serve.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const chokidar = require("chokidar");
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const { transformFileSync } = require("@swc/core");
|
|
7
|
+
const SRC_DIR = path.resolve(process.cwd(), "./src");
|
|
8
|
+
const DIST_DIR = path.resolve(process.cwd(), "./dist");
|
|
9
|
+
const ENTRY_FILE = path.join(DIST_DIR, "server.js");
|
|
10
|
+
let nodeProcess = null;
|
|
11
|
+
let isRestarting = false;
|
|
12
|
+
// Color constants for better type safety
|
|
13
|
+
const Colors = {
|
|
14
|
+
RESET: '\x1b[0m',
|
|
15
|
+
DIM: '\x1b[90m',
|
|
16
|
+
WHITE: '\x1b[37m',
|
|
17
|
+
CYAN: '\x1b[36m',
|
|
18
|
+
GREEN: '\x1b[32m',
|
|
19
|
+
YELLOW: '\x1b[33m',
|
|
20
|
+
RED: '\x1b[31m',
|
|
21
|
+
BOLD: '\x1b[1m'
|
|
22
|
+
};
|
|
23
|
+
// Unified logging with colors and timestamps
|
|
24
|
+
function log(category, message, color = Colors.WHITE) {
|
|
25
|
+
const timestamp = new Date().toTimeString().split(' ')[0];
|
|
26
|
+
console.log(`${Colors.DIM}[${timestamp}]${Colors.RESET} ${color}[${category}]${Colors.RESET} ${message}`);
|
|
27
|
+
}
|
|
28
|
+
function logCompile(message) {
|
|
29
|
+
log('COMPILE', message, Colors.CYAN);
|
|
30
|
+
}
|
|
31
|
+
function logServer(message) {
|
|
32
|
+
log('AVLEON', message, Colors.GREEN);
|
|
33
|
+
}
|
|
34
|
+
function logWatch(message) {
|
|
35
|
+
log('WATCH', message, Colors.YELLOW);
|
|
36
|
+
}
|
|
37
|
+
function logError(category, message) {
|
|
38
|
+
log(category, message, Colors.RED);
|
|
39
|
+
}
|
|
40
|
+
// Helper function to check if file is a test file
|
|
41
|
+
function isTestFile(filePath) {
|
|
42
|
+
const filename = path.basename(filePath);
|
|
43
|
+
return filename.includes('.test.') || filename.includes('.spec.');
|
|
44
|
+
}
|
|
45
|
+
// compile single TS file
|
|
46
|
+
function compileFile(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
const rel = path.relative(SRC_DIR, filePath);
|
|
49
|
+
const outFile = path.join(DIST_DIR, rel).replace(/\.ts$/, ".js");
|
|
50
|
+
const result = transformFileSync(filePath, {
|
|
51
|
+
jsc: {
|
|
52
|
+
parser: { syntax: "typescript", decorators: true },
|
|
53
|
+
target: "es2022",
|
|
54
|
+
transform: { decoratorMetadata: true },
|
|
55
|
+
},
|
|
56
|
+
module: { type: "commonjs" },
|
|
57
|
+
sourceMaps: false,
|
|
58
|
+
});
|
|
59
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
60
|
+
fs.writeFileSync(outFile, result.code, "utf8");
|
|
61
|
+
//logCompile(`✓ ${rel}`);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
66
|
+
logError('COMPILE', `✗ ${path.relative(SRC_DIR, filePath)} - ${errorMessage}`);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// compile all TS (excluding test files)
|
|
71
|
+
function compileAll(dir = SRC_DIR) {
|
|
72
|
+
if (!fs.existsSync(dir)) {
|
|
73
|
+
logError('COMPILE', `Source directory ${dir} does not exist`);
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
let compiled = 0;
|
|
77
|
+
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
78
|
+
for (const f of files) {
|
|
79
|
+
const full = path.join(dir, f.name);
|
|
80
|
+
if (f.isDirectory()) {
|
|
81
|
+
compiled += compileAll(full);
|
|
82
|
+
}
|
|
83
|
+
else if (f.name.endsWith(".ts")) {
|
|
84
|
+
// Skip test files
|
|
85
|
+
if (isTestFile(full)) {
|
|
86
|
+
// logWatch(`⏭️ Skipping test file: ${path.relative(SRC_DIR, full)}`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (compileFile(full)) {
|
|
90
|
+
compiled++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return compiled;
|
|
95
|
+
}
|
|
96
|
+
// start Node with integrated output
|
|
97
|
+
function startNode() {
|
|
98
|
+
if (nodeProcess) {
|
|
99
|
+
logServer("⚠️ Process already running, skipping start");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (isRestarting) {
|
|
103
|
+
// Reset the flag when actually starting
|
|
104
|
+
isRestarting = false;
|
|
105
|
+
}
|
|
106
|
+
if (!fs.existsSync(ENTRY_FILE)) {
|
|
107
|
+
logError('AVLEON', `Entry file ${ENTRY_FILE} does not exist`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
logServer("🚀 Starting server...");
|
|
111
|
+
try {
|
|
112
|
+
nodeProcess = spawn("node", [ENTRY_FILE], {
|
|
113
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
114
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
115
|
+
detached: false // Ensure process stays attached
|
|
116
|
+
});
|
|
117
|
+
// Forward stdout with server prefix
|
|
118
|
+
nodeProcess.stdout?.on('data', (data) => {
|
|
119
|
+
const lines = data.toString().split('\n').filter((line) => line.trim());
|
|
120
|
+
lines.forEach((line) => logServer(line));
|
|
121
|
+
});
|
|
122
|
+
// Forward stderr with server prefix
|
|
123
|
+
nodeProcess.stderr?.on('data', (data) => {
|
|
124
|
+
const lines = data.toString().split('\n').filter((line) => line.trim());
|
|
125
|
+
lines.forEach((line) => logError('AVLEON', line));
|
|
126
|
+
});
|
|
127
|
+
nodeProcess.on("exit", (code, signal) => {
|
|
128
|
+
const wasRestarting = isRestarting;
|
|
129
|
+
nodeProcess = null;
|
|
130
|
+
if (wasRestarting) {
|
|
131
|
+
// This is expected during restart
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (code && code !== 0) {
|
|
135
|
+
logError('AVLEON', `Process exited with code ${code}`);
|
|
136
|
+
}
|
|
137
|
+
else if (signal) {
|
|
138
|
+
logServer(`Process terminated by signal ${signal}`);
|
|
139
|
+
}
|
|
140
|
+
isRestarting = false;
|
|
141
|
+
});
|
|
142
|
+
nodeProcess.on("error", (error) => {
|
|
143
|
+
logError('AVLEON', `Process error: ${error.message}`);
|
|
144
|
+
nodeProcess = null;
|
|
145
|
+
isRestarting = false;
|
|
146
|
+
});
|
|
147
|
+
// Add a small delay to let the server start
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
if (nodeProcess) {
|
|
150
|
+
logServer("✅ Server started successfully");
|
|
151
|
+
}
|
|
152
|
+
}, 500);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
156
|
+
logError('AVLEON', `Failed to start server: ${errorMessage}`);
|
|
157
|
+
nodeProcess = null;
|
|
158
|
+
isRestarting = false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// kill Node
|
|
162
|
+
function killNode() {
|
|
163
|
+
return new Promise((resolve) => {
|
|
164
|
+
if (!nodeProcess)
|
|
165
|
+
return resolve();
|
|
166
|
+
logServer("🛑 Stopping server...");
|
|
167
|
+
let resolved = false;
|
|
168
|
+
const cleanup = () => {
|
|
169
|
+
if (!resolved) {
|
|
170
|
+
resolved = true;
|
|
171
|
+
nodeProcess = null;
|
|
172
|
+
logServer("✓ Server stopped");
|
|
173
|
+
resolve();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
nodeProcess.once("exit", cleanup);
|
|
177
|
+
nodeProcess.once("close", cleanup);
|
|
178
|
+
// Try graceful shutdown first
|
|
179
|
+
try {
|
|
180
|
+
nodeProcess.kill("SIGTERM");
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
184
|
+
logError('AVLEON', `Error sending SIGTERM: ${errorMessage}`);
|
|
185
|
+
}
|
|
186
|
+
// Force kill after 1 second if still running
|
|
187
|
+
const forceKillTimeout = setTimeout(() => {
|
|
188
|
+
if (nodeProcess && !resolved) {
|
|
189
|
+
logServer("⚠️ Force killing server...");
|
|
190
|
+
try {
|
|
191
|
+
nodeProcess.kill("SIGKILL");
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
195
|
+
logError('AVLEON', `Error sending SIGKILL: ${errorMessage}`);
|
|
196
|
+
}
|
|
197
|
+
setTimeout(cleanup, 100);
|
|
198
|
+
}
|
|
199
|
+
}, 1000);
|
|
200
|
+
// Fallback timeout - always resolve within 3 seconds
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
clearTimeout(forceKillTimeout);
|
|
203
|
+
if (!resolved) {
|
|
204
|
+
logError('AVLEON', 'Force cleanup - process may still be running');
|
|
205
|
+
nodeProcess = null;
|
|
206
|
+
resolved = true;
|
|
207
|
+
resolve();
|
|
208
|
+
}
|
|
209
|
+
}, 3000);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
// restart Node
|
|
213
|
+
async function restartNode() {
|
|
214
|
+
if (isRestarting) {
|
|
215
|
+
logServer("⏳ Already restarting, skipping...");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
isRestarting = true;
|
|
219
|
+
logServer("🔄 Restarting server...");
|
|
220
|
+
try {
|
|
221
|
+
await killNode();
|
|
222
|
+
// Add delay to ensure process is fully cleaned up
|
|
223
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
224
|
+
startNode();
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
228
|
+
logError('AVLEON', `Restart error: ${errorMessage}`);
|
|
229
|
+
isRestarting = false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// watch src
|
|
233
|
+
function watch() {
|
|
234
|
+
const watcher = chokidar.watch(SRC_DIR, {
|
|
235
|
+
ignoreInitial: true,
|
|
236
|
+
ignored: [
|
|
237
|
+
/(^|[\/\\])\../, // ignore dotfiles
|
|
238
|
+
/\.(test|spec)\.(ts|js)$/ // ignore test files
|
|
239
|
+
],
|
|
240
|
+
persistent: true,
|
|
241
|
+
followSymlinks: false,
|
|
242
|
+
awaitWriteFinish: {
|
|
243
|
+
stabilityThreshold: 100,
|
|
244
|
+
pollInterval: 50
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
// Debug: log when watcher is ready
|
|
248
|
+
// watcher.on('ready', () => {
|
|
249
|
+
// logWatch('✅ Initial scan complete. Ready for changes');
|
|
250
|
+
// });
|
|
251
|
+
watcher.on("add", (file) => {
|
|
252
|
+
if (file.endsWith('.ts') && !isTestFile(file)) {
|
|
253
|
+
if (compileFile(file)) {
|
|
254
|
+
setTimeout(() => restartNode(), 100);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
watcher.on("change", async (file) => {
|
|
259
|
+
if (file.endsWith('.ts') && !isTestFile(file)) {
|
|
260
|
+
if (compileFile(file)) {
|
|
261
|
+
await restartNode();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
watcher.on("unlink", async (file) => {
|
|
266
|
+
if (!isTestFile(file)) {
|
|
267
|
+
const outFile = path
|
|
268
|
+
.join(DIST_DIR, path.relative(SRC_DIR, file))
|
|
269
|
+
.replace(/\.ts$/, ".js");
|
|
270
|
+
if (fs.existsSync(outFile)) {
|
|
271
|
+
fs.unlinkSync(outFile);
|
|
272
|
+
}
|
|
273
|
+
await restartNode();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
// Debug: log all watcher events
|
|
277
|
+
watcher.on('all', (eventName, filePath) => {
|
|
278
|
+
if (!['add', 'change', 'unlink'].includes(eventName)) {
|
|
279
|
+
//logWatch(`🔍 Debug: ${eventName} -> ${filePath}`);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
watcher.on("error", (error) => {
|
|
283
|
+
logError('WATCH', `Watcher error: ${error.message}`);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
// Graceful shutdown
|
|
287
|
+
process.on("SIGINT", async () => {
|
|
288
|
+
await killNode();
|
|
289
|
+
process.exit(0);
|
|
290
|
+
});
|
|
291
|
+
process.on("SIGTERM", async () => {
|
|
292
|
+
await killNode();
|
|
293
|
+
process.exit(0);
|
|
294
|
+
});
|
|
295
|
+
// Main execution
|
|
296
|
+
(async () => {
|
|
297
|
+
console.log(`${Colors.BOLD}${Colors.CYAN}🔧 Development Server${Colors.RESET}`);
|
|
298
|
+
console.log('━'.repeat(50));
|
|
299
|
+
logWatch("🚀 Starting development server...");
|
|
300
|
+
const compiled = compileAll();
|
|
301
|
+
startNode();
|
|
302
|
+
watch();
|
|
303
|
+
console.log('━'.repeat(50));
|
|
304
|
+
logWatch("✨ Ready! Press Ctrl+C to stop");
|
|
305
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avleon/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.44",
|
|
4
4
|
"bin": {
|
|
5
5
|
"avleon": "./dist/cli.js"
|
|
6
6
|
},
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"types": "dist/index.d.ts",
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "rimraf dist & tsc",
|
|
12
|
-
"clean": "rm -rf ./dist",
|
|
13
|
-
"prepublish": "npm run build",
|
|
14
|
-
"watch": "tsc-watch"
|
|
15
|
-
},
|
|
16
10
|
"keywords": [
|
|
17
11
|
"cli",
|
|
18
12
|
"backend-api"
|
|
@@ -20,9 +14,13 @@
|
|
|
20
14
|
"author": "Tareq Hossain",
|
|
21
15
|
"license": "ISC",
|
|
22
16
|
"devDependencies": {
|
|
17
|
+
"@swc/cli": "^0.7.8",
|
|
18
|
+
"@swc/core": "^1.13.5",
|
|
23
19
|
"@types/fs-extra": "^11.0.4",
|
|
24
20
|
"@types/lodash": "^4.17.16",
|
|
25
21
|
"@types/node": "^22.9.3",
|
|
22
|
+
"chokidar": "^4.0.3",
|
|
23
|
+
"concurrently": "^9.2.1",
|
|
26
24
|
"nodemon": "^3.1.7",
|
|
27
25
|
"tsc-watch": "^6.2.1",
|
|
28
26
|
"tsup": "^8.4.0",
|
|
@@ -40,5 +38,14 @@
|
|
|
40
38
|
"repository": {
|
|
41
39
|
"type": "git",
|
|
42
40
|
"url": "git+https://github.com/avleonjs/avleon-cli"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "rimraf dist & tsc",
|
|
47
|
+
"clean": "rm -rf ./dist",
|
|
48
|
+
"prepublish": "npm run build",
|
|
49
|
+
"watch": "tsc-watch"
|
|
43
50
|
}
|
|
44
|
-
}
|
|
51
|
+
}
|