neex 0.6.50 → 0.6.51

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.
@@ -8,63 +8,86 @@ const start_manager_js_1 = require("../start-manager.js");
8
8
  const logger_manager_js_1 = require("../logger-manager.js");
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
10
  const figures_1 = __importDefault(require("figures"));
11
- const os_1 = __importDefault(require("os"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const fs_1 = __importDefault(require("fs"));
12
13
  function addStartCommands(program) {
13
14
  let startManager = null;
14
- // Main start command
15
+ // Start command for production execution
15
16
  program
16
- .command('start [entry]')
17
- .description('Start the production server (default: dist/index.js)')
18
- .option('-p, --port <port>', 'Port to run the server on', '3000')
19
- .option('-h, --host <host>', 'Host to bind the server to', '0.0.0.0')
20
- .option('-e, --env <env>', 'Environment mode', 'production')
21
- .option('-w, --watch', 'Watch for file changes and restart')
17
+ .command('start [file]')
18
+ .description('Start production application (default: dist/index.js)')
19
+ .option('-d, --dir <directory>', 'Working directory', process.cwd())
20
+ .option('-e, --env <file>', 'Environment file to load', '.env')
21
+ .option('-p, --port <port>', 'Port number (if applicable)', parseInt)
22
+ .option('-w, --workers <count>', 'Number of worker processes', parseInt, 1)
22
23
  .option('-c, --cluster', 'Enable cluster mode')
23
- .option('--workers <count>', 'Number of worker processes (default: CPU count)', os_1.default.cpus().length.toString())
24
- .option('-m, --memory <mb>', 'Memory limit in MB', '512')
25
- .option('-t, --timeout <ms>', 'Startup timeout in milliseconds', '30000')
26
- .option('--graceful-timeout <ms>', 'Graceful shutdown timeout in milliseconds', '5000')
27
- .option('-v, --verbose', 'Verbose output')
28
- .option('-q, --quiet', 'Quiet output')
24
+ .option('-m, --memory <limit>', 'Memory limit (e.g., 512M, 1G)')
25
+ .option('-l, --log-level <level>', 'Log level (error, warn, info, debug)', 'info')
26
+ .option('-f, --log-file <file>', 'Log file path')
29
27
  .option('--no-color', 'Disable colored output')
30
- .option('--log-level <level>', 'Log level (error, warn, info, debug)', 'info')
31
- .option('--node-args <args>', 'Additional Node.js arguments (comma-separated)')
28
+ .option('-q, --quiet', 'Quiet mode')
29
+ .option('-v, --verbose', 'Verbose output')
30
+ .option('--watch', 'Watch for changes and restart')
31
+ .option('--max-memory <limit>', 'Maximum memory before restart (e.g., 1G)')
32
+ .option('--max-crashes <count>', 'Maximum crashes before giving up', parseInt, 5)
33
+ .option('--restart-delay <ms>', 'Delay between restarts (ms)', parseInt, 1000)
34
+ .option('--health-check', 'Enable health check monitoring')
35
+ .option('--health-port <port>', 'Health check port', parseInt, 3001)
36
+ .option('--graceful-timeout <ms>', 'Graceful shutdown timeout (ms)', parseInt, 30000)
32
37
  .option('--inspect', 'Enable Node.js inspector')
33
- .option('--inspect-port <port>', 'Inspector port', '9229')
34
- .option('--pid <file>', 'Write process ID to file')
35
- .option('--restart', 'Auto-restart on failure', true)
36
- .option('--restart-delay <ms>', 'Delay between restarts in milliseconds', '1000')
37
- .option('--max-restarts <count>', 'Maximum restart attempts', '10')
38
- .action(async (entry, options) => {
38
+ .option('--inspect-brk', 'Enable Node.js inspector with break')
39
+ .option('--node-args <args>', 'Additional Node.js arguments')
40
+ .action(async (file, options) => {
39
41
  try {
40
- const entryFile = entry || 'dist/index.js';
42
+ const targetFile = file || 'dist/index.js';
43
+ const resolvedFile = path_1.default.resolve(options.dir, targetFile);
44
+ // Check if file exists
45
+ if (!fs_1.default.existsSync(resolvedFile)) {
46
+ // Try to find main file from package.json
47
+ const packageJsonPath = path_1.default.join(options.dir, 'package.json');
48
+ if (fs_1.default.existsSync(packageJsonPath)) {
49
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
50
+ const mainFile = packageJson.main || 'index.js';
51
+ const alternativeFile = path_1.default.resolve(options.dir, mainFile);
52
+ if (fs_1.default.existsSync(alternativeFile)) {
53
+ if (options.verbose) {
54
+ logger_manager_js_1.loggerManager.printLine(`Using main file from package.json: ${mainFile}`, 'info');
55
+ }
56
+ }
57
+ else {
58
+ throw new Error(`Neither ${targetFile} nor ${mainFile} found. Please build your project first.`);
59
+ }
60
+ }
61
+ else {
62
+ throw new Error(`File not found: ${resolvedFile}. Please build your project first.`);
63
+ }
64
+ }
41
65
  if (!options.quiet) {
42
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Starting production server...`, 'info');
66
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Starting production server: ${chalk_1.default.cyan(targetFile)}`, 'info');
43
67
  }
44
- const nodeArgs = options.nodeArgs ?
45
- options.nodeArgs.split(',').map((arg) => arg.trim()) : [];
46
68
  startManager = new start_manager_js_1.StartManager({
47
- entry: entryFile,
48
- port: parseInt(options.port),
49
- host: options.host,
50
- env: options.env,
51
- watch: options.watch,
69
+ file: resolvedFile,
70
+ workingDir: options.dir,
71
+ envFile: options.env,
72
+ port: options.port,
73
+ workers: options.workers,
52
74
  cluster: options.cluster,
53
- workers: parseInt(options.workers),
54
- memory: parseInt(options.memory),
55
- timeout: parseInt(options.timeout),
56
- gracefulTimeout: parseInt(options.gracefulTimeout),
57
- verbose: options.verbose,
58
- quiet: options.quiet,
59
- color: options.color,
75
+ memoryLimit: options.memory,
60
76
  logLevel: options.logLevel,
61
- nodeArgs,
77
+ logFile: options.logFile,
78
+ color: options.color,
79
+ quiet: options.quiet,
80
+ verbose: options.verbose,
81
+ watch: options.watch,
82
+ maxMemory: options.maxMemory,
83
+ maxCrashes: options.maxCrashes,
84
+ restartDelay: options.restartDelay,
85
+ healthCheck: options.healthCheck,
86
+ healthPort: options.healthPort,
87
+ gracefulTimeout: options.gracefulTimeout,
62
88
  inspect: options.inspect,
63
- inspectPort: parseInt(options.inspectPort),
64
- pid: options.pid,
65
- restart: options.restart,
66
- restartDelay: parseInt(options.restartDelay),
67
- maxRestarts: parseInt(options.maxRestarts)
89
+ inspectBrk: options.inspectBrk,
90
+ nodeArgs: options.nodeArgs
68
91
  });
69
92
  await startManager.start();
70
93
  }
@@ -78,135 +101,109 @@ function addStartCommands(program) {
78
101
  process.exit(1);
79
102
  }
80
103
  });
81
- // Quick start command for development
104
+ // Quick start command
82
105
  program
83
- .command('serve [entry]')
84
- .description('Quick start with watch mode (development)')
85
- .option('-p, --port <port>', 'Port to run the server on', '3000')
86
- .option('-v, --verbose', 'Verbose output')
87
- .action(async (entry, options) => {
106
+ .command('run [file]')
107
+ .description('Quick start without options (alias for start)')
108
+ .action(async (file) => {
88
109
  try {
89
- const entryFile = entry || 'dist/index.js';
90
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Starting development server...`, 'info');
110
+ const targetFile = file || 'dist/index.js';
111
+ if (!fs_1.default.existsSync(targetFile)) {
112
+ throw new Error(`File not found: ${targetFile}. Please build your project first.`);
113
+ }
114
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Running: ${chalk_1.default.cyan(targetFile)}`, 'info');
91
115
  startManager = new start_manager_js_1.StartManager({
92
- entry: entryFile,
93
- port: parseInt(options.port),
94
- host: '127.0.0.1',
95
- env: 'development',
96
- watch: true,
97
- cluster: false,
116
+ file: path_1.default.resolve(targetFile),
117
+ workingDir: process.cwd(),
118
+ envFile: '.env',
119
+ port: undefined,
98
120
  workers: 1,
99
- memory: 256,
100
- timeout: 15000,
101
- gracefulTimeout: 3000,
102
- verbose: options.verbose,
103
- quiet: false,
104
- color: true,
121
+ cluster: false,
122
+ memoryLimit: undefined,
105
123
  logLevel: 'info',
106
- nodeArgs: [],
124
+ logFile: undefined,
125
+ color: true,
126
+ quiet: false,
127
+ verbose: false,
128
+ watch: false,
129
+ maxMemory: undefined,
130
+ maxCrashes: 5,
131
+ restartDelay: 1000,
132
+ healthCheck: false,
133
+ healthPort: 3001,
134
+ gracefulTimeout: 30000,
107
135
  inspect: false,
108
- inspectPort: 9229,
109
- pid: '',
110
- restart: true,
111
- restartDelay: 500,
112
- maxRestarts: 50
136
+ inspectBrk: false,
137
+ nodeArgs: undefined
113
138
  });
114
139
  await startManager.start();
115
140
  }
116
141
  catch (error) {
117
142
  if (error instanceof Error) {
118
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Serve failed: ${error.message}`, 'error');
143
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Run failed: ${error.message}`, 'error');
119
144
  }
120
145
  else {
121
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} An unknown serve error occurred`, 'error');
146
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} An unknown run error occurred`, 'error');
122
147
  }
123
148
  process.exit(1);
124
149
  }
125
150
  });
126
- // Production deployment command
151
+ // Production command with clustering
127
152
  program
128
- .command('deploy [entry]')
129
- .description('Deploy to production with optimal settings')
130
- .option('-p, --port <port>', 'Port to run the server on', process.env.PORT || '3000')
131
- .option('-w, --workers <count>', 'Number of worker processes', os_1.default.cpus().length.toString())
132
- .option('-m, --memory <mb>', 'Memory limit in MB', '1024')
133
- .option('--pid <file>', 'Write process ID to file', 'server.pid')
134
- .option('-v, --verbose', 'Verbose output')
135
- .action(async (entry, options) => {
153
+ .command('prod [file]')
154
+ .description('Start in production mode with clustering')
155
+ .option('-w, --workers <count>', 'Number of workers (default: CPU cores)', parseInt)
156
+ .option('-p, --port <port>', 'Port number', parseInt)
157
+ .option('-e, --env <file>', 'Environment file', '.env.production')
158
+ .option('-l, --log-file <file>', 'Log file path')
159
+ .option('-q, --quiet', 'Quiet mode')
160
+ .action(async (file, options) => {
136
161
  try {
137
- const entryFile = entry || 'dist/index.js';
138
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Deploying to production...`, 'info');
162
+ const targetFile = file || 'dist/index.js';
163
+ const resolvedFile = path_1.default.resolve(targetFile);
164
+ if (!fs_1.default.existsSync(resolvedFile)) {
165
+ throw new Error(`File not found: ${resolvedFile}. Please build your project first.`);
166
+ }
167
+ const workers = options.workers || require('os').cpus().length;
168
+ if (!options.quiet) {
169
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Starting production server with ${workers} workers`, 'info');
170
+ }
139
171
  startManager = new start_manager_js_1.StartManager({
140
- entry: entryFile,
141
- port: parseInt(options.port),
142
- host: '0.0.0.0',
143
- env: 'production',
144
- watch: false,
172
+ file: resolvedFile,
173
+ workingDir: process.cwd(),
174
+ envFile: options.env,
175
+ port: options.port,
176
+ workers,
145
177
  cluster: true,
146
- workers: parseInt(options.workers),
147
- memory: parseInt(options.memory),
148
- timeout: 30000,
149
- gracefulTimeout: 10000,
150
- verbose: options.verbose,
151
- quiet: false,
178
+ memoryLimit: undefined,
179
+ logLevel: 'info',
180
+ logFile: options.logFile,
152
181
  color: false,
153
- logLevel: 'warn',
154
- nodeArgs: ['--optimize-for-size'],
155
- inspect: false,
156
- inspectPort: 9229,
157
- pid: options.pid,
158
- restart: true,
182
+ quiet: options.quiet,
183
+ verbose: false,
184
+ watch: false,
185
+ maxMemory: '1G',
186
+ maxCrashes: 3,
159
187
  restartDelay: 2000,
160
- maxRestarts: 5
188
+ healthCheck: true,
189
+ healthPort: 3001,
190
+ gracefulTimeout: 30000,
191
+ inspect: false,
192
+ inspectBrk: false,
193
+ nodeArgs: undefined
161
194
  });
162
195
  await startManager.start();
163
196
  }
164
197
  catch (error) {
165
198
  if (error instanceof Error) {
166
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Deploy failed: ${error.message}`, 'error');
199
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Production start failed: ${error.message}`, 'error');
167
200
  }
168
201
  else {
169
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} An unknown deploy error occurred`, 'error');
202
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} An unknown production start error occurred`, 'error');
170
203
  }
171
204
  process.exit(1);
172
205
  }
173
206
  });
174
- // Health check command
175
- program
176
- .command('health [url]')
177
- .description('Check server health')
178
- .option('-t, --timeout <ms>', 'Request timeout in milliseconds', '5000')
179
- .option('-i, --interval <ms>', 'Check interval in milliseconds', '1000')
180
- .option('-c, --count <number>', 'Number of checks', '1')
181
- .action(async (url, options) => {
182
- const checkUrl = url || 'http://localhost:3000/health';
183
- const timeout = parseInt(options.timeout);
184
- const interval = parseInt(options.interval);
185
- const count = parseInt(options.count);
186
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Health check: ${checkUrl}`, 'info');
187
- for (let i = 0; i < count; i++) {
188
- try {
189
- const controller = new AbortController();
190
- const timeoutId = setTimeout(() => controller.abort(), timeout);
191
- const response = await fetch(checkUrl, {
192
- signal: controller.signal
193
- });
194
- clearTimeout(timeoutId);
195
- if (response.ok) {
196
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Health check ${i + 1}/${count}: OK (${response.status})`, 'info');
197
- }
198
- else {
199
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Health check ${i + 1}/${count}: Failed (${response.status})`, 'error');
200
- }
201
- }
202
- catch (error) {
203
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Health check ${i + 1}/${count}: Error (${error.message})`, 'error');
204
- }
205
- if (i < count - 1) {
206
- await new Promise(resolve => setTimeout(resolve, interval));
207
- }
208
- }
209
- });
210
207
  // Cleanup function
211
208
  const cleanupStart = async () => {
212
209
  if (startManager) {
@@ -222,7 +219,7 @@ function addStartCommands(program) {
222
219
  // Handle process termination
223
220
  const handleExit = (signal) => {
224
221
  if (startManager) {
225
- logger_manager_js_1.loggerManager.printLine(`\n${chalk_1.default.yellow(figures_1.default.warning)} Received ${signal}, shutting down gracefully...`, 'info');
222
+ logger_manager_js_1.loggerManager.printLine(`\n${chalk_1.default.yellow(figures_1.default.warning)} Received ${signal}, stopping server...`, 'info');
226
223
  cleanupStart().then(() => {
227
224
  process.exit(0);
228
225
  }).catch(() => {
@@ -236,19 +233,11 @@ function addStartCommands(program) {
236
233
  // Register signal handlers
237
234
  process.on('SIGINT', () => handleExit('SIGINT'));
238
235
  process.on('SIGTERM', () => handleExit('SIGTERM'));
239
- process.on('SIGQUIT', () => handleExit('SIGQUIT'));
240
- // Handle uncaught exceptions
241
- process.on('uncaughtException', (error) => {
242
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Uncaught exception: ${error.message}`, 'error');
243
- cleanupStart().then(() => {
244
- process.exit(1);
245
- });
246
- });
247
- process.on('unhandledRejection', (reason, promise) => {
248
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Unhandled rejection at: ${promise} reason: ${reason}`, 'error');
249
- cleanupStart().then(() => {
250
- process.exit(1);
251
- });
236
+ process.on('SIGUSR2', () => handleExit('SIGUSR2')); // PM2 restart
237
+ process.on('exit', () => {
238
+ if (startManager) {
239
+ cleanupStart();
240
+ }
252
241
  });
253
242
  return { cleanupStart };
254
243
  }
@@ -4,407 +4,453 @@ 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 - Production server manager for built TypeScript projects
7
+ // src/start-manager.ts - Production start manager with clustering and monitoring
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");
11
11
  const chalk_1 = __importDefault(require("chalk"));
12
- const figures_1 = __importDefault(require("figures"));
13
12
  const path_1 = __importDefault(require("path"));
14
- const promises_1 = __importDefault(require("fs/promises"));
15
- const fs_1 = require("fs");
16
- const perf_hooks_1 = require("perf_hooks");
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const http_1 = __importDefault(require("http"));
15
+ const lodash_1 = require("lodash");
17
16
  class StartManager {
18
17
  constructor(options) {
19
- this.serverProcess = null;
18
+ this.workers = new Map();
19
+ this.masterProcess = null;
20
20
  this.watcher = null;
21
- this.isStarting = false;
22
- this.restartCount = 0;
23
- this.startTime = 0;
21
+ this.healthServer = null;
24
22
  this.isShuttingDown = false;
25
- this.restartTimer = null;
23
+ this.logStream = null;
24
+ this.totalRestarts = 0;
26
25
  this.options = options;
26
+ this.startTime = new Date();
27
+ this.debouncedRestart = (0, lodash_1.debounce)(this.restartAll.bind(this), options.restartDelay);
28
+ this.setupLogging();
27
29
  }
28
- async validateEntry() {
29
- let entryPath = this.options.entry;
30
- // Check if entry exists as provided
31
- if ((0, fs_1.existsSync)(entryPath)) {
32
- return path_1.default.resolve(entryPath);
33
- }
34
- // Try with .js extension
35
- if (!entryPath.endsWith('.js')) {
36
- const jsPath = entryPath + '.js';
37
- if ((0, fs_1.existsSync)(jsPath)) {
38
- return path_1.default.resolve(jsPath);
30
+ setupLogging() {
31
+ if (this.options.logFile) {
32
+ try {
33
+ const logDir = path_1.default.dirname(this.options.logFile);
34
+ if (!fs_1.default.existsSync(logDir)) {
35
+ fs_1.default.mkdirSync(logDir, { recursive: true });
36
+ }
37
+ this.logStream = fs_1.default.createWriteStream(this.options.logFile, { flags: 'a' });
38
+ if (this.options.verbose) {
39
+ logger_manager_js_1.loggerManager.printLine(`Logging to: ${this.options.logFile}`, 'info');
40
+ }
41
+ }
42
+ catch (error) {
43
+ logger_manager_js_1.loggerManager.printLine(`Failed to setup logging: ${error.message}`, 'warn');
39
44
  }
40
45
  }
41
- // Try in dist directory
42
- const distPath = path_1.default.join('dist', entryPath);
43
- if ((0, fs_1.existsSync)(distPath)) {
44
- return path_1.default.resolve(distPath);
46
+ }
47
+ log(message, level = 'info') {
48
+ const timestamp = new Date().toISOString();
49
+ const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
50
+ if (this.logStream) {
51
+ this.logStream.write(logMessage + '\n');
45
52
  }
46
- // Try in dist with .js extension
47
- if (!entryPath.endsWith('.js')) {
48
- const distJsPath = path_1.default.join('dist', entryPath + '.js');
49
- if ((0, fs_1.existsSync)(distJsPath)) {
50
- return path_1.default.resolve(distJsPath);
51
- }
53
+ if (!this.options.quiet) {
54
+ logger_manager_js_1.loggerManager.printLine(message, level);
52
55
  }
53
- // Try common entry points
54
- const commonEntries = [
55
- 'dist/index.js',
56
- 'dist/server.js',
57
- 'dist/app.js',
58
- 'dist/main.js',
59
- 'index.js',
60
- 'server.js'
61
- ];
62
- for (const entry of commonEntries) {
63
- if ((0, fs_1.existsSync)(entry)) {
64
- if (!this.options.quiet) {
65
- logger_manager_js_1.loggerManager.printLine(`Using entry point: ${entry}`, 'info');
56
+ }
57
+ loadEnvFile() {
58
+ if (this.options.envFile && fs_1.default.existsSync(this.options.envFile)) {
59
+ try {
60
+ const envContent = fs_1.default.readFileSync(this.options.envFile, 'utf8');
61
+ const lines = envContent.split('\n');
62
+ for (const line of lines) {
63
+ const trimmed = line.trim();
64
+ if (trimmed && !trimmed.startsWith('#')) {
65
+ const [key, ...values] = trimmed.split('=');
66
+ if (key && values.length > 0) {
67
+ const value = values.join('=').trim();
68
+ // Remove quotes if present
69
+ const cleanValue = value.replace(/^["']|["']$/g, '');
70
+ process.env[key.trim()] = cleanValue;
71
+ }
72
+ }
73
+ }
74
+ if (this.options.verbose) {
75
+ this.log(`Loaded environment variables from ${this.options.envFile}`);
66
76
  }
67
- return path_1.default.resolve(entry);
68
77
  }
69
- }
70
- throw new Error(`Entry file not found: ${entryPath}`);
71
- }
72
- async loadPackageJson() {
73
- try {
74
- const packageJsonPath = path_1.default.join(process.cwd(), 'package.json');
75
- if ((0, fs_1.existsSync)(packageJsonPath)) {
76
- const content = await promises_1.default.readFile(packageJsonPath, 'utf8');
77
- return JSON.parse(content);
78
+ catch (error) {
79
+ this.log(`Failed to load environment file: ${error.message}`, 'warn');
78
80
  }
79
81
  }
80
- catch (error) {
81
- // Ignore package.json errors
82
- }
83
- return null;
82
+ }
83
+ parseMemoryLimit(limit) {
84
+ var _a;
85
+ if (!limit)
86
+ return undefined;
87
+ const match = limit.match(/^(\d+)([KMGT]?)$/i);
88
+ if (!match)
89
+ return undefined;
90
+ const value = parseInt(match[1]);
91
+ const unit = ((_a = match[2]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || '';
92
+ const multipliers = { K: 1024, M: 1024 * 1024, G: 1024 * 1024 * 1024, T: 1024 * 1024 * 1024 * 1024 };
93
+ return value * (multipliers[unit] || 1);
84
94
  }
85
95
  getNodeArgs() {
86
96
  const args = [];
87
- // Add custom node arguments
88
- if (this.options.nodeArgs.length > 0) {
89
- args.push(...this.options.nodeArgs);
97
+ if (this.options.memoryLimit) {
98
+ args.push(`--max-old-space-size=${this.parseMemoryLimit(this.options.memoryLimit) / (1024 * 1024)}`);
90
99
  }
91
- // Memory limit
92
- if (this.options.memory > 0) {
93
- args.push(`--max-old-space-size=${this.options.memory}`);
94
- }
95
- // Debugging
96
100
  if (this.options.inspect) {
97
- args.push(`--inspect=${this.options.inspectPort}`);
101
+ args.push('--inspect');
102
+ }
103
+ if (this.options.inspectBrk) {
104
+ args.push('--inspect-brk');
98
105
  }
99
- // Production optimizations
100
- if (this.options.env === 'production') {
101
- args.push('--optimize-for-size');
106
+ if (this.options.nodeArgs) {
107
+ args.push(...this.options.nodeArgs.split(' '));
102
108
  }
103
109
  return args;
104
110
  }
105
- getEnvironment() {
111
+ async startWorker(workerId) {
112
+ var _a, _b;
113
+ const nodeArgs = this.getNodeArgs();
106
114
  const env = {
107
115
  ...process.env,
108
- NODE_ENV: this.options.env,
109
- PORT: this.options.port.toString(),
110
- HOST: this.options.host,
111
- LOG_LEVEL: this.options.logLevel
116
+ NODE_ENV: process.env.NODE_ENV || 'production',
117
+ WORKER_ID: workerId.toString(),
118
+ CLUSTER_WORKER: 'true',
119
+ FORCE_COLOR: this.options.color ? '1' : '0'
112
120
  };
113
- // Add color support
114
- if (this.options.color) {
115
- env.FORCE_COLOR = '1';
121
+ if (this.options.port) {
122
+ env.PORT = this.options.port.toString();
116
123
  }
117
- return env;
118
- }
119
- async writePidFile(pid) {
120
- if (this.options.pid) {
121
- try {
122
- await promises_1.default.writeFile(this.options.pid, pid.toString());
123
- if (this.options.verbose) {
124
- logger_manager_js_1.loggerManager.printLine(`PID file written: ${this.options.pid}`, 'info');
124
+ const workerProcess = (0, child_process_1.fork)(this.options.file, [], {
125
+ cwd: this.options.workingDir,
126
+ env,
127
+ execArgv: nodeArgs,
128
+ silent: true
129
+ });
130
+ const workerInfo = {
131
+ process: workerProcess,
132
+ pid: workerProcess.pid,
133
+ restarts: 0,
134
+ startTime: new Date(),
135
+ memoryUsage: 0,
136
+ cpuUsage: 0
137
+ };
138
+ this.workers.set(workerId, workerInfo);
139
+ // Handle worker output
140
+ (_a = workerProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
141
+ if (!this.options.quiet) {
142
+ const message = data.toString().trim();
143
+ if (message) {
144
+ console.log(chalk_1.default.dim(`[Worker ${workerId}]`) + ' ' + message);
125
145
  }
126
146
  }
127
- catch (error) {
128
- logger_manager_js_1.loggerManager.printLine(`Failed to write PID file: ${error.message}`, 'warn');
147
+ });
148
+ (_b = workerProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
149
+ if (!this.options.quiet) {
150
+ const message = data.toString().trim();
151
+ if (message) {
152
+ console.error(chalk_1.default.dim(`[Worker ${workerId}]`) + ' ' + chalk_1.default.red(message));
153
+ }
129
154
  }
130
- }
155
+ });
156
+ workerProcess.on('error', (error) => {
157
+ this.log(`Worker ${workerId} error: ${error.message}`, 'error');
158
+ });
159
+ workerProcess.on('exit', (code, signal) => {
160
+ this.workers.delete(workerId);
161
+ if (!this.isShuttingDown) {
162
+ if (code !== 0) {
163
+ this.log(`Worker ${workerId} exited with code ${code}`, 'error');
164
+ this.restartWorker(workerId);
165
+ }
166
+ else {
167
+ this.log(`Worker ${workerId} exited gracefully`);
168
+ }
169
+ }
170
+ });
171
+ workerProcess.on('message', (message) => {
172
+ if (this.options.verbose) {
173
+ this.log(`Worker ${workerId} message: ${JSON.stringify(message)}`);
174
+ }
175
+ });
176
+ this.log(`Started worker ${workerId} (PID: ${workerProcess.pid})`);
177
+ return workerInfo;
131
178
  }
132
- async removePidFile() {
133
- if (this.options.pid && (0, fs_1.existsSync)(this.options.pid)) {
179
+ async restartWorker(workerId) {
180
+ const workerInfo = this.workers.get(workerId);
181
+ if (workerInfo) {
182
+ workerInfo.restarts++;
183
+ this.totalRestarts++;
184
+ if (workerInfo.restarts >= this.options.maxCrashes) {
185
+ this.log(`Worker ${workerId} reached max crashes (${this.options.maxCrashes}), not restarting`, 'error');
186
+ return;
187
+ }
188
+ this.log(`Restarting worker ${workerId} (restart #${workerInfo.restarts})`);
189
+ // Graceful shutdown
134
190
  try {
135
- await promises_1.default.unlink(this.options.pid);
136
- if (this.options.verbose) {
137
- logger_manager_js_1.loggerManager.printLine(`PID file removed: ${this.options.pid}`, 'info');
138
- }
191
+ workerInfo.process.kill('SIGTERM');
192
+ await this.waitForProcessExit(workerInfo.process, 5000);
139
193
  }
140
194
  catch (error) {
141
- // Ignore cleanup errors
195
+ workerInfo.process.kill('SIGKILL');
142
196
  }
197
+ // Start new worker
198
+ setTimeout(() => {
199
+ this.startWorker(workerId);
200
+ }, this.options.restartDelay);
143
201
  }
144
202
  }
145
- async startServer() {
146
- if (this.isStarting || this.isShuttingDown) {
147
- return;
148
- }
149
- this.isStarting = true;
150
- this.startTime = perf_hooks_1.performance.now();
151
- try {
152
- const entryPath = await this.validateEntry();
153
- const nodeArgs = this.getNodeArgs();
154
- const env = this.getEnvironment();
155
- if (!this.options.quiet) {
156
- const restartInfo = this.restartCount > 0 ? ` (restart #${this.restartCount})` : '';
157
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Starting server${restartInfo}...`, 'info');
158
- if (this.options.verbose) {
159
- logger_manager_js_1.loggerManager.printLine(`Entry: ${entryPath}`, 'info');
160
- logger_manager_js_1.loggerManager.printLine(`Environment: ${this.options.env}`, 'info');
161
- logger_manager_js_1.loggerManager.printLine(`Host: ${this.options.host}:${this.options.port}`, 'info');
162
- if (nodeArgs.length > 0) {
163
- logger_manager_js_1.loggerManager.printLine(`Node args: ${nodeArgs.join(' ')}`, 'info');
164
- }
165
- }
166
- }
167
- const args = [...nodeArgs, entryPath];
168
- this.serverProcess = (0, child_process_1.spawn)('node', args, {
169
- stdio: this.options.verbose ? ['ignore', 'pipe', 'pipe'] : ['ignore', 'ignore', 'pipe'],
170
- env,
171
- detached: false,
172
- cwd: process.cwd()
203
+ async waitForProcessExit(process, timeout) {
204
+ return new Promise((resolve, reject) => {
205
+ const timer = setTimeout(() => {
206
+ reject(new Error('Process exit timeout'));
207
+ }, timeout);
208
+ process.on('exit', () => {
209
+ clearTimeout(timer);
210
+ resolve();
173
211
  });
174
- if (this.serverProcess.pid) {
175
- await this.writePidFile(this.serverProcess.pid);
176
- }
177
- // Handle server output
178
- if (this.options.verbose && this.serverProcess.stdout) {
179
- this.serverProcess.stdout.on('data', (data) => {
180
- const output = data.toString().trim();
181
- if (output) {
182
- logger_manager_js_1.loggerManager.printLine(`[SERVER] ${output}`, 'info');
212
+ });
213
+ }
214
+ async startSingleProcess() {
215
+ const nodeArgs = this.getNodeArgs();
216
+ const env = {
217
+ ...process.env,
218
+ NODE_ENV: process.env.NODE_ENV || 'production',
219
+ FORCE_COLOR: this.options.color ? '1' : '0'
220
+ };
221
+ if (this.options.port) {
222
+ env.PORT = this.options.port.toString();
223
+ }
224
+ this.masterProcess = (0, child_process_1.spawn)('node', [...nodeArgs, this.options.file], {
225
+ cwd: this.options.workingDir,
226
+ env,
227
+ stdio: this.options.quiet ? 'ignore' : 'inherit'
228
+ });
229
+ this.masterProcess.on('error', (error) => {
230
+ this.log(`Process error: ${error.message}`, 'error');
231
+ });
232
+ this.masterProcess.on('exit', (code, signal) => {
233
+ if (!this.isShuttingDown) {
234
+ if (code !== 0) {
235
+ this.log(`Process exited with code ${code}`, 'error');
236
+ if (this.totalRestarts < this.options.maxCrashes) {
237
+ this.totalRestarts++;
238
+ this.log(`Restarting process (restart #${this.totalRestarts})`);
239
+ setTimeout(() => {
240
+ this.startSingleProcess();
241
+ }, this.options.restartDelay);
183
242
  }
184
- });
185
- }
186
- // Handle server errors
187
- if (this.serverProcess.stderr) {
188
- this.serverProcess.stderr.on('data', (data) => {
189
- const error = data.toString().trim();
190
- if (error && !this.options.quiet) {
191
- logger_manager_js_1.loggerManager.printLine(`[SERVER] ${error}`, 'error');
243
+ else {
244
+ this.log(`Reached max crashes (${this.options.maxCrashes}), giving up`, 'error');
245
+ process.exit(1);
192
246
  }
193
- });
194
- }
195
- // Handle process events
196
- this.serverProcess.on('error', (error) => {
197
- this.isStarting = false;
198
- if (!this.isShuttingDown) {
199
- logger_manager_js_1.loggerManager.printLine(`Server process error: ${error.message}`, 'error');
200
- this.handleServerExit(1);
201
247
  }
202
- });
203
- this.serverProcess.on('exit', (code, signal) => {
204
- this.isStarting = false;
205
- if (!this.isShuttingDown) {
206
- this.handleServerExit(code || 0, signal || undefined);
248
+ else {
249
+ this.log('Process exited gracefully');
207
250
  }
208
- });
209
- // Wait for server to start
210
- await this.waitForServerStart();
211
- this.isStarting = false;
212
- const duration = Math.round(perf_hooks_1.performance.now() - this.startTime);
213
- if (!this.options.quiet) {
214
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Server started successfully in ${duration}ms`, 'info');
215
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Running on ${chalk_1.default.cyan(`http://${this.options.host}:${this.options.port}`)}`, 'info');
216
251
  }
217
- }
218
- catch (error) {
219
- this.isStarting = false;
220
- throw error;
252
+ });
253
+ this.log(`Started process (PID: ${this.masterProcess.pid})`);
254
+ }
255
+ async startCluster() {
256
+ this.log(`Starting cluster with ${this.options.workers} workers`);
257
+ for (let i = 0; i < this.options.workers; i++) {
258
+ await this.startWorker(i + 1);
221
259
  }
222
260
  }
223
- async waitForServerStart() {
224
- return new Promise((resolve, reject) => {
225
- const timeout = setTimeout(() => {
226
- reject(new Error(`Server startup timeout after ${this.options.timeout}ms`));
227
- }, this.options.timeout);
228
- if (this.serverProcess) {
229
- // If process exits immediately, it's likely an error
230
- const exitHandler = (code) => {
231
- clearTimeout(timeout);
232
- if (code !== 0) {
233
- reject(new Error(`Server exited with code ${code}`));
234
- }
235
- else {
236
- resolve();
237
- }
261
+ setupHealthCheck() {
262
+ if (!this.options.healthCheck)
263
+ return;
264
+ this.healthServer = http_1.default.createServer((req, res) => {
265
+ if (req.url === '/health') {
266
+ const stats = {
267
+ status: 'ok',
268
+ uptime: Date.now() - this.startTime.getTime(),
269
+ workers: this.workers.size,
270
+ totalRestarts: this.totalRestarts,
271
+ memoryUsage: process.memoryUsage(),
272
+ cpuUsage: process.cpuUsage()
238
273
  };
239
- this.serverProcess.once('exit', exitHandler);
240
- // Give some time for the server to start
241
- setTimeout(() => {
242
- if (this.serverProcess && !this.serverProcess.killed) {
243
- this.serverProcess.removeListener('exit', exitHandler);
244
- clearTimeout(timeout);
245
- resolve();
246
- }
247
- }, 1000);
274
+ res.writeHead(200, { 'Content-Type': 'application/json' });
275
+ res.end(JSON.stringify(stats, null, 2));
248
276
  }
249
277
  else {
250
- clearTimeout(timeout);
251
- reject(new Error('Server process not created'));
278
+ res.writeHead(404);
279
+ res.end('Not Found');
252
280
  }
253
281
  });
254
- }
255
- handleServerExit(code, signal) {
256
- this.removePidFile();
257
- if (this.isShuttingDown) {
258
- return;
259
- }
260
- const exitMessage = signal
261
- ? `Server process killed with signal ${signal}`
262
- : `Server process exited with code ${code}`;
263
- if (code !== 0 && !this.options.quiet) {
264
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} ${exitMessage}`, 'error');
265
- }
266
- // Handle restart logic
267
- if (this.options.restart && code !== 0 && this.restartCount < this.options.maxRestarts) {
268
- this.scheduleRestart();
269
- }
270
- else if (this.restartCount >= this.options.maxRestarts) {
271
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Maximum restart attempts reached (${this.options.maxRestarts})`, 'error');
272
- process.exit(1);
273
- }
274
- else if (code !== 0) {
275
- process.exit(code);
276
- }
277
- }
278
- scheduleRestart() {
279
- this.restartCount++;
280
- const delay = this.options.restartDelay * Math.min(this.restartCount, 5); // Exponential backoff
281
- if (!this.options.quiet) {
282
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.warning)} Restarting server in ${delay}ms... (${this.restartCount}/${this.options.maxRestarts})`, 'info');
283
- }
284
- this.restartTimer = setTimeout(() => {
285
- this.restartTimer = null;
286
- this.startServer().catch(error => {
287
- logger_manager_js_1.loggerManager.printLine(`Restart failed: ${error.message}`, 'error');
288
- this.handleServerExit(1);
289
- });
290
- }, delay);
282
+ this.healthServer.listen(this.options.healthPort, () => {
283
+ this.log(`Health check server listening on port ${this.options.healthPort}`);
284
+ });
291
285
  }
292
286
  setupWatcher() {
293
287
  if (!this.options.watch)
294
288
  return;
295
289
  const watchPatterns = [
296
- 'dist/**/*.js',
297
- 'package.json',
298
- 'dist/package.json'
290
+ `${this.options.workingDir}/**/*.js`,
291
+ `${this.options.workingDir}/**/*.json`,
292
+ `${this.options.workingDir}/**/*.env*`
299
293
  ];
300
294
  this.watcher = (0, chokidar_1.watch)(watchPatterns, {
301
- ignoreInitial: true,
302
- followSymlinks: false,
303
- usePolling: false,
304
- atomic: 500,
305
295
  ignored: [
306
296
  '**/node_modules/**',
307
297
  '**/.git/**',
308
- '**/*.log',
309
- '**/*.map'
310
- ]
298
+ '**/logs/**',
299
+ '**/*.log'
300
+ ],
301
+ ignoreInitial: true,
302
+ followSymlinks: false,
303
+ usePolling: false,
304
+ atomic: 300
311
305
  });
312
306
  this.watcher.on('change', (filePath) => {
313
307
  if (this.options.verbose) {
314
- logger_manager_js_1.loggerManager.printLine(`File changed: ${path_1.default.relative(process.cwd(), filePath)}`, 'info');
308
+ this.log(`File changed: ${path_1.default.relative(this.options.workingDir, filePath)}`);
315
309
  }
316
- this.restart();
310
+ this.debouncedRestart();
317
311
  });
318
312
  this.watcher.on('error', (error) => {
319
- logger_manager_js_1.loggerManager.printLine(`Watcher error: ${error.message}`, 'error');
313
+ this.log(`Watcher error: ${error.message}`, 'error');
320
314
  });
321
- if (this.options.verbose) {
322
- logger_manager_js_1.loggerManager.printLine(`Watching: ${watchPatterns.join(', ')}`, 'info');
323
- }
315
+ this.log('File watcher enabled');
324
316
  }
325
- async restart() {
317
+ async restartAll() {
326
318
  if (this.isShuttingDown)
327
319
  return;
328
- if (!this.options.quiet) {
329
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.warning)} Restarting server...`, 'info');
320
+ this.log('Restarting all processes due to file changes');
321
+ if (this.options.cluster) {
322
+ // Graceful restart of workers
323
+ for (const [workerId, workerInfo] of this.workers.entries()) {
324
+ try {
325
+ workerInfo.process.kill('SIGTERM');
326
+ await this.waitForProcessExit(workerInfo.process, 5000);
327
+ }
328
+ catch (error) {
329
+ workerInfo.process.kill('SIGKILL');
330
+ }
331
+ // Start new worker
332
+ setTimeout(() => {
333
+ this.startWorker(workerId);
334
+ }, this.options.restartDelay);
335
+ }
330
336
  }
331
- await this.stopServer();
332
- // Small delay before restart
333
- setTimeout(() => {
334
- this.startServer().catch(error => {
335
- logger_manager_js_1.loggerManager.printLine(`Restart failed: ${error.message}`, 'error');
336
- process.exit(1);
337
- });
338
- }, 100);
339
- }
340
- async stopServer() {
341
- if (!this.serverProcess)
342
- return;
343
- return new Promise((resolve) => {
344
- if (!this.serverProcess) {
345
- resolve();
346
- return;
337
+ else if (this.masterProcess) {
338
+ // Restart single process
339
+ try {
340
+ this.masterProcess.kill('SIGTERM');
341
+ await this.waitForProcessExit(this.masterProcess, 5000);
347
342
  }
348
- const proc = this.serverProcess;
349
- this.serverProcess = null;
350
- if (this.restartTimer) {
351
- clearTimeout(this.restartTimer);
352
- this.restartTimer = null;
343
+ catch (error) {
344
+ this.masterProcess.kill('SIGKILL');
353
345
  }
354
- const cleanup = () => {
355
- this.removePidFile();
356
- resolve();
357
- };
358
- proc.on('exit', cleanup);
359
- proc.on('error', cleanup);
360
- try {
361
- if (proc.pid) {
362
- // Graceful shutdown
363
- proc.kill('SIGTERM');
364
- // Force kill after timeout
365
- setTimeout(() => {
366
- if (proc.pid && !proc.killed) {
367
- try {
368
- proc.kill('SIGKILL');
369
- }
370
- catch (e) {
371
- // Ignore
346
+ setTimeout(() => {
347
+ this.startSingleProcess();
348
+ }, this.options.restartDelay);
349
+ }
350
+ }
351
+ setupMonitoring() {
352
+ setInterval(() => {
353
+ if (this.options.verbose) {
354
+ this.workers.forEach((workerInfo, workerId) => {
355
+ try {
356
+ const usage = process.memoryUsage();
357
+ workerInfo.memoryUsage = usage.heapUsed;
358
+ if (this.options.maxMemory) {
359
+ const maxMemory = this.parseMemoryLimit(this.options.maxMemory);
360
+ if (maxMemory && usage.heapUsed > maxMemory) {
361
+ this.log(`Worker ${workerId} exceeded memory limit, restarting`, 'warn');
362
+ this.restartWorker(workerId);
372
363
  }
373
364
  }
374
- }, this.options.gracefulTimeout);
375
- }
376
- }
377
- catch (error) {
378
- cleanup();
365
+ }
366
+ catch (error) {
367
+ // Ignore monitoring errors
368
+ }
369
+ });
379
370
  }
380
- });
371
+ }, 10000); // Check every 10 seconds
381
372
  }
382
373
  async start() {
383
374
  try {
384
- await this.startServer();
375
+ this.loadEnvFile();
376
+ this.setupHealthCheck();
385
377
  this.setupWatcher();
386
- if (this.options.watch && !this.options.quiet) {
387
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Watching for changes...`, 'info');
378
+ this.setupMonitoring();
379
+ const runtime = Date.now() - this.startTime.getTime();
380
+ this.log(`Starting application in ${runtime}ms`);
381
+ if (this.options.cluster) {
382
+ await this.startCluster();
383
+ }
384
+ else {
385
+ await this.startSingleProcess();
386
+ }
387
+ this.log(`Application started successfully`);
388
+ if (this.options.verbose) {
389
+ this.log(`Configuration: ${JSON.stringify({
390
+ workers: this.options.workers,
391
+ cluster: this.options.cluster,
392
+ watch: this.options.watch,
393
+ healthCheck: this.options.healthCheck,
394
+ port: this.options.port
395
+ })}`);
388
396
  }
389
397
  }
390
398
  catch (error) {
391
- logger_manager_js_1.loggerManager.printLine(`Failed to start server: ${error.message}`, 'error');
392
- process.exit(1);
399
+ this.log(`Failed to start application: ${error.message}`, 'error');
400
+ throw error;
393
401
  }
394
402
  }
395
403
  async stop() {
404
+ if (this.isShuttingDown)
405
+ return;
396
406
  this.isShuttingDown = true;
397
- if (!this.options.quiet) {
398
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.warning)} Stopping server...`, 'info');
399
- }
407
+ this.log('Stopping application...');
408
+ // Stop watcher
400
409
  if (this.watcher) {
401
410
  await this.watcher.close();
402
411
  this.watcher = null;
403
412
  }
404
- await this.stopServer();
405
- if (!this.options.quiet) {
406
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.tick)} Server stopped successfully`, 'info');
413
+ // Stop health server
414
+ if (this.healthServer) {
415
+ this.healthServer.close();
416
+ this.healthServer = null;
417
+ }
418
+ // Stop all workers
419
+ const shutdownPromises = [];
420
+ for (const [workerId, workerInfo] of this.workers.entries()) {
421
+ shutdownPromises.push((async () => {
422
+ try {
423
+ workerInfo.process.kill('SIGTERM');
424
+ await this.waitForProcessExit(workerInfo.process, this.options.gracefulTimeout);
425
+ }
426
+ catch (error) {
427
+ workerInfo.process.kill('SIGKILL');
428
+ }
429
+ })());
430
+ }
431
+ // Stop master process
432
+ if (this.masterProcess) {
433
+ shutdownPromises.push((async () => {
434
+ try {
435
+ this.masterProcess.kill('SIGTERM');
436
+ await this.waitForProcessExit(this.masterProcess, this.options.gracefulTimeout);
437
+ }
438
+ catch (error) {
439
+ this.masterProcess.kill('SIGKILL');
440
+ }
441
+ })());
442
+ }
443
+ // Wait for all processes to shut down
444
+ await Promise.allSettled(shutdownPromises);
445
+ // Close log stream
446
+ if (this.logStream) {
447
+ this.logStream.end();
448
+ this.logStream = null;
407
449
  }
450
+ const uptime = Date.now() - this.startTime.getTime();
451
+ this.log(`Application stopped after ${uptime}ms uptime`);
452
+ this.workers.clear();
453
+ this.masterProcess = null;
408
454
  }
409
455
  }
410
456
  exports.StartManager = StartManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neex",
3
- "version": "0.6.50",
3
+ "version": "0.6.51",
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",