neex 0.6.52 → 0.6.54

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.
@@ -29,60 +29,118 @@ 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
35
  const targetFile = file || 'dist/index.js';
35
- const resolvedFile = path_1.default.resolve(options.dir, targetFile);
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
+ 'build/index.js',
57
+ 'build/server.js',
58
+ 'src/index.js',
59
+ 'src/server.js',
60
+ 'index.js',
61
+ 'server.js'
62
+ ];
63
+ let found = false;
64
+ for (const location of commonLocations) {
65
+ const testPath = path_1.default.resolve(options.dir, location);
66
+ if (fs_1.default.existsSync(testPath)) {
67
+ resolvedFile = testPath;
68
+ found = true;
69
+ if (options.verbose) {
70
+ logger_manager_js_1.loggerManager.printLine(`Found application file: ${location}`, 'info');
71
+ }
72
+ break;
73
+ }
74
+ }
75
+ if (!found) {
76
+ throw new Error(`Application file not found. Tried: ${targetFile}, ${mainFile}, and common locations.`);
77
+ }
46
78
  }
47
79
  }
48
- else {
49
- throw new Error(`Application file not found. Please build your project first.`);
80
+ catch (parseError) {
81
+ throw new Error(`Failed to parse package.json: ${parseError.message}`);
50
82
  }
51
83
  }
52
84
  else {
53
85
  throw new Error(`Application file not found: ${resolvedFile}`);
54
86
  }
55
87
  }
56
- // Auto-detect optimal configuration
88
+ // Detect environment and set optimal configuration
57
89
  const isDevelopment = options.watch || process.env.NODE_ENV === 'development';
58
90
  const isProduction = !isDevelopment;
59
91
  const cpuCount = os_1.default.cpus().length;
60
- const workers = options.workers || (isProduction ? cpuCount : 1);
92
+ // Smart worker allocation
93
+ let workers = options.workers;
94
+ if (!workers) {
95
+ if (isDevelopment) {
96
+ workers = 1; // Single worker for development
97
+ }
98
+ else {
99
+ // For production, use CPU count but cap at 8 for most applications
100
+ workers = Math.min(cpuCount, 8);
101
+ }
102
+ }
61
103
  const healthCheck = options.health !== false && isProduction;
104
+ const defaultPort = parseInt(process.env.PORT || '8000');
105
+ const port = options.port || defaultPort;
106
+ // Environment setup
107
+ if (!process.env.NODE_ENV) {
108
+ process.env.NODE_ENV = isProduction ? 'production' : 'development';
109
+ }
62
110
  // Startup logging
63
111
  const mode = isDevelopment ? 'development' : 'production';
64
112
  const clusterInfo = workers > 1 ? ` (${workers} workers)` : '';
65
113
  logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.play)} Starting ${mode} server${clusterInfo}`, 'info');
114
+ if (options.verbose) {
115
+ logger_manager_js_1.loggerManager.printLine(`File: ${path_1.default.relative(process.cwd(), resolvedFile)}`);
116
+ logger_manager_js_1.loggerManager.printLine(`Working Directory: ${options.dir}`);
117
+ logger_manager_js_1.loggerManager.printLine(`Environment: ${process.env.NODE_ENV}`);
118
+ logger_manager_js_1.loggerManager.printLine(`Port: ${port}`);
119
+ logger_manager_js_1.loggerManager.printLine(`Workers: ${workers}`);
120
+ if (healthCheck) {
121
+ logger_manager_js_1.loggerManager.printLine(`Health Check: http://localhost:${options.healthPort}/health`);
122
+ }
123
+ }
66
124
  startManager = new start_manager_js_1.StartManager({
67
125
  file: resolvedFile,
68
126
  workingDir: options.dir,
69
127
  envFile: options.env,
70
- port: options.port,
128
+ port,
71
129
  workers,
72
130
  memoryLimit: isProduction ? '512M' : undefined,
73
131
  logLevel: options.verbose ? 'debug' : 'info',
74
- color: true,
132
+ color: process.stdout.isTTY,
75
133
  verbose: options.verbose,
76
134
  watch: options.watch,
77
135
  maxMemory: options.maxMemory || (isProduction ? '1G' : undefined),
78
- maxCrashes: isProduction ? 3 : 5,
136
+ maxCrashes: isProduction ? 3 : 10,
79
137
  restartDelay: isProduction ? 2000 : 1000,
80
138
  healthCheck,
81
139
  healthPort: options.healthPort,
82
140
  gracefulTimeout: options.gracefulTimeout,
83
141
  inspect: options.inspect,
84
142
  inspectBrk: options.inspectBrk,
85
- nodeArgs: undefined
143
+ nodeArgs: options.nodeArgs
86
144
  });
87
145
  await startManager.start();
88
146
  }
@@ -104,13 +162,17 @@ function addStartCommands(program) {
104
162
  startManager = null;
105
163
  }
106
164
  catch (error) {
107
- // Ignore cleanup errors
165
+ // Ignore cleanup errors but log in verbose mode
166
+ if (process.env.VERBOSE) {
167
+ console.error('Cleanup error:', error);
168
+ }
108
169
  }
109
170
  }
110
171
  };
111
- // Handle process termination
172
+ // Enhanced 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(() => {
@@ -125,6 +187,19 @@ function addStartCommands(program) {
125
187
  process.on('SIGINT', () => handleExit('SIGINT'));
126
188
  process.on('SIGTERM', () => handleExit('SIGTERM'));
127
189
  process.on('SIGUSR2', () => handleExit('SIGUSR2'));
190
+ // Handle uncaught exceptions
191
+ process.on('uncaughtException', (error) => {
192
+ console.error(`${chalk_1.default.red(figures_1.default.cross)} Uncaught Exception:`, error);
193
+ cleanupStart().then(() => {
194
+ process.exit(1);
195
+ });
196
+ });
197
+ process.on('unhandledRejection', (reason, promise) => {
198
+ console.error(`${chalk_1.default.red(figures_1.default.cross)} Unhandled Rejection at:`, promise, 'reason:', reason);
199
+ cleanupStart().then(() => {
200
+ process.exit(1);
201
+ });
202
+ });
128
203
  return { cleanupStart };
129
204
  }
130
205
  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 and optimized 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,7 @@ class StartManager {
21
22
  this.healthServer = null;
22
23
  this.isShuttingDown = false;
23
24
  this.totalRestarts = 0;
25
+ this.envLoaded = false;
24
26
  this.options = options;
25
27
  this.startTime = new Date();
26
28
  this.debouncedRestart = (0, lodash_1.debounce)(this.restartAll.bind(this), options.restartDelay);
@@ -29,10 +31,13 @@ class StartManager {
29
31
  logger_manager_js_1.loggerManager.printLine(message, level);
30
32
  }
31
33
  loadEnvFile() {
34
+ if (this.envLoaded)
35
+ return;
32
36
  if (this.options.envFile && fs_1.default.existsSync(this.options.envFile)) {
33
37
  try {
34
38
  const envContent = fs_1.default.readFileSync(this.options.envFile, 'utf8');
35
39
  const lines = envContent.split('\n');
40
+ let loadedCount = 0;
36
41
  for (const line of lines) {
37
42
  const trimmed = line.trim();
38
43
  if (trimmed && !trimmed.startsWith('#')) {
@@ -40,10 +45,17 @@ class StartManager {
40
45
  if (key && values.length > 0) {
41
46
  const value = values.join('=').trim();
42
47
  const cleanValue = value.replace(/^["']|["']$/g, '');
43
- process.env[key.trim()] = cleanValue;
48
+ if (!process.env[key.trim()]) {
49
+ process.env[key.trim()] = cleanValue;
50
+ loadedCount++;
51
+ }
44
52
  }
45
53
  }
46
54
  }
55
+ if (this.options.verbose && loadedCount > 0) {
56
+ this.log(`Loaded ${loadedCount} environment variables from ${this.options.envFile}`);
57
+ }
58
+ this.envLoaded = true;
47
59
  }
48
60
  catch (error) {
49
61
  if (this.options.verbose) {
@@ -67,8 +79,11 @@ class StartManager {
67
79
  getNodeArgs() {
68
80
  const args = [];
69
81
  if (this.options.memoryLimit) {
70
- const memoryMB = this.parseMemoryLimit(this.options.memoryLimit) / (1024 * 1024);
71
- args.push(`--max-old-space-size=${memoryMB}`);
82
+ const memoryBytes = this.parseMemoryLimit(this.options.memoryLimit);
83
+ if (memoryBytes) {
84
+ const memoryMB = Math.floor(memoryBytes / (1024 * 1024));
85
+ args.push(`--max-old-space-size=${memoryMB}`);
86
+ }
72
87
  }
73
88
  if (this.options.inspect) {
74
89
  args.push('--inspect');
@@ -77,51 +92,63 @@ class StartManager {
77
92
  args.push('--inspect-brk');
78
93
  }
79
94
  if (this.options.nodeArgs) {
80
- args.push(...this.options.nodeArgs.split(' '));
95
+ args.push(...this.options.nodeArgs.split(' ').filter(arg => arg.trim()));
81
96
  }
82
97
  return args;
83
98
  }
84
99
  async startWorker(workerId) {
85
100
  var _a, _b;
86
101
  const nodeArgs = this.getNodeArgs();
102
+ const basePort = this.options.port || 8000;
103
+ const workerPort = this.options.workers > 1 ? basePort + workerId - 1 : basePort;
87
104
  const env = {
88
105
  ...process.env,
89
106
  NODE_ENV: process.env.NODE_ENV || 'production',
90
107
  WORKER_ID: workerId.toString(),
108
+ PORT: workerPort.toString(),
91
109
  CLUSTER_WORKER: 'true',
92
- FORCE_COLOR: this.options.color ? '1' : '0'
110
+ FORCE_COLOR: this.options.color ? '1' : '0',
111
+ // Prevent dotenv from loading again in worker processes
112
+ DOTENV_CONFIG_PATH: '',
113
+ NODE_OPTIONS: '--no-deprecation'
93
114
  };
94
- if (this.options.port) {
95
- env.PORT = this.options.port.toString();
96
- }
97
115
  const workerProcess = (0, child_process_1.fork)(this.options.file, [], {
98
116
  cwd: this.options.workingDir,
99
117
  env,
100
118
  execArgv: nodeArgs,
101
- silent: false
119
+ silent: true // Capture output to avoid duplication
102
120
  });
103
121
  const workerInfo = {
104
122
  process: workerProcess,
105
123
  pid: workerProcess.pid,
106
124
  restarts: 0,
107
- startTime: new Date()
125
+ startTime: new Date(),
126
+ id: workerId
108
127
  };
109
128
  this.workers.set(workerId, workerInfo);
110
- // Handle worker output
129
+ // Handle worker output with proper prefixing
111
130
  (_a = workerProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
112
131
  const message = data.toString().trim();
113
132
  if (message) {
114
- console.log(this.options.verbose ?
115
- chalk_1.default.dim(`[Worker ${workerId}]`) + ' ' + message :
116
- message);
133
+ // Filter out repetitive dotenv messages
134
+ if (message.includes('[dotenv@') || message.includes('injecting env')) {
135
+ return;
136
+ }
137
+ const prefix = this.options.verbose ?
138
+ chalk_1.default.dim(`[Worker ${workerId}:${workerPort}] `) : '';
139
+ console.log(prefix + message);
117
140
  }
118
141
  });
119
142
  (_b = workerProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
120
143
  const message = data.toString().trim();
121
144
  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));
145
+ // Filter out non-critical warnings
146
+ if (message.includes('[dotenv@') || message.includes('injecting env')) {
147
+ return;
148
+ }
149
+ const prefix = this.options.verbose ?
150
+ chalk_1.default.dim(`[Worker ${workerId}:${workerPort}] `) : '';
151
+ console.error(prefix + chalk_1.default.red(message));
125
152
  }
126
153
  });
127
154
  workerProcess.on('error', (error) => {
@@ -130,14 +157,41 @@ class StartManager {
130
157
  workerProcess.on('exit', (code, signal) => {
131
158
  this.workers.delete(workerId);
132
159
  if (!this.isShuttingDown) {
133
- if (code !== 0) {
134
- this.log(`Worker ${workerId} crashed (code: ${code})`, 'error');
160
+ if (code !== 0 && signal !== 'SIGTERM') {
161
+ this.log(`Worker ${workerId} crashed (code: ${code}, signal: ${signal})`, 'error');
135
162
  this.restartWorker(workerId);
136
163
  }
137
164
  }
138
165
  });
166
+ // Wait for worker to be ready
167
+ await new Promise((resolve, reject) => {
168
+ const timeout = setTimeout(() => {
169
+ reject(new Error(`Worker ${workerId} failed to start within timeout`));
170
+ }, 10000);
171
+ workerProcess.on('message', (message) => {
172
+ if (message && message.type === 'ready') {
173
+ clearTimeout(timeout);
174
+ resolve();
175
+ }
176
+ });
177
+ workerProcess.on('error', (error) => {
178
+ clearTimeout(timeout);
179
+ reject(error);
180
+ });
181
+ workerProcess.on('exit', (code) => {
182
+ if (code !== 0) {
183
+ clearTimeout(timeout);
184
+ reject(new Error(`Worker ${workerId} exited with code ${code}`));
185
+ }
186
+ });
187
+ // Fallback - assume ready after 2 seconds if no message
188
+ setTimeout(() => {
189
+ clearTimeout(timeout);
190
+ resolve();
191
+ }, 2000);
192
+ });
139
193
  if (this.options.verbose) {
140
- this.log(`Worker ${workerId} started (PID: ${workerProcess.pid})`);
194
+ this.log(`Worker ${workerId} started (PID: ${workerProcess.pid}, Port: ${workerPort})`);
141
195
  }
142
196
  return workerInfo;
143
197
  }
@@ -147,7 +201,7 @@ class StartManager {
147
201
  workerInfo.restarts++;
148
202
  this.totalRestarts++;
149
203
  if (workerInfo.restarts >= this.options.maxCrashes) {
150
- this.log(`Worker ${workerId} reached max crashes, not restarting`, 'error');
204
+ this.log(`Worker ${workerId} reached max crashes (${this.options.maxCrashes}), not restarting`, 'error');
151
205
  return;
152
206
  }
153
207
  if (this.options.verbose) {
@@ -174,30 +228,57 @@ class StartManager {
174
228
  clearTimeout(timer);
175
229
  resolve();
176
230
  });
231
+ if (process.killed) {
232
+ clearTimeout(timer);
233
+ resolve();
234
+ }
177
235
  });
178
236
  }
179
237
  async startCluster() {
238
+ const startPromises = [];
180
239
  for (let i = 0; i < this.options.workers; i++) {
181
- await this.startWorker(i + 1);
240
+ startPromises.push(this.startWorker(i + 1));
241
+ }
242
+ try {
243
+ await Promise.all(startPromises);
244
+ }
245
+ catch (error) {
246
+ this.log(`Failed to start some workers: ${error.message}`, 'error');
247
+ // Continue with successfully started workers
182
248
  }
183
249
  }
184
250
  setupHealthCheck() {
185
251
  if (!this.options.healthCheck)
186
252
  return;
187
253
  this.healthServer = http_1.default.createServer((req, res) => {
254
+ res.setHeader('Access-Control-Allow-Origin', '*');
255
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
256
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
257
+ if (req.method === 'OPTIONS') {
258
+ res.writeHead(200);
259
+ res.end();
260
+ return;
261
+ }
188
262
  if (req.url === '/health') {
189
263
  const stats = {
190
264
  status: 'ok',
191
265
  uptime: Date.now() - this.startTime.getTime(),
192
266
  workers: this.workers.size,
267
+ activeWorkers: Array.from(this.workers.values()).map(w => ({
268
+ id: w.id,
269
+ pid: w.pid,
270
+ restarts: w.restarts,
271
+ uptime: Date.now() - w.startTime.getTime()
272
+ })),
193
273
  totalRestarts: this.totalRestarts,
194
- memory: process.memoryUsage()
274
+ memory: process.memoryUsage(),
275
+ cpu: os_1.default.loadavg()
195
276
  };
196
277
  res.writeHead(200, { 'Content-Type': 'application/json' });
197
- res.end(JSON.stringify(stats));
278
+ res.end(JSON.stringify(stats, null, 2));
198
279
  }
199
280
  else {
200
- res.writeHead(404);
281
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
201
282
  res.end('Not Found');
202
283
  }
203
284
  });
@@ -216,9 +297,13 @@ class StartManager {
216
297
  `${this.options.workingDir}/**/*.env*`
217
298
  ];
218
299
  this.watcher = (0, chokidar_1.watch)(watchPatterns, {
219
- ignored: ['**/node_modules/**', '**/.git/**', '**/logs/**'],
300
+ ignored: ['**/node_modules/**', '**/.git/**', '**/logs/**', '**/tmp/**'],
220
301
  ignoreInitial: true,
221
- atomic: 300
302
+ atomic: 300,
303
+ awaitWriteFinish: {
304
+ stabilityThreshold: 2000,
305
+ pollInterval: 100
306
+ }
222
307
  });
223
308
  this.watcher.on('change', (filePath) => {
224
309
  if (this.options.verbose) {
@@ -226,6 +311,9 @@ class StartManager {
226
311
  }
227
312
  this.debouncedRestart();
228
313
  });
314
+ this.watcher.on('error', (error) => {
315
+ this.log(`Watcher error: ${error.message}`, 'error');
316
+ });
229
317
  if (this.options.verbose) {
230
318
  this.log('File watching enabled');
231
319
  }
@@ -233,19 +321,24 @@ class StartManager {
233
321
  async restartAll() {
234
322
  if (this.isShuttingDown)
235
323
  return;
236
- this.log('Restarting due to file changes...');
324
+ this.log('Restarting all workers due to file changes...');
325
+ const restartPromises = [];
237
326
  for (const [workerId, workerInfo] of this.workers.entries()) {
238
- try {
239
- workerInfo.process.kill('SIGTERM');
240
- await this.waitForProcessExit(workerInfo.process, 5000);
241
- }
242
- catch (error) {
243
- workerInfo.process.kill('SIGKILL');
244
- }
245
- setTimeout(() => {
246
- this.startWorker(workerId);
247
- }, this.options.restartDelay);
327
+ restartPromises.push((async () => {
328
+ try {
329
+ workerInfo.process.kill('SIGTERM');
330
+ await this.waitForProcessExit(workerInfo.process, 5000);
331
+ }
332
+ catch (error) {
333
+ workerInfo.process.kill('SIGKILL');
334
+ }
335
+ })());
248
336
  }
337
+ await Promise.allSettled(restartPromises);
338
+ // Wait a bit before restarting
339
+ setTimeout(() => {
340
+ this.startCluster();
341
+ }, this.options.restartDelay);
249
342
  }
250
343
  setupMemoryMonitoring() {
251
344
  if (!this.options.maxMemory)
@@ -253,12 +346,17 @@ class StartManager {
253
346
  const maxMemory = this.parseMemoryLimit(this.options.maxMemory);
254
347
  if (!maxMemory)
255
348
  return;
256
- setInterval(() => {
349
+ const checkInterval = setInterval(() => {
350
+ if (this.isShuttingDown) {
351
+ clearInterval(checkInterval);
352
+ return;
353
+ }
257
354
  this.workers.forEach((workerInfo, workerId) => {
258
355
  try {
356
+ // This is a simplified check - in practice, you'd need to get actual worker memory usage
259
357
  const usage = process.memoryUsage();
260
358
  if (usage.heapUsed > maxMemory) {
261
- this.log(`Worker ${workerId} exceeded memory limit, restarting`, 'warn');
359
+ this.log(`Worker ${workerId} may have exceeded memory limit, restarting`, 'warn');
262
360
  this.restartWorker(workerId);
263
361
  }
264
362
  }
@@ -270,17 +368,23 @@ class StartManager {
270
368
  }
271
369
  async start() {
272
370
  try {
371
+ // Load environment variables only once in the main process
273
372
  this.loadEnvFile();
373
+ // Set up monitoring and health checks
274
374
  this.setupHealthCheck();
275
375
  this.setupWatcher();
276
376
  this.setupMemoryMonitoring();
377
+ // Start worker cluster
277
378
  await this.startCluster();
278
379
  // 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');
380
+ const basePort = this.options.port || 8000;
381
+ const portInfo = this.options.workers > 1 ?
382
+ ` on ports ${basePort}-${basePort + this.options.workers - 1}` :
383
+ ` on port ${basePort}`;
384
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Server ready${portInfo} (${this.workers.size} workers)`, 'info');
282
385
  }
283
386
  catch (error) {
387
+ this.log(`Failed to start server: ${error.message}`, 'error');
284
388
  throw error;
285
389
  }
286
390
  }
@@ -288,6 +392,7 @@ class StartManager {
288
392
  if (this.isShuttingDown)
289
393
  return;
290
394
  this.isShuttingDown = true;
395
+ this.log('Shutting down gracefully...');
291
396
  if (this.watcher) {
292
397
  await this.watcher.close();
293
398
  }
@@ -308,6 +413,7 @@ class StartManager {
308
413
  }
309
414
  await Promise.allSettled(shutdownPromises);
310
415
  this.workers.clear();
416
+ this.log('Server stopped');
311
417
  }
312
418
  }
313
419
  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.54",
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",