neex 0.6.50 → 0.6.52

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,205 +8,94 @@ 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 path_1 = __importDefault(require("path"));
12
+ const fs_1 = __importDefault(require("fs"));
11
13
  const os_1 = __importDefault(require("os"));
12
14
  function addStartCommands(program) {
13
15
  let startManager = null;
14
- // Main start command
16
+ // Single optimized start command for production
15
17
  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')
22
- .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')
18
+ .command('start [file]')
19
+ .description('Start production application with automatic optimization')
20
+ .option('-d, --dir <directory>', 'Working directory', process.cwd())
21
+ .option('-e, --env <file>', 'Environment file to load', '.env')
22
+ .option('-p, --port <port>', 'Port number', parseInt)
23
+ .option('-w, --workers <count>', 'Number of worker processes (default: auto)', parseInt)
27
24
  .option('-v, --verbose', 'Verbose output')
28
- .option('-q, --quiet', 'Quiet output')
29
- .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)')
25
+ .option('--watch', 'Watch for changes and restart (development mode)')
26
+ .option('--no-health', 'Disable health check endpoint')
27
+ .option('--health-port <port>', 'Health check port', parseInt, 3001)
28
+ .option('--max-memory <limit>', 'Maximum memory before restart (e.g., 1G)')
29
+ .option('--graceful-timeout <ms>', 'Graceful shutdown timeout (ms)', parseInt, 30000)
32
30
  .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) => {
31
+ .option('--inspect-brk', 'Enable Node.js inspector with break')
32
+ .action(async (file, options) => {
39
33
  try {
40
- const entryFile = entry || 'dist/index.js';
41
- if (!options.quiet) {
42
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.blue(figures_1.default.info)} Starting production server...`, 'info');
43
- }
44
- const nodeArgs = options.nodeArgs ?
45
- options.nodeArgs.split(',').map((arg) => arg.trim()) : [];
46
- 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,
52
- 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,
60
- logLevel: options.logLevel,
61
- nodeArgs,
62
- 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)
68
- });
69
- await startManager.start();
70
- }
71
- catch (error) {
72
- if (error instanceof Error) {
73
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Start failed: ${error.message}`, 'error');
74
- }
75
- else {
76
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} An unknown start error occurred`, 'error');
34
+ const targetFile = file || 'dist/index.js';
35
+ const resolvedFile = path_1.default.resolve(options.dir, targetFile);
36
+ // Auto-detect main file if not found
37
+ if (!fs_1.default.existsSync(resolvedFile)) {
38
+ const packageJsonPath = path_1.default.join(options.dir, 'package.json');
39
+ 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');
46
+ }
47
+ }
48
+ else {
49
+ throw new Error(`Application file not found. Please build your project first.`);
50
+ }
51
+ }
52
+ else {
53
+ throw new Error(`Application file not found: ${resolvedFile}`);
54
+ }
77
55
  }
78
- process.exit(1);
79
- }
80
- });
81
- // Quick start command for development
82
- 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) => {
88
- 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');
56
+ // Auto-detect optimal configuration
57
+ const isDevelopment = options.watch || process.env.NODE_ENV === 'development';
58
+ const isProduction = !isDevelopment;
59
+ const cpuCount = os_1.default.cpus().length;
60
+ const workers = options.workers || (isProduction ? cpuCount : 1);
61
+ const healthCheck = options.health !== false && isProduction;
62
+ // Startup logging
63
+ const mode = isDevelopment ? 'development' : 'production';
64
+ const clusterInfo = workers > 1 ? ` (${workers} workers)` : '';
65
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.green(figures_1.default.play)} Starting ${mode} server${clusterInfo}`, 'info');
91
66
  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,
98
- workers: 1,
99
- memory: 256,
100
- timeout: 15000,
101
- gracefulTimeout: 3000,
102
- verbose: options.verbose,
103
- quiet: false,
67
+ file: resolvedFile,
68
+ workingDir: options.dir,
69
+ envFile: options.env,
70
+ port: options.port,
71
+ workers,
72
+ memoryLimit: isProduction ? '512M' : undefined,
73
+ logLevel: options.verbose ? 'debug' : 'info',
104
74
  color: true,
105
- logLevel: 'info',
106
- nodeArgs: [],
107
- inspect: false,
108
- inspectPort: 9229,
109
- pid: '',
110
- restart: true,
111
- restartDelay: 500,
112
- maxRestarts: 50
113
- });
114
- await startManager.start();
115
- }
116
- catch (error) {
117
- if (error instanceof Error) {
118
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Serve failed: ${error.message}`, 'error');
119
- }
120
- else {
121
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} An unknown serve error occurred`, 'error');
122
- }
123
- process.exit(1);
124
- }
125
- });
126
- // Production deployment command
127
- 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) => {
136
- 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');
139
- 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,
145
- cluster: true,
146
- workers: parseInt(options.workers),
147
- memory: parseInt(options.memory),
148
- timeout: 30000,
149
- gracefulTimeout: 10000,
150
75
  verbose: options.verbose,
151
- quiet: false,
152
- color: false,
153
- logLevel: 'warn',
154
- nodeArgs: ['--optimize-for-size'],
155
- inspect: false,
156
- inspectPort: 9229,
157
- pid: options.pid,
158
- restart: true,
159
- restartDelay: 2000,
160
- maxRestarts: 5
76
+ watch: options.watch,
77
+ maxMemory: options.maxMemory || (isProduction ? '1G' : undefined),
78
+ maxCrashes: isProduction ? 3 : 5,
79
+ restartDelay: isProduction ? 2000 : 1000,
80
+ healthCheck,
81
+ healthPort: options.healthPort,
82
+ gracefulTimeout: options.gracefulTimeout,
83
+ inspect: options.inspect,
84
+ inspectBrk: options.inspectBrk,
85
+ nodeArgs: undefined
161
86
  });
162
87
  await startManager.start();
163
88
  }
164
89
  catch (error) {
165
90
  if (error instanceof Error) {
166
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Deploy failed: ${error.message}`, 'error');
91
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} ${error.message}`, 'error');
167
92
  }
168
93
  else {
169
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} An unknown deploy error occurred`, 'error');
94
+ logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Startup failed`, 'error');
170
95
  }
171
96
  process.exit(1);
172
97
  }
173
98
  });
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
99
  // Cleanup function
211
100
  const cleanupStart = async () => {
212
101
  if (startManager) {
@@ -222,7 +111,6 @@ function addStartCommands(program) {
222
111
  // Handle process termination
223
112
  const handleExit = (signal) => {
224
113
  if (startManager) {
225
- logger_manager_js_1.loggerManager.printLine(`\n${chalk_1.default.yellow(figures_1.default.warning)} Received ${signal}, shutting down gracefully...`, 'info');
226
114
  cleanupStart().then(() => {
227
115
  process.exit(0);
228
116
  }).catch(() => {
@@ -236,20 +124,7 @@ function addStartCommands(program) {
236
124
  // Register signal handlers
237
125
  process.on('SIGINT', () => handleExit('SIGINT'));
238
126
  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
- });
252
- });
127
+ process.on('SIGUSR2', () => handleExit('SIGUSR2'));
253
128
  return { cleanupStart };
254
129
  }
255
130
  exports.addStartCommands = addStartCommands;
@@ -4,407 +4,310 @@ 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 - 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");
11
11
  const chalk_1 = __importDefault(require("chalk"));
12
12
  const figures_1 = __importDefault(require("figures"));
13
13
  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");
14
+ const fs_1 = __importDefault(require("fs"));
15
+ const http_1 = __importDefault(require("http"));
16
+ const lodash_1 = require("lodash");
17
17
  class StartManager {
18
18
  constructor(options) {
19
- this.serverProcess = null;
19
+ this.workers = new Map();
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.totalRestarts = 0;
26
24
  this.options = options;
25
+ this.startTime = new Date();
26
+ this.debouncedRestart = (0, lodash_1.debounce)(this.restartAll.bind(this), options.restartDelay);
27
27
  }
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);
39
- }
40
- }
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);
45
- }
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);
28
+ log(message, level = 'info') {
29
+ logger_manager_js_1.loggerManager.printLine(message, level);
30
+ }
31
+ loadEnvFile() {
32
+ if (this.options.envFile && fs_1.default.existsSync(this.options.envFile)) {
33
+ try {
34
+ const envContent = fs_1.default.readFileSync(this.options.envFile, 'utf8');
35
+ const lines = envContent.split('\n');
36
+ for (const line of lines) {
37
+ const trimmed = line.trim();
38
+ if (trimmed && !trimmed.startsWith('#')) {
39
+ const [key, ...values] = trimmed.split('=');
40
+ if (key && values.length > 0) {
41
+ const value = values.join('=').trim();
42
+ const cleanValue = value.replace(/^["']|["']$/g, '');
43
+ process.env[key.trim()] = cleanValue;
44
+ }
45
+ }
46
+ }
51
47
  }
52
- }
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');
48
+ catch (error) {
49
+ if (this.options.verbose) {
50
+ this.log(`Failed to load environment file: ${error.message}`, 'warn');
66
51
  }
67
- return path_1.default.resolve(entry);
68
52
  }
69
53
  }
70
- throw new Error(`Entry file not found: ${entryPath}`);
71
54
  }
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
- }
79
- }
80
- catch (error) {
81
- // Ignore package.json errors
82
- }
83
- return null;
55
+ parseMemoryLimit(limit) {
56
+ var _a;
57
+ if (!limit)
58
+ return undefined;
59
+ const match = limit.match(/^(\d+)([KMGT]?)$/i);
60
+ if (!match)
61
+ return undefined;
62
+ const value = parseInt(match[1]);
63
+ const unit = ((_a = match[2]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || '';
64
+ const multipliers = { K: 1024, M: 1024 * 1024, G: 1024 * 1024 * 1024, T: 1024 * 1024 * 1024 * 1024 };
65
+ return value * (multipliers[unit] || 1);
84
66
  }
85
67
  getNodeArgs() {
86
68
  const args = [];
87
- // Add custom node arguments
88
- if (this.options.nodeArgs.length > 0) {
89
- args.push(...this.options.nodeArgs);
69
+ if (this.options.memoryLimit) {
70
+ const memoryMB = this.parseMemoryLimit(this.options.memoryLimit) / (1024 * 1024);
71
+ args.push(`--max-old-space-size=${memoryMB}`);
90
72
  }
91
- // Memory limit
92
- if (this.options.memory > 0) {
93
- args.push(`--max-old-space-size=${this.options.memory}`);
94
- }
95
- // Debugging
96
73
  if (this.options.inspect) {
97
- args.push(`--inspect=${this.options.inspectPort}`);
74
+ args.push('--inspect');
75
+ }
76
+ if (this.options.inspectBrk) {
77
+ args.push('--inspect-brk');
98
78
  }
99
- // Production optimizations
100
- if (this.options.env === 'production') {
101
- args.push('--optimize-for-size');
79
+ if (this.options.nodeArgs) {
80
+ args.push(...this.options.nodeArgs.split(' '));
102
81
  }
103
82
  return args;
104
83
  }
105
- getEnvironment() {
84
+ async startWorker(workerId) {
85
+ var _a, _b;
86
+ const nodeArgs = this.getNodeArgs();
106
87
  const env = {
107
88
  ...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
89
+ NODE_ENV: process.env.NODE_ENV || 'production',
90
+ WORKER_ID: workerId.toString(),
91
+ CLUSTER_WORKER: 'true',
92
+ FORCE_COLOR: this.options.color ? '1' : '0'
112
93
  };
113
- // Add color support
114
- if (this.options.color) {
115
- env.FORCE_COLOR = '1';
94
+ if (this.options.port) {
95
+ env.PORT = this.options.port.toString();
116
96
  }
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');
125
- }
97
+ const workerProcess = (0, child_process_1.fork)(this.options.file, [], {
98
+ cwd: this.options.workingDir,
99
+ env,
100
+ execArgv: nodeArgs,
101
+ silent: false
102
+ });
103
+ const workerInfo = {
104
+ process: workerProcess,
105
+ pid: workerProcess.pid,
106
+ restarts: 0,
107
+ startTime: new Date()
108
+ };
109
+ this.workers.set(workerId, workerInfo);
110
+ // Handle worker output
111
+ (_a = workerProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
112
+ const message = data.toString().trim();
113
+ if (message) {
114
+ console.log(this.options.verbose ?
115
+ chalk_1.default.dim(`[Worker ${workerId}]`) + ' ' + message :
116
+ message);
126
117
  }
127
- catch (error) {
128
- logger_manager_js_1.loggerManager.printLine(`Failed to write PID file: ${error.message}`, 'warn');
118
+ });
119
+ (_b = workerProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
120
+ 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));
129
125
  }
130
- }
131
- }
132
- async removePidFile() {
133
- if (this.options.pid && (0, fs_1.existsSync)(this.options.pid)) {
134
- 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');
126
+ });
127
+ workerProcess.on('error', (error) => {
128
+ this.log(`Worker ${workerId} error: ${error.message}`, 'error');
129
+ });
130
+ workerProcess.on('exit', (code, signal) => {
131
+ this.workers.delete(workerId);
132
+ if (!this.isShuttingDown) {
133
+ if (code !== 0) {
134
+ this.log(`Worker ${workerId} crashed (code: ${code})`, 'error');
135
+ this.restartWorker(workerId);
138
136
  }
139
137
  }
140
- catch (error) {
141
- // Ignore cleanup errors
142
- }
138
+ });
139
+ if (this.options.verbose) {
140
+ this.log(`Worker ${workerId} started (PID: ${workerProcess.pid})`);
143
141
  }
142
+ return workerInfo;
144
143
  }
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()
173
- });
174
- if (this.serverProcess.pid) {
175
- await this.writePidFile(this.serverProcess.pid);
144
+ async restartWorker(workerId) {
145
+ const workerInfo = this.workers.get(workerId);
146
+ if (workerInfo) {
147
+ workerInfo.restarts++;
148
+ this.totalRestarts++;
149
+ if (workerInfo.restarts >= this.options.maxCrashes) {
150
+ this.log(`Worker ${workerId} reached max crashes, not restarting`, 'error');
151
+ return;
176
152
  }
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');
183
- }
184
- });
153
+ if (this.options.verbose) {
154
+ this.log(`Restarting worker ${workerId} (attempt ${workerInfo.restarts})`);
185
155
  }
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');
192
- }
193
- });
156
+ try {
157
+ workerInfo.process.kill('SIGTERM');
158
+ await this.waitForProcessExit(workerInfo.process, 5000);
194
159
  }
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
- }
202
- });
203
- this.serverProcess.on('exit', (code, signal) => {
204
- this.isStarting = false;
205
- if (!this.isShuttingDown) {
206
- this.handleServerExit(code || 0, signal || undefined);
207
- }
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');
160
+ catch (error) {
161
+ workerInfo.process.kill('SIGKILL');
216
162
  }
217
- }
218
- catch (error) {
219
- this.isStarting = false;
220
- throw error;
163
+ setTimeout(() => {
164
+ this.startWorker(workerId);
165
+ }, this.options.restartDelay);
221
166
  }
222
167
  }
223
- async waitForServerStart() {
168
+ async waitForProcessExit(process, timeout) {
224
169
  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
- }
170
+ const timer = setTimeout(() => {
171
+ reject(new Error('Process exit timeout'));
172
+ }, timeout);
173
+ process.on('exit', () => {
174
+ clearTimeout(timer);
175
+ resolve();
176
+ });
177
+ });
178
+ }
179
+ async startCluster() {
180
+ for (let i = 0; i < this.options.workers; i++) {
181
+ await this.startWorker(i + 1);
182
+ }
183
+ }
184
+ setupHealthCheck() {
185
+ if (!this.options.healthCheck)
186
+ return;
187
+ this.healthServer = http_1.default.createServer((req, res) => {
188
+ if (req.url === '/health') {
189
+ const stats = {
190
+ status: 'ok',
191
+ uptime: Date.now() - this.startTime.getTime(),
192
+ workers: this.workers.size,
193
+ totalRestarts: this.totalRestarts,
194
+ memory: process.memoryUsage()
238
195
  };
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);
196
+ res.writeHead(200, { 'Content-Type': 'application/json' });
197
+ res.end(JSON.stringify(stats));
248
198
  }
249
199
  else {
250
- clearTimeout(timeout);
251
- reject(new Error('Server process not created'));
200
+ res.writeHead(404);
201
+ res.end('Not Found');
202
+ }
203
+ });
204
+ this.healthServer.listen(this.options.healthPort, () => {
205
+ if (this.options.verbose) {
206
+ this.log(`Health endpoint: http://localhost:${this.options.healthPort}/health`);
252
207
  }
253
208
  });
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);
291
209
  }
292
210
  setupWatcher() {
293
211
  if (!this.options.watch)
294
212
  return;
295
213
  const watchPatterns = [
296
- 'dist/**/*.js',
297
- 'package.json',
298
- 'dist/package.json'
214
+ `${this.options.workingDir}/**/*.js`,
215
+ `${this.options.workingDir}/**/*.json`,
216
+ `${this.options.workingDir}/**/*.env*`
299
217
  ];
300
218
  this.watcher = (0, chokidar_1.watch)(watchPatterns, {
219
+ ignored: ['**/node_modules/**', '**/.git/**', '**/logs/**'],
301
220
  ignoreInitial: true,
302
- followSymlinks: false,
303
- usePolling: false,
304
- atomic: 500,
305
- ignored: [
306
- '**/node_modules/**',
307
- '**/.git/**',
308
- '**/*.log',
309
- '**/*.map'
310
- ]
221
+ atomic: 300
311
222
  });
312
223
  this.watcher.on('change', (filePath) => {
313
224
  if (this.options.verbose) {
314
- logger_manager_js_1.loggerManager.printLine(`File changed: ${path_1.default.relative(process.cwd(), filePath)}`, 'info');
225
+ this.log(`File changed: ${path_1.default.relative(this.options.workingDir, filePath)}`);
315
226
  }
316
- this.restart();
317
- });
318
- this.watcher.on('error', (error) => {
319
- logger_manager_js_1.loggerManager.printLine(`Watcher error: ${error.message}`, 'error');
227
+ this.debouncedRestart();
320
228
  });
321
229
  if (this.options.verbose) {
322
- logger_manager_js_1.loggerManager.printLine(`Watching: ${watchPatterns.join(', ')}`, 'info');
230
+ this.log('File watching enabled');
323
231
  }
324
232
  }
325
- async restart() {
233
+ async restartAll() {
326
234
  if (this.isShuttingDown)
327
235
  return;
328
- if (!this.options.quiet) {
329
- logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.yellow(figures_1.default.warning)} Restarting server...`, 'info');
330
- }
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;
347
- }
348
- const proc = this.serverProcess;
349
- this.serverProcess = null;
350
- if (this.restartTimer) {
351
- clearTimeout(this.restartTimer);
352
- this.restartTimer = null;
353
- }
354
- const cleanup = () => {
355
- this.removePidFile();
356
- resolve();
357
- };
358
- proc.on('exit', cleanup);
359
- proc.on('error', cleanup);
236
+ this.log('Restarting due to file changes...');
237
+ for (const [workerId, workerInfo] of this.workers.entries()) {
360
238
  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
372
- }
373
- }
374
- }, this.options.gracefulTimeout);
375
- }
239
+ workerInfo.process.kill('SIGTERM');
240
+ await this.waitForProcessExit(workerInfo.process, 5000);
376
241
  }
377
242
  catch (error) {
378
- cleanup();
243
+ workerInfo.process.kill('SIGKILL');
379
244
  }
380
- });
245
+ setTimeout(() => {
246
+ this.startWorker(workerId);
247
+ }, this.options.restartDelay);
248
+ }
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);
263
+ }
264
+ }
265
+ catch (error) {
266
+ // Ignore monitoring errors
267
+ }
268
+ });
269
+ }, 30000); // Check every 30 seconds
381
270
  }
382
271
  async start() {
383
272
  try {
384
- await this.startServer();
273
+ this.loadEnvFile();
274
+ this.setupHealthCheck();
385
275
  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');
388
- }
276
+ this.setupMemoryMonitoring();
277
+ await this.startCluster();
278
+ // 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');
389
282
  }
390
283
  catch (error) {
391
- logger_manager_js_1.loggerManager.printLine(`Failed to start server: ${error.message}`, 'error');
392
- process.exit(1);
284
+ throw error;
393
285
  }
394
286
  }
395
287
  async stop() {
288
+ if (this.isShuttingDown)
289
+ return;
396
290
  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
- }
400
291
  if (this.watcher) {
401
292
  await this.watcher.close();
402
- this.watcher = null;
403
293
  }
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');
294
+ if (this.healthServer) {
295
+ this.healthServer.close();
296
+ }
297
+ const shutdownPromises = [];
298
+ for (const [workerId, workerInfo] of this.workers.entries()) {
299
+ shutdownPromises.push((async () => {
300
+ try {
301
+ workerInfo.process.kill('SIGTERM');
302
+ await this.waitForProcessExit(workerInfo.process, this.options.gracefulTimeout);
303
+ }
304
+ catch (error) {
305
+ workerInfo.process.kill('SIGKILL');
306
+ }
307
+ })());
407
308
  }
309
+ await Promise.allSettled(shutdownPromises);
310
+ this.workers.clear();
408
311
  }
409
312
  }
410
313
  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.52",
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",