neex 0.6.52 → 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.
@@ -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
- // Single optimized start command for production
16
+ // Production start command
17
17
  program
18
18
  .command('start [file]')
19
- .description('Start production application with automatic optimization')
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)
@@ -29,60 +29,119 @@ function addStartCommands(program) {
29
29
  .option('--graceful-timeout <ms>', 'Graceful shutdown timeout (ms)', parseInt, 30000)
30
30
  .option('--inspect', 'Enable Node.js inspector')
31
31
  .option('--inspect-brk', 'Enable Node.js inspector with break')
32
+ .option('--node-args <args>', 'Additional Node.js arguments')
32
33
  .action(async (file, options) => {
33
34
  try {
34
- const targetFile = file || 'dist/index.js';
35
- const resolvedFile = path_1.default.resolve(options.dir, targetFile);
35
+ const targetFile = file || 'dist/server.js';
36
+ let resolvedFile = path_1.default.resolve(options.dir, targetFile);
36
37
  // Auto-detect main file if not found
37
38
  if (!fs_1.default.existsSync(resolvedFile)) {
38
39
  const packageJsonPath = path_1.default.join(options.dir, 'package.json');
39
40
  if (fs_1.default.existsSync(packageJsonPath)) {
40
- const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
41
- const mainFile = packageJson.main || 'index.js';
42
- const alternativeFile = path_1.default.resolve(options.dir, mainFile);
43
- if (fs_1.default.existsSync(alternativeFile)) {
44
- if (options.verbose) {
45
- logger_manager_js_1.loggerManager.printLine(`Using main file: ${mainFile}`, 'info');
41
+ try {
42
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
43
+ const mainFile = packageJson.main || 'index.js';
44
+ const alternativeFile = path_1.default.resolve(options.dir, mainFile);
45
+ if (fs_1.default.existsSync(alternativeFile)) {
46
+ resolvedFile = alternativeFile;
47
+ if (options.verbose) {
48
+ logger_manager_js_1.loggerManager.printLine(`Using main file: ${mainFile}`, 'info');
49
+ }
50
+ }
51
+ else {
52
+ // Try common locations
53
+ const commonLocations = [
54
+ 'dist/server.js',
55
+ 'dist/app.js',
56
+ 'dist/index.js',
57
+ 'build/server.js',
58
+ 'build/app.js',
59
+ 'build/index.js',
60
+ 'server.js',
61
+ 'app.js',
62
+ 'index.js'
63
+ ];
64
+ let found = false;
65
+ for (const location of commonLocations) {
66
+ const testPath = path_1.default.resolve(options.dir, location);
67
+ if (fs_1.default.existsSync(testPath)) {
68
+ resolvedFile = testPath;
69
+ found = true;
70
+ if (options.verbose) {
71
+ logger_manager_js_1.loggerManager.printLine(`Found application file: ${location}`, 'info');
72
+ }
73
+ break;
74
+ }
75
+ }
76
+ if (!found) {
77
+ throw new Error(`Application file not found. Tried: ${targetFile}, ${mainFile}, and common locations.`);
78
+ }
46
79
  }
47
80
  }
48
- else {
49
- throw new Error(`Application file not found. Please build your project first.`);
81
+ catch (parseError) {
82
+ throw new Error(`Failed to parse package.json: ${parseError.message}`);
50
83
  }
51
84
  }
52
85
  else {
53
86
  throw new Error(`Application file not found: ${resolvedFile}`);
54
87
  }
55
88
  }
56
- // Auto-detect optimal configuration
89
+ // Environment detection
57
90
  const isDevelopment = options.watch || process.env.NODE_ENV === 'development';
58
91
  const isProduction = !isDevelopment;
59
92
  const cpuCount = os_1.default.cpus().length;
60
- const workers = options.workers || (isProduction ? cpuCount : 1);
61
- const healthCheck = options.health !== false && isProduction;
93
+ // Smart worker allocation
94
+ let workers = options.workers;
95
+ if (!workers) {
96
+ if (isDevelopment) {
97
+ workers = 1;
98
+ }
99
+ else {
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
102
+ }
103
+ }
104
+ const healthCheck = options.health !== false;
105
+ const defaultPort = parseInt(process.env.PORT || '8000');
106
+ const port = options.port || defaultPort;
107
+ // Set NODE_ENV if not already set
108
+ if (!process.env.NODE_ENV) {
109
+ process.env.NODE_ENV = isProduction ? 'production' : 'development';
110
+ }
62
111
  // Startup logging
63
112
  const mode = isDevelopment ? 'development' : 'production';
64
113
  const clusterInfo = workers > 1 ? ` (${workers} workers)` : '';
65
114
  logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.play)} Starting ${mode} server${clusterInfo}`, 'info');
115
+ if (options.verbose) {
116
+ logger_manager_js_1.loggerManager.printLine(`File: ${path_1.default.relative(process.cwd(), resolvedFile)}`);
117
+ logger_manager_js_1.loggerManager.printLine(`Working Directory: ${options.dir}`);
118
+ logger_manager_js_1.loggerManager.printLine(`Environment: ${process.env.NODE_ENV}`);
119
+ logger_manager_js_1.loggerManager.printLine(`Port: ${port}`);
120
+ logger_manager_js_1.loggerManager.printLine(`Workers: ${workers}`);
121
+ if (healthCheck) {
122
+ logger_manager_js_1.loggerManager.printLine(`Health Check: http://localhost:${options.healthPort}/health`);
123
+ }
124
+ }
66
125
  startManager = new start_manager_js_1.StartManager({
67
126
  file: resolvedFile,
68
127
  workingDir: options.dir,
69
128
  envFile: options.env,
70
- port: options.port,
129
+ port,
71
130
  workers,
72
131
  memoryLimit: isProduction ? '512M' : undefined,
73
132
  logLevel: options.verbose ? 'debug' : 'info',
74
- color: true,
133
+ color: process.stdout.isTTY,
75
134
  verbose: options.verbose,
76
135
  watch: options.watch,
77
136
  maxMemory: options.maxMemory || (isProduction ? '1G' : undefined),
78
- maxCrashes: isProduction ? 3 : 5,
79
- restartDelay: isProduction ? 2000 : 1000,
137
+ maxCrashes: isProduction ? 3 : 10,
138
+ restartDelay: isProduction ? 1000 : 500,
80
139
  healthCheck,
81
140
  healthPort: options.healthPort,
82
141
  gracefulTimeout: options.gracefulTimeout,
83
142
  inspect: options.inspect,
84
143
  inspectBrk: options.inspectBrk,
85
- nodeArgs: undefined
144
+ nodeArgs: options.nodeArgs
86
145
  });
87
146
  await startManager.start();
88
147
  }
@@ -104,13 +163,16 @@ function addStartCommands(program) {
104
163
  startManager = null;
105
164
  }
106
165
  catch (error) {
107
- // Ignore cleanup errors
166
+ if (process.env.VERBOSE) {
167
+ console.error('Cleanup error:', error);
168
+ }
108
169
  }
109
170
  }
110
171
  };
111
- // Handle process termination
172
+ // Signal handling
112
173
  const handleExit = (signal) => {
113
174
  if (startManager) {
175
+ console.log(`\n${chalk_1.default.yellow(figures_1.default.warning)} Received ${signal}, shutting down gracefully...`);
114
176
  cleanupStart().then(() => {
115
177
  process.exit(0);
116
178
  }).catch(() => {
@@ -121,10 +183,21 @@ function addStartCommands(program) {
121
183
  process.exit(0);
122
184
  }
123
185
  };
124
- // Register signal handlers
125
186
  process.on('SIGINT', () => handleExit('SIGINT'));
126
187
  process.on('SIGTERM', () => handleExit('SIGTERM'));
127
188
  process.on('SIGUSR2', () => handleExit('SIGUSR2'));
189
+ process.on('uncaughtException', (error) => {
190
+ console.error(`${chalk_1.default.red(figures_1.default.cross)} Uncaught Exception:`, error);
191
+ cleanupStart().then(() => {
192
+ process.exit(1);
193
+ });
194
+ });
195
+ process.on('unhandledRejection', (reason, promise) => {
196
+ console.error(`${chalk_1.default.red(figures_1.default.cross)} Unhandled Rejection at:`, promise, 'reason:', reason);
197
+ cleanupStart().then(() => {
198
+ process.exit(1);
199
+ });
200
+ });
128
201
  return { cleanupStart };
129
202
  }
130
203
  exports.addStartCommands = addStartCommands;
@@ -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 - Optimized production start manager
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");
@@ -14,6 +14,7 @@ const path_1 = __importDefault(require("path"));
14
14
  const fs_1 = __importDefault(require("fs"));
15
15
  const http_1 = __importDefault(require("http"));
16
16
  const lodash_1 = require("lodash");
17
+ const os_1 = __importDefault(require("os"));
17
18
  class StartManager {
18
19
  constructor(options) {
19
20
  this.workers = new Map();
@@ -21,6 +22,8 @@ class StartManager {
21
22
  this.healthServer = null;
22
23
  this.isShuttingDown = false;
23
24
  this.totalRestarts = 0;
25
+ this.envLoaded = false;
26
+ this.masterProcess = null;
24
27
  this.options = options;
25
28
  this.startTime = new Date();
26
29
  this.debouncedRestart = (0, lodash_1.debounce)(this.restartAll.bind(this), options.restartDelay);
@@ -29,10 +32,13 @@ class StartManager {
29
32
  logger_manager_js_1.loggerManager.printLine(message, level);
30
33
  }
31
34
  loadEnvFile() {
35
+ if (this.envLoaded)
36
+ return;
32
37
  if (this.options.envFile && fs_1.default.existsSync(this.options.envFile)) {
33
38
  try {
34
39
  const envContent = fs_1.default.readFileSync(this.options.envFile, 'utf8');
35
40
  const lines = envContent.split('\n');
41
+ let loadedCount = 0;
36
42
  for (const line of lines) {
37
43
  const trimmed = line.trim();
38
44
  if (trimmed && !trimmed.startsWith('#')) {
@@ -40,10 +46,17 @@ class StartManager {
40
46
  if (key && values.length > 0) {
41
47
  const value = values.join('=').trim();
42
48
  const cleanValue = value.replace(/^["']|["']$/g, '');
43
- process.env[key.trim()] = cleanValue;
49
+ if (!process.env[key.trim()]) {
50
+ process.env[key.trim()] = cleanValue;
51
+ loadedCount++;
52
+ }
44
53
  }
45
54
  }
46
55
  }
56
+ if (this.options.verbose && loadedCount > 0) {
57
+ this.log(`Loaded ${loadedCount} environment variables from ${this.options.envFile}`);
58
+ }
59
+ this.envLoaded = true;
47
60
  }
48
61
  catch (error) {
49
62
  if (this.options.verbose) {
@@ -67,8 +80,11 @@ class StartManager {
67
80
  getNodeArgs() {
68
81
  const args = [];
69
82
  if (this.options.memoryLimit) {
70
- const memoryMB = this.parseMemoryLimit(this.options.memoryLimit) / (1024 * 1024);
71
- args.push(`--max-old-space-size=${memoryMB}`);
83
+ const memoryBytes = this.parseMemoryLimit(this.options.memoryLimit);
84
+ if (memoryBytes) {
85
+ const memoryMB = Math.floor(memoryBytes / (1024 * 1024));
86
+ args.push(`--max-old-space-size=${memoryMB}`);
87
+ }
72
88
  }
73
89
  if (this.options.inspect) {
74
90
  args.push('--inspect');
@@ -77,51 +93,115 @@ class StartManager {
77
93
  args.push('--inspect-brk');
78
94
  }
79
95
  if (this.options.nodeArgs) {
80
- args.push(...this.options.nodeArgs.split(' '));
96
+ args.push(...this.options.nodeArgs.split(' ').filter(arg => arg.trim()));
81
97
  }
82
98
  return args;
83
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
+ }
84
162
  async startWorker(workerId) {
85
163
  var _a, _b;
86
164
  const nodeArgs = this.getNodeArgs();
165
+ const port = this.options.port || 8000;
87
166
  const env = {
88
167
  ...process.env,
89
168
  NODE_ENV: process.env.NODE_ENV || 'production',
90
169
  WORKER_ID: workerId.toString(),
170
+ PORT: port.toString(),
91
171
  CLUSTER_WORKER: 'true',
92
- FORCE_COLOR: this.options.color ? '1' : '0'
172
+ FORCE_COLOR: this.options.color ? '1' : '0',
173
+ NODE_OPTIONS: '--no-deprecation'
93
174
  };
94
- if (this.options.port) {
95
- env.PORT = this.options.port.toString();
96
- }
97
175
  const workerProcess = (0, child_process_1.fork)(this.options.file, [], {
98
176
  cwd: this.options.workingDir,
99
177
  env,
100
178
  execArgv: nodeArgs,
101
- silent: false
179
+ silent: true
102
180
  });
103
181
  const workerInfo = {
104
182
  process: workerProcess,
105
183
  pid: workerProcess.pid,
106
184
  restarts: 0,
107
- startTime: new Date()
185
+ startTime: new Date(),
186
+ id: workerId,
187
+ port: port
108
188
  };
109
189
  this.workers.set(workerId, workerInfo);
110
190
  // Handle worker output
111
191
  (_a = workerProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
112
192
  const message = data.toString().trim();
113
- if (message) {
114
- console.log(this.options.verbose ?
115
- chalk_1.default.dim(`[Worker ${workerId}]`) + ' ' + message :
116
- message);
193
+ if (message && !message.includes('[dotenv@') && !message.includes('injecting env')) {
194
+ const prefix = this.options.verbose ?
195
+ chalk_1.default.dim(`[Worker ${workerId}] `) : '';
196
+ console.log(prefix + message);
117
197
  }
118
198
  });
119
199
  (_b = workerProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
120
200
  const message = data.toString().trim();
121
- if (message) {
122
- console.error(this.options.verbose ?
123
- chalk_1.default.dim(`[Worker ${workerId}]`) + ' ' + chalk_1.default.red(message) :
124
- chalk_1.default.red(message));
201
+ if (message && !message.includes('[dotenv@') && !message.includes('injecting env')) {
202
+ const prefix = this.options.verbose ?
203
+ chalk_1.default.dim(`[Worker ${workerId}] `) : '';
204
+ console.error(prefix + chalk_1.default.red(message));
125
205
  }
126
206
  });
127
207
  workerProcess.on('error', (error) => {
@@ -129,12 +209,35 @@ class StartManager {
129
209
  });
130
210
  workerProcess.on('exit', (code, signal) => {
131
211
  this.workers.delete(workerId);
132
- if (!this.isShuttingDown) {
212
+ if (!this.isShuttingDown && code !== 0 && signal !== 'SIGTERM') {
213
+ this.log(`Worker ${workerId} crashed (code: ${code}, signal: ${signal})`, 'error');
214
+ this.restartWorker(workerId);
215
+ }
216
+ });
217
+ // Wait for worker to be ready
218
+ await new Promise((resolve, reject) => {
219
+ const timeout = setTimeout(() => {
220
+ resolve(); // Don't reject, assume it's ready
221
+ }, 5000);
222
+ workerProcess.on('message', (message) => {
223
+ if (message && message.type === 'ready') {
224
+ clearTimeout(timeout);
225
+ resolve();
226
+ }
227
+ });
228
+ workerProcess.on('error', (error) => {
229
+ clearTimeout(timeout);
230
+ reject(error);
231
+ });
232
+ workerProcess.on('exit', (code) => {
233
+ clearTimeout(timeout);
133
234
  if (code !== 0) {
134
- this.log(`Worker ${workerId} crashed (code: ${code})`, 'error');
135
- this.restartWorker(workerId);
235
+ reject(new Error(`Worker ${workerId} exited with code ${code}`));
136
236
  }
137
- }
237
+ else {
238
+ resolve();
239
+ }
240
+ });
138
241
  });
139
242
  if (this.options.verbose) {
140
243
  this.log(`Worker ${workerId} started (PID: ${workerProcess.pid})`);
@@ -147,7 +250,7 @@ class StartManager {
147
250
  workerInfo.restarts++;
148
251
  this.totalRestarts++;
149
252
  if (workerInfo.restarts >= this.options.maxCrashes) {
150
- this.log(`Worker ${workerId} reached max crashes, not restarting`, 'error');
253
+ this.log(`Worker ${workerId} reached max crashes (${this.options.maxCrashes}), not restarting`, 'error');
151
254
  return;
152
255
  }
153
256
  if (this.options.verbose) {
@@ -174,30 +277,64 @@ class StartManager {
174
277
  clearTimeout(timer);
175
278
  resolve();
176
279
  });
280
+ if (process.killed) {
281
+ clearTimeout(timer);
282
+ resolve();
283
+ }
177
284
  });
178
285
  }
179
286
  async startCluster() {
287
+ if (this.options.workers === 1) {
288
+ // Single process mode
289
+ await this.startSingleProcess();
290
+ return;
291
+ }
292
+ // Multi-worker mode
293
+ const startPromises = [];
180
294
  for (let i = 0; i < this.options.workers; i++) {
181
- await this.startWorker(i + 1);
295
+ startPromises.push(this.startWorker(i + 1));
296
+ }
297
+ try {
298
+ await Promise.all(startPromises);
299
+ }
300
+ catch (error) {
301
+ this.log(`Failed to start some workers: ${error.message}`, 'error');
302
+ // Continue with successfully started workers
182
303
  }
183
304
  }
184
305
  setupHealthCheck() {
185
306
  if (!this.options.healthCheck)
186
307
  return;
187
308
  this.healthServer = http_1.default.createServer((req, res) => {
309
+ res.setHeader('Access-Control-Allow-Origin', '*');
310
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
311
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
312
+ if (req.method === 'OPTIONS') {
313
+ res.writeHead(200);
314
+ res.end();
315
+ return;
316
+ }
188
317
  if (req.url === '/health') {
189
318
  const stats = {
190
319
  status: 'ok',
191
320
  uptime: Date.now() - this.startTime.getTime(),
192
321
  workers: this.workers.size,
322
+ activeWorkers: Array.from(this.workers.values()).map(w => ({
323
+ id: w.id,
324
+ pid: w.pid,
325
+ restarts: w.restarts,
326
+ uptime: Date.now() - w.startTime.getTime()
327
+ })),
193
328
  totalRestarts: this.totalRestarts,
194
- memory: process.memoryUsage()
329
+ memory: process.memoryUsage(),
330
+ cpu: os_1.default.loadavg(),
331
+ port: this.options.port || 8000
195
332
  };
196
333
  res.writeHead(200, { 'Content-Type': 'application/json' });
197
- res.end(JSON.stringify(stats));
334
+ res.end(JSON.stringify(stats, null, 2));
198
335
  }
199
336
  else {
200
- res.writeHead(404);
337
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
201
338
  res.end('Not Found');
202
339
  }
203
340
  });
@@ -216,9 +353,13 @@ class StartManager {
216
353
  `${this.options.workingDir}/**/*.env*`
217
354
  ];
218
355
  this.watcher = (0, chokidar_1.watch)(watchPatterns, {
219
- ignored: ['**/node_modules/**', '**/.git/**', '**/logs/**'],
356
+ ignored: ['**/node_modules/**', '**/.git/**', '**/logs/**', '**/tmp/**'],
220
357
  ignoreInitial: true,
221
- atomic: 300
358
+ atomic: 300,
359
+ awaitWriteFinish: {
360
+ stabilityThreshold: 2000,
361
+ pollInterval: 100
362
+ }
222
363
  });
223
364
  this.watcher.on('change', (filePath) => {
224
365
  if (this.options.verbose) {
@@ -226,6 +367,9 @@ class StartManager {
226
367
  }
227
368
  this.debouncedRestart();
228
369
  });
370
+ this.watcher.on('error', (error) => {
371
+ this.log(`Watcher error: ${error.message}`, 'error');
372
+ });
229
373
  if (this.options.verbose) {
230
374
  this.log('File watching enabled');
231
375
  }
@@ -234,53 +378,56 @@ class StartManager {
234
378
  if (this.isShuttingDown)
235
379
  return;
236
380
  this.log('Restarting due to file changes...');
237
- for (const [workerId, workerInfo] of this.workers.entries()) {
381
+ if (this.options.workers === 1 && this.masterProcess) {
382
+ // Single process restart
238
383
  try {
239
- workerInfo.process.kill('SIGTERM');
240
- await this.waitForProcessExit(workerInfo.process, 5000);
384
+ this.masterProcess.kill('SIGTERM');
385
+ await this.waitForProcessExit(this.masterProcess, 5000);
241
386
  }
242
387
  catch (error) {
243
- workerInfo.process.kill('SIGKILL');
388
+ this.masterProcess.kill('SIGKILL');
244
389
  }
245
390
  setTimeout(() => {
246
- this.startWorker(workerId);
391
+ this.startSingleProcess();
247
392
  }, this.options.restartDelay);
248
393
  }
249
- }
250
- setupMemoryMonitoring() {
251
- if (!this.options.maxMemory)
252
- return;
253
- const maxMemory = this.parseMemoryLimit(this.options.maxMemory);
254
- if (!maxMemory)
255
- return;
256
- setInterval(() => {
257
- this.workers.forEach((workerInfo, workerId) => {
258
- try {
259
- const usage = process.memoryUsage();
260
- if (usage.heapUsed > maxMemory) {
261
- this.log(`Worker ${workerId} exceeded memory limit, restarting`, 'warn');
262
- this.restartWorker(workerId);
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);
263
402
  }
264
- }
265
- catch (error) {
266
- // Ignore monitoring errors
267
- }
268
- });
269
- }, 30000); // Check every 30 seconds
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
+ }
270
413
  }
271
414
  async start() {
272
415
  try {
416
+ // Load environment variables
273
417
  this.loadEnvFile();
418
+ // Set up monitoring and health checks
274
419
  this.setupHealthCheck();
275
420
  this.setupWatcher();
276
- this.setupMemoryMonitoring();
421
+ // Start the application
277
422
  await this.startCluster();
278
423
  // Success message
279
- const port = this.options.port || process.env.PORT;
280
- const portInfo = port ? ` on port ${port}` : '';
281
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Server ready${portInfo}`, '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');
282
428
  }
283
429
  catch (error) {
430
+ this.log(`Failed to start server: ${error.message}`, 'error');
284
431
  throw error;
285
432
  }
286
433
  }
@@ -288,12 +435,24 @@ class StartManager {
288
435
  if (this.isShuttingDown)
289
436
  return;
290
437
  this.isShuttingDown = true;
438
+ this.log('Shutting down gracefully...');
291
439
  if (this.watcher) {
292
440
  await this.watcher.close();
293
441
  }
294
442
  if (this.healthServer) {
295
443
  this.healthServer.close();
296
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
297
456
  const shutdownPromises = [];
298
457
  for (const [workerId, workerInfo] of this.workers.entries()) {
299
458
  shutdownPromises.push((async () => {
@@ -308,6 +467,7 @@ class StartManager {
308
467
  }
309
468
  await Promise.allSettled(shutdownPromises);
310
469
  this.workers.clear();
470
+ this.log('Server stopped');
311
471
  }
312
472
  }
313
473
  exports.StartManager = StartManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neex",
3
- "version": "0.6.52",
3
+ "version": "0.6.55",
4
4
  "description": "The Modern Build System for Polyrepo-in-Monorepo Architecture",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",