neex 0.6.54 → 0.6.55
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/dist/src/commands/start-commands.js +17 -19
- package/dist/src/start-manager.js +140 -86
- package/package.json +1 -1
|
@@ -13,10 +13,10 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
13
13
|
const os_1 = __importDefault(require("os"));
|
|
14
14
|
function addStartCommands(program) {
|
|
15
15
|
let startManager = null;
|
|
16
|
-
//
|
|
16
|
+
// Production start command
|
|
17
17
|
program
|
|
18
18
|
.command('start [file]')
|
|
19
|
-
.description('Start production application
|
|
19
|
+
.description('Start production application')
|
|
20
20
|
.option('-d, --dir <directory>', 'Working directory', process.cwd())
|
|
21
21
|
.option('-e, --env <file>', 'Environment file to load', '.env')
|
|
22
22
|
.option('-p, --port <port>', 'Port number', parseInt)
|
|
@@ -32,7 +32,7 @@ function addStartCommands(program) {
|
|
|
32
32
|
.option('--node-args <args>', 'Additional Node.js arguments')
|
|
33
33
|
.action(async (file, options) => {
|
|
34
34
|
try {
|
|
35
|
-
const targetFile = file || 'dist/
|
|
35
|
+
const targetFile = file || 'dist/server.js';
|
|
36
36
|
let resolvedFile = path_1.default.resolve(options.dir, targetFile);
|
|
37
37
|
// Auto-detect main file if not found
|
|
38
38
|
if (!fs_1.default.existsSync(resolvedFile)) {
|
|
@@ -53,12 +53,13 @@ function addStartCommands(program) {
|
|
|
53
53
|
const commonLocations = [
|
|
54
54
|
'dist/server.js',
|
|
55
55
|
'dist/app.js',
|
|
56
|
-
'
|
|
56
|
+
'dist/index.js',
|
|
57
57
|
'build/server.js',
|
|
58
|
-
'
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'
|
|
58
|
+
'build/app.js',
|
|
59
|
+
'build/index.js',
|
|
60
|
+
'server.js',
|
|
61
|
+
'app.js',
|
|
62
|
+
'index.js'
|
|
62
63
|
];
|
|
63
64
|
let found = false;
|
|
64
65
|
for (const location of commonLocations) {
|
|
@@ -85,7 +86,7 @@ function addStartCommands(program) {
|
|
|
85
86
|
throw new Error(`Application file not found: ${resolvedFile}`);
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
|
-
//
|
|
89
|
+
// Environment detection
|
|
89
90
|
const isDevelopment = options.watch || process.env.NODE_ENV === 'development';
|
|
90
91
|
const isProduction = !isDevelopment;
|
|
91
92
|
const cpuCount = os_1.default.cpus().length;
|
|
@@ -93,17 +94,17 @@ function addStartCommands(program) {
|
|
|
93
94
|
let workers = options.workers;
|
|
94
95
|
if (!workers) {
|
|
95
96
|
if (isDevelopment) {
|
|
96
|
-
workers = 1;
|
|
97
|
+
workers = 1;
|
|
97
98
|
}
|
|
98
99
|
else {
|
|
99
|
-
// For production, use CPU count but cap at
|
|
100
|
-
workers = Math.min(cpuCount,
|
|
100
|
+
// For production, use CPU count but cap at reasonable limit
|
|
101
|
+
workers = Math.min(cpuCount, 4); // Reduced from 8 to 4 for better resource management
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
|
-
const healthCheck = options.health !== false
|
|
104
|
+
const healthCheck = options.health !== false;
|
|
104
105
|
const defaultPort = parseInt(process.env.PORT || '8000');
|
|
105
106
|
const port = options.port || defaultPort;
|
|
106
|
-
//
|
|
107
|
+
// Set NODE_ENV if not already set
|
|
107
108
|
if (!process.env.NODE_ENV) {
|
|
108
109
|
process.env.NODE_ENV = isProduction ? 'production' : 'development';
|
|
109
110
|
}
|
|
@@ -134,7 +135,7 @@ function addStartCommands(program) {
|
|
|
134
135
|
watch: options.watch,
|
|
135
136
|
maxMemory: options.maxMemory || (isProduction ? '1G' : undefined),
|
|
136
137
|
maxCrashes: isProduction ? 3 : 10,
|
|
137
|
-
restartDelay: isProduction ?
|
|
138
|
+
restartDelay: isProduction ? 1000 : 500,
|
|
138
139
|
healthCheck,
|
|
139
140
|
healthPort: options.healthPort,
|
|
140
141
|
gracefulTimeout: options.gracefulTimeout,
|
|
@@ -162,14 +163,13 @@ function addStartCommands(program) {
|
|
|
162
163
|
startManager = null;
|
|
163
164
|
}
|
|
164
165
|
catch (error) {
|
|
165
|
-
// Ignore cleanup errors but log in verbose mode
|
|
166
166
|
if (process.env.VERBOSE) {
|
|
167
167
|
console.error('Cleanup error:', error);
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
|
-
//
|
|
172
|
+
// Signal handling
|
|
173
173
|
const handleExit = (signal) => {
|
|
174
174
|
if (startManager) {
|
|
175
175
|
console.log(`\n${chalk_1.default.yellow(figures_1.default.warning)} Received ${signal}, shutting down gracefully...`);
|
|
@@ -183,11 +183,9 @@ function addStartCommands(program) {
|
|
|
183
183
|
process.exit(0);
|
|
184
184
|
}
|
|
185
185
|
};
|
|
186
|
-
// Register signal handlers
|
|
187
186
|
process.on('SIGINT', () => handleExit('SIGINT'));
|
|
188
187
|
process.on('SIGTERM', () => handleExit('SIGTERM'));
|
|
189
188
|
process.on('SIGUSR2', () => handleExit('SIGUSR2'));
|
|
190
|
-
// Handle uncaught exceptions
|
|
191
189
|
process.on('uncaughtException', (error) => {
|
|
192
190
|
console.error(`${chalk_1.default.red(figures_1.default.cross)} Uncaught Exception:`, error);
|
|
193
191
|
cleanupStart().then(() => {
|
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.StartManager = void 0;
|
|
7
|
-
// src/start-manager.ts - Fixed
|
|
7
|
+
// src/start-manager.ts - Fixed production start manager
|
|
8
8
|
const child_process_1 = require("child_process");
|
|
9
9
|
const chokidar_1 = require("chokidar");
|
|
10
10
|
const logger_manager_js_1 = require("./logger-manager.js");
|
|
@@ -23,6 +23,7 @@ class StartManager {
|
|
|
23
23
|
this.isShuttingDown = false;
|
|
24
24
|
this.totalRestarts = 0;
|
|
25
25
|
this.envLoaded = false;
|
|
26
|
+
this.masterProcess = null;
|
|
26
27
|
this.options = options;
|
|
27
28
|
this.startTime = new Date();
|
|
28
29
|
this.debouncedRestart = (0, lodash_1.debounce)(this.restartAll.bind(this), options.restartDelay);
|
|
@@ -96,58 +97,110 @@ class StartManager {
|
|
|
96
97
|
}
|
|
97
98
|
return args;
|
|
98
99
|
}
|
|
100
|
+
async startSingleProcess() {
|
|
101
|
+
const nodeArgs = this.getNodeArgs();
|
|
102
|
+
const port = this.options.port || 8000;
|
|
103
|
+
const env = {
|
|
104
|
+
...process.env,
|
|
105
|
+
NODE_ENV: process.env.NODE_ENV || 'production',
|
|
106
|
+
PORT: port.toString(),
|
|
107
|
+
FORCE_COLOR: this.options.color ? '1' : '0',
|
|
108
|
+
NODE_OPTIONS: '--no-deprecation'
|
|
109
|
+
};
|
|
110
|
+
this.masterProcess = (0, child_process_1.fork)(this.options.file, [], {
|
|
111
|
+
cwd: this.options.workingDir,
|
|
112
|
+
env,
|
|
113
|
+
execArgv: nodeArgs,
|
|
114
|
+
silent: false // Let the process handle its own output
|
|
115
|
+
});
|
|
116
|
+
this.masterProcess.on('error', (error) => {
|
|
117
|
+
this.log(`Process error: ${error.message}`, 'error');
|
|
118
|
+
});
|
|
119
|
+
this.masterProcess.on('exit', (code, signal) => {
|
|
120
|
+
if (!this.isShuttingDown && code !== 0 && signal !== 'SIGTERM') {
|
|
121
|
+
this.log(`Process crashed (code: ${code}, signal: ${signal})`, 'error');
|
|
122
|
+
this.totalRestarts++;
|
|
123
|
+
if (this.totalRestarts < this.options.maxCrashes) {
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
this.startSingleProcess();
|
|
126
|
+
}, this.options.restartDelay);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.log(`Max crashes reached (${this.options.maxCrashes}), not restarting`, 'error');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// Wait for process to be ready
|
|
134
|
+
await new Promise((resolve, reject) => {
|
|
135
|
+
const timeout = setTimeout(() => {
|
|
136
|
+
resolve(); // Don't reject, assume it's ready
|
|
137
|
+
}, 5000);
|
|
138
|
+
this.masterProcess.on('message', (message) => {
|
|
139
|
+
if (message && message.type === 'ready') {
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
resolve();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
this.masterProcess.on('error', (error) => {
|
|
145
|
+
clearTimeout(timeout);
|
|
146
|
+
reject(error);
|
|
147
|
+
});
|
|
148
|
+
this.masterProcess.on('exit', (code) => {
|
|
149
|
+
clearTimeout(timeout);
|
|
150
|
+
if (code !== 0) {
|
|
151
|
+
reject(new Error(`Process exited with code ${code}`));
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
resolve();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
if (this.options.verbose) {
|
|
159
|
+
this.log(`Process started (PID: ${this.masterProcess.pid}, Port: ${port})`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
99
162
|
async startWorker(workerId) {
|
|
100
163
|
var _a, _b;
|
|
101
164
|
const nodeArgs = this.getNodeArgs();
|
|
102
|
-
const
|
|
103
|
-
const workerPort = this.options.workers > 1 ? basePort + workerId - 1 : basePort;
|
|
165
|
+
const port = this.options.port || 8000;
|
|
104
166
|
const env = {
|
|
105
167
|
...process.env,
|
|
106
168
|
NODE_ENV: process.env.NODE_ENV || 'production',
|
|
107
169
|
WORKER_ID: workerId.toString(),
|
|
108
|
-
PORT:
|
|
170
|
+
PORT: port.toString(),
|
|
109
171
|
CLUSTER_WORKER: 'true',
|
|
110
172
|
FORCE_COLOR: this.options.color ? '1' : '0',
|
|
111
|
-
// Prevent dotenv from loading again in worker processes
|
|
112
|
-
DOTENV_CONFIG_PATH: '',
|
|
113
173
|
NODE_OPTIONS: '--no-deprecation'
|
|
114
174
|
};
|
|
115
175
|
const workerProcess = (0, child_process_1.fork)(this.options.file, [], {
|
|
116
176
|
cwd: this.options.workingDir,
|
|
117
177
|
env,
|
|
118
178
|
execArgv: nodeArgs,
|
|
119
|
-
silent: true
|
|
179
|
+
silent: true
|
|
120
180
|
});
|
|
121
181
|
const workerInfo = {
|
|
122
182
|
process: workerProcess,
|
|
123
183
|
pid: workerProcess.pid,
|
|
124
184
|
restarts: 0,
|
|
125
185
|
startTime: new Date(),
|
|
126
|
-
id: workerId
|
|
186
|
+
id: workerId,
|
|
187
|
+
port: port
|
|
127
188
|
};
|
|
128
189
|
this.workers.set(workerId, workerInfo);
|
|
129
|
-
// Handle worker output
|
|
190
|
+
// Handle worker output
|
|
130
191
|
(_a = workerProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
|
|
131
192
|
const message = data.toString().trim();
|
|
132
|
-
if (message) {
|
|
133
|
-
// Filter out repetitive dotenv messages
|
|
134
|
-
if (message.includes('[dotenv@') || message.includes('injecting env')) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
193
|
+
if (message && !message.includes('[dotenv@') && !message.includes('injecting env')) {
|
|
137
194
|
const prefix = this.options.verbose ?
|
|
138
|
-
chalk_1.default.dim(`[Worker ${workerId}
|
|
195
|
+
chalk_1.default.dim(`[Worker ${workerId}] `) : '';
|
|
139
196
|
console.log(prefix + message);
|
|
140
197
|
}
|
|
141
198
|
});
|
|
142
199
|
(_b = workerProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
|
|
143
200
|
const message = data.toString().trim();
|
|
144
|
-
if (message) {
|
|
145
|
-
// Filter out non-critical warnings
|
|
146
|
-
if (message.includes('[dotenv@') || message.includes('injecting env')) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
201
|
+
if (message && !message.includes('[dotenv@') && !message.includes('injecting env')) {
|
|
149
202
|
const prefix = this.options.verbose ?
|
|
150
|
-
chalk_1.default.dim(`[Worker ${workerId}
|
|
203
|
+
chalk_1.default.dim(`[Worker ${workerId}] `) : '';
|
|
151
204
|
console.error(prefix + chalk_1.default.red(message));
|
|
152
205
|
}
|
|
153
206
|
});
|
|
@@ -156,18 +209,16 @@ class StartManager {
|
|
|
156
209
|
});
|
|
157
210
|
workerProcess.on('exit', (code, signal) => {
|
|
158
211
|
this.workers.delete(workerId);
|
|
159
|
-
if (!this.isShuttingDown) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
this.restartWorker(workerId);
|
|
163
|
-
}
|
|
212
|
+
if (!this.isShuttingDown && code !== 0 && signal !== 'SIGTERM') {
|
|
213
|
+
this.log(`Worker ${workerId} crashed (code: ${code}, signal: ${signal})`, 'error');
|
|
214
|
+
this.restartWorker(workerId);
|
|
164
215
|
}
|
|
165
216
|
});
|
|
166
217
|
// Wait for worker to be ready
|
|
167
218
|
await new Promise((resolve, reject) => {
|
|
168
219
|
const timeout = setTimeout(() => {
|
|
169
|
-
|
|
170
|
-
},
|
|
220
|
+
resolve(); // Don't reject, assume it's ready
|
|
221
|
+
}, 5000);
|
|
171
222
|
workerProcess.on('message', (message) => {
|
|
172
223
|
if (message && message.type === 'ready') {
|
|
173
224
|
clearTimeout(timeout);
|
|
@@ -179,19 +230,17 @@ class StartManager {
|
|
|
179
230
|
reject(error);
|
|
180
231
|
});
|
|
181
232
|
workerProcess.on('exit', (code) => {
|
|
233
|
+
clearTimeout(timeout);
|
|
182
234
|
if (code !== 0) {
|
|
183
|
-
clearTimeout(timeout);
|
|
184
235
|
reject(new Error(`Worker ${workerId} exited with code ${code}`));
|
|
185
236
|
}
|
|
237
|
+
else {
|
|
238
|
+
resolve();
|
|
239
|
+
}
|
|
186
240
|
});
|
|
187
|
-
// Fallback - assume ready after 2 seconds if no message
|
|
188
|
-
setTimeout(() => {
|
|
189
|
-
clearTimeout(timeout);
|
|
190
|
-
resolve();
|
|
191
|
-
}, 2000);
|
|
192
241
|
});
|
|
193
242
|
if (this.options.verbose) {
|
|
194
|
-
this.log(`Worker ${workerId} started (PID: ${workerProcess.pid}
|
|
243
|
+
this.log(`Worker ${workerId} started (PID: ${workerProcess.pid})`);
|
|
195
244
|
}
|
|
196
245
|
return workerInfo;
|
|
197
246
|
}
|
|
@@ -235,6 +284,12 @@ class StartManager {
|
|
|
235
284
|
});
|
|
236
285
|
}
|
|
237
286
|
async startCluster() {
|
|
287
|
+
if (this.options.workers === 1) {
|
|
288
|
+
// Single process mode
|
|
289
|
+
await this.startSingleProcess();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
// Multi-worker mode
|
|
238
293
|
const startPromises = [];
|
|
239
294
|
for (let i = 0; i < this.options.workers; i++) {
|
|
240
295
|
startPromises.push(this.startWorker(i + 1));
|
|
@@ -272,7 +327,8 @@ class StartManager {
|
|
|
272
327
|
})),
|
|
273
328
|
totalRestarts: this.totalRestarts,
|
|
274
329
|
memory: process.memoryUsage(),
|
|
275
|
-
cpu: os_1.default.loadavg()
|
|
330
|
+
cpu: os_1.default.loadavg(),
|
|
331
|
+
port: this.options.port || 8000
|
|
276
332
|
};
|
|
277
333
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
278
334
|
res.end(JSON.stringify(stats, null, 2));
|
|
@@ -321,67 +377,54 @@ class StartManager {
|
|
|
321
377
|
async restartAll() {
|
|
322
378
|
if (this.isShuttingDown)
|
|
323
379
|
return;
|
|
324
|
-
this.log('Restarting
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
await this.waitForProcessExit(workerInfo.process, 5000);
|
|
331
|
-
}
|
|
332
|
-
catch (error) {
|
|
333
|
-
workerInfo.process.kill('SIGKILL');
|
|
334
|
-
}
|
|
335
|
-
})());
|
|
336
|
-
}
|
|
337
|
-
await Promise.allSettled(restartPromises);
|
|
338
|
-
// Wait a bit before restarting
|
|
339
|
-
setTimeout(() => {
|
|
340
|
-
this.startCluster();
|
|
341
|
-
}, this.options.restartDelay);
|
|
342
|
-
}
|
|
343
|
-
setupMemoryMonitoring() {
|
|
344
|
-
if (!this.options.maxMemory)
|
|
345
|
-
return;
|
|
346
|
-
const maxMemory = this.parseMemoryLimit(this.options.maxMemory);
|
|
347
|
-
if (!maxMemory)
|
|
348
|
-
return;
|
|
349
|
-
const checkInterval = setInterval(() => {
|
|
350
|
-
if (this.isShuttingDown) {
|
|
351
|
-
clearInterval(checkInterval);
|
|
352
|
-
return;
|
|
380
|
+
this.log('Restarting due to file changes...');
|
|
381
|
+
if (this.options.workers === 1 && this.masterProcess) {
|
|
382
|
+
// Single process restart
|
|
383
|
+
try {
|
|
384
|
+
this.masterProcess.kill('SIGTERM');
|
|
385
|
+
await this.waitForProcessExit(this.masterProcess, 5000);
|
|
353
386
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
387
|
+
catch (error) {
|
|
388
|
+
this.masterProcess.kill('SIGKILL');
|
|
389
|
+
}
|
|
390
|
+
setTimeout(() => {
|
|
391
|
+
this.startSingleProcess();
|
|
392
|
+
}, this.options.restartDelay);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
// Multi-worker restart
|
|
396
|
+
const restartPromises = [];
|
|
397
|
+
for (const [workerId, workerInfo] of this.workers.entries()) {
|
|
398
|
+
restartPromises.push((async () => {
|
|
399
|
+
try {
|
|
400
|
+
workerInfo.process.kill('SIGTERM');
|
|
401
|
+
await this.waitForProcessExit(workerInfo.process, 5000);
|
|
361
402
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
403
|
+
catch (error) {
|
|
404
|
+
workerInfo.process.kill('SIGKILL');
|
|
405
|
+
}
|
|
406
|
+
})());
|
|
407
|
+
}
|
|
408
|
+
await Promise.allSettled(restartPromises);
|
|
409
|
+
setTimeout(() => {
|
|
410
|
+
this.startCluster();
|
|
411
|
+
}, this.options.restartDelay);
|
|
412
|
+
}
|
|
368
413
|
}
|
|
369
414
|
async start() {
|
|
370
415
|
try {
|
|
371
|
-
// Load environment variables
|
|
416
|
+
// Load environment variables
|
|
372
417
|
this.loadEnvFile();
|
|
373
418
|
// Set up monitoring and health checks
|
|
374
419
|
this.setupHealthCheck();
|
|
375
420
|
this.setupWatcher();
|
|
376
|
-
|
|
377
|
-
// Start worker cluster
|
|
421
|
+
// Start the application
|
|
378
422
|
await this.startCluster();
|
|
379
423
|
// Success message
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Server ready${portInfo} (${this.workers.size} workers)`, 'info');
|
|
424
|
+
const port = this.options.port || 8000;
|
|
425
|
+
const workerInfo = this.options.workers === 1 ?
|
|
426
|
+
'' : ` (${this.workers.size} workers)`;
|
|
427
|
+
logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Server ready on port ${port}${workerInfo}`, 'info');
|
|
385
428
|
}
|
|
386
429
|
catch (error) {
|
|
387
430
|
this.log(`Failed to start server: ${error.message}`, 'error');
|
|
@@ -399,6 +442,17 @@ class StartManager {
|
|
|
399
442
|
if (this.healthServer) {
|
|
400
443
|
this.healthServer.close();
|
|
401
444
|
}
|
|
445
|
+
// Stop single process
|
|
446
|
+
if (this.masterProcess) {
|
|
447
|
+
try {
|
|
448
|
+
this.masterProcess.kill('SIGTERM');
|
|
449
|
+
await this.waitForProcessExit(this.masterProcess, this.options.gracefulTimeout);
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
this.masterProcess.kill('SIGKILL');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// Stop workers
|
|
402
456
|
const shutdownPromises = [];
|
|
403
457
|
for (const [workerId, workerInfo] of this.workers.entries()) {
|
|
404
458
|
shutdownPromises.push((async () => {
|