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.
- package/dist/src/commands/start-commands.js +67 -192
- package/dist/src/start-manager.js +220 -317
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
16
|
+
// Single optimized start command for production
|
|
15
17
|
program
|
|
16
|
-
.command('start [
|
|
17
|
-
.description('Start
|
|
18
|
-
.option('-
|
|
19
|
-
.option('-
|
|
20
|
-
.option('-
|
|
21
|
-
.option('-w, --
|
|
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('
|
|
29
|
-
.option('--no-
|
|
30
|
-
.option('--
|
|
31
|
-
.option('--
|
|
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-
|
|
34
|
-
.
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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)}
|
|
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)}
|
|
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('
|
|
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 -
|
|
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
|
|
15
|
-
const
|
|
16
|
-
const
|
|
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.
|
|
19
|
+
this.workers = new Map();
|
|
20
20
|
this.watcher = null;
|
|
21
|
-
this.
|
|
22
|
-
this.restartCount = 0;
|
|
23
|
-
this.startTime = 0;
|
|
21
|
+
this.healthServer = null;
|
|
24
22
|
this.isShuttingDown = false;
|
|
25
|
-
this.
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
args.push(
|
|
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(
|
|
74
|
+
args.push('--inspect');
|
|
75
|
+
}
|
|
76
|
+
if (this.options.inspectBrk) {
|
|
77
|
+
args.push('--inspect-brk');
|
|
98
78
|
}
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
84
|
+
async startWorker(workerId) {
|
|
85
|
+
var _a, _b;
|
|
86
|
+
const nodeArgs = this.getNodeArgs();
|
|
106
87
|
const env = {
|
|
107
88
|
...process.env,
|
|
108
|
-
NODE_ENV:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
env.FORCE_COLOR = '1';
|
|
94
|
+
if (this.options.port) {
|
|
95
|
+
env.PORT = this.options.port.toString();
|
|
116
96
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
this.
|
|
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
|
-
|
|
196
|
-
|
|
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
|
-
|
|
219
|
-
this.
|
|
220
|
-
throw error;
|
|
163
|
+
setTimeout(() => {
|
|
164
|
+
this.startWorker(workerId);
|
|
165
|
+
}, this.options.restartDelay);
|
|
221
166
|
}
|
|
222
167
|
}
|
|
223
|
-
async
|
|
168
|
+
async waitForProcessExit(process, timeout) {
|
|
224
169
|
return new Promise((resolve, reject) => {
|
|
225
|
-
const
|
|
226
|
-
reject(new Error(
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
240
|
-
|
|
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
|
-
|
|
251
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
+
this.log(`File changed: ${path_1.default.relative(this.options.workingDir, filePath)}`);
|
|
315
226
|
}
|
|
316
|
-
this.
|
|
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
|
-
|
|
230
|
+
this.log('File watching enabled');
|
|
323
231
|
}
|
|
324
232
|
}
|
|
325
|
-
async
|
|
233
|
+
async restartAll() {
|
|
326
234
|
if (this.isShuttingDown)
|
|
327
235
|
return;
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
362
|
-
|
|
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
|
-
|
|
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
|
-
|
|
273
|
+
this.loadEnvFile();
|
|
274
|
+
this.setupHealthCheck();
|
|
385
275
|
this.setupWatcher();
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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;
|