neex 0.6.51 → 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 +33 -147
- package/dist/src/start-manager.js +67 -210
- package/package.json +1 -1
|
@@ -10,40 +10,31 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
10
10
|
const figures_1 = __importDefault(require("figures"));
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const os_1 = __importDefault(require("os"));
|
|
13
14
|
function addStartCommands(program) {
|
|
14
15
|
let startManager = null;
|
|
15
|
-
//
|
|
16
|
+
// Single optimized start command for production
|
|
16
17
|
program
|
|
17
18
|
.command('start [file]')
|
|
18
|
-
.description('Start production application
|
|
19
|
+
.description('Start production application with automatic optimization')
|
|
19
20
|
.option('-d, --dir <directory>', 'Working directory', process.cwd())
|
|
20
21
|
.option('-e, --env <file>', 'Environment file to load', '.env')
|
|
21
|
-
.option('-p, --port <port>', 'Port number
|
|
22
|
-
.option('-w, --workers <count>', 'Number of worker processes', parseInt
|
|
23
|
-
.option('-c, --cluster', 'Enable cluster mode')
|
|
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')
|
|
27
|
-
.option('--no-color', 'Disable colored output')
|
|
28
|
-
.option('-q, --quiet', 'Quiet mode')
|
|
22
|
+
.option('-p, --port <port>', 'Port number', parseInt)
|
|
23
|
+
.option('-w, --workers <count>', 'Number of worker processes (default: auto)', parseInt)
|
|
29
24
|
.option('-v, --verbose', 'Verbose output')
|
|
30
|
-
.option('--watch', 'Watch for changes and restart')
|
|
31
|
-
.option('--
|
|
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')
|
|
25
|
+
.option('--watch', 'Watch for changes and restart (development mode)')
|
|
26
|
+
.option('--no-health', 'Disable health check endpoint')
|
|
35
27
|
.option('--health-port <port>', 'Health check port', parseInt, 3001)
|
|
28
|
+
.option('--max-memory <limit>', 'Maximum memory before restart (e.g., 1G)')
|
|
36
29
|
.option('--graceful-timeout <ms>', 'Graceful shutdown timeout (ms)', parseInt, 30000)
|
|
37
30
|
.option('--inspect', 'Enable Node.js inspector')
|
|
38
31
|
.option('--inspect-brk', 'Enable Node.js inspector with break')
|
|
39
|
-
.option('--node-args <args>', 'Additional Node.js arguments')
|
|
40
32
|
.action(async (file, options) => {
|
|
41
33
|
try {
|
|
42
34
|
const targetFile = file || 'dist/index.js';
|
|
43
35
|
const resolvedFile = path_1.default.resolve(options.dir, targetFile);
|
|
44
|
-
//
|
|
36
|
+
// Auto-detect main file if not found
|
|
45
37
|
if (!fs_1.default.existsSync(resolvedFile)) {
|
|
46
|
-
// Try to find main file from package.json
|
|
47
38
|
const packageJsonPath = path_1.default.join(options.dir, 'package.json');
|
|
48
39
|
if (fs_1.default.existsSync(packageJsonPath)) {
|
|
49
40
|
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
@@ -51,155 +42,56 @@ function addStartCommands(program) {
|
|
|
51
42
|
const alternativeFile = path_1.default.resolve(options.dir, mainFile);
|
|
52
43
|
if (fs_1.default.existsSync(alternativeFile)) {
|
|
53
44
|
if (options.verbose) {
|
|
54
|
-
logger_manager_js_1.loggerManager.printLine(`Using main file
|
|
45
|
+
logger_manager_js_1.loggerManager.printLine(`Using main file: ${mainFile}`, 'info');
|
|
55
46
|
}
|
|
56
47
|
}
|
|
57
48
|
else {
|
|
58
|
-
throw new Error(`
|
|
49
|
+
throw new Error(`Application file not found. Please build your project first.`);
|
|
59
50
|
}
|
|
60
51
|
}
|
|
61
52
|
else {
|
|
62
|
-
throw new Error(`
|
|
53
|
+
throw new Error(`Application file not found: ${resolvedFile}`);
|
|
63
54
|
}
|
|
64
55
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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');
|
|
68
66
|
startManager = new start_manager_js_1.StartManager({
|
|
69
67
|
file: resolvedFile,
|
|
70
68
|
workingDir: options.dir,
|
|
71
69
|
envFile: options.env,
|
|
72
70
|
port: options.port,
|
|
73
|
-
workers
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
logFile: options.logFile,
|
|
78
|
-
color: options.color,
|
|
79
|
-
quiet: options.quiet,
|
|
71
|
+
workers,
|
|
72
|
+
memoryLimit: isProduction ? '512M' : undefined,
|
|
73
|
+
logLevel: options.verbose ? 'debug' : 'info',
|
|
74
|
+
color: true,
|
|
80
75
|
verbose: options.verbose,
|
|
81
76
|
watch: options.watch,
|
|
82
|
-
maxMemory: options.maxMemory,
|
|
83
|
-
maxCrashes:
|
|
84
|
-
restartDelay:
|
|
85
|
-
healthCheck
|
|
77
|
+
maxMemory: options.maxMemory || (isProduction ? '1G' : undefined),
|
|
78
|
+
maxCrashes: isProduction ? 3 : 5,
|
|
79
|
+
restartDelay: isProduction ? 2000 : 1000,
|
|
80
|
+
healthCheck,
|
|
86
81
|
healthPort: options.healthPort,
|
|
87
82
|
gracefulTimeout: options.gracefulTimeout,
|
|
88
83
|
inspect: options.inspect,
|
|
89
84
|
inspectBrk: options.inspectBrk,
|
|
90
|
-
nodeArgs: options.nodeArgs
|
|
91
|
-
});
|
|
92
|
-
await startManager.start();
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
if (error instanceof Error) {
|
|
96
|
-
logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Start failed: ${error.message}`, 'error');
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} An unknown start error occurred`, 'error');
|
|
100
|
-
}
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
// Quick start command
|
|
105
|
-
program
|
|
106
|
-
.command('run [file]')
|
|
107
|
-
.description('Quick start without options (alias for start)')
|
|
108
|
-
.action(async (file) => {
|
|
109
|
-
try {
|
|
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');
|
|
115
|
-
startManager = new start_manager_js_1.StartManager({
|
|
116
|
-
file: path_1.default.resolve(targetFile),
|
|
117
|
-
workingDir: process.cwd(),
|
|
118
|
-
envFile: '.env',
|
|
119
|
-
port: undefined,
|
|
120
|
-
workers: 1,
|
|
121
|
-
cluster: false,
|
|
122
|
-
memoryLimit: undefined,
|
|
123
|
-
logLevel: 'info',
|
|
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,
|
|
135
|
-
inspect: false,
|
|
136
|
-
inspectBrk: false,
|
|
137
85
|
nodeArgs: undefined
|
|
138
86
|
});
|
|
139
87
|
await startManager.start();
|
|
140
88
|
}
|
|
141
89
|
catch (error) {
|
|
142
90
|
if (error instanceof Error) {
|
|
143
|
-
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');
|
|
144
92
|
}
|
|
145
93
|
else {
|
|
146
|
-
logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)}
|
|
147
|
-
}
|
|
148
|
-
process.exit(1);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
// Production command with clustering
|
|
152
|
-
program
|
|
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) => {
|
|
161
|
-
try {
|
|
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
|
-
}
|
|
171
|
-
startManager = new start_manager_js_1.StartManager({
|
|
172
|
-
file: resolvedFile,
|
|
173
|
-
workingDir: process.cwd(),
|
|
174
|
-
envFile: options.env,
|
|
175
|
-
port: options.port,
|
|
176
|
-
workers,
|
|
177
|
-
cluster: true,
|
|
178
|
-
memoryLimit: undefined,
|
|
179
|
-
logLevel: 'info',
|
|
180
|
-
logFile: options.logFile,
|
|
181
|
-
color: false,
|
|
182
|
-
quiet: options.quiet,
|
|
183
|
-
verbose: false,
|
|
184
|
-
watch: false,
|
|
185
|
-
maxMemory: '1G',
|
|
186
|
-
maxCrashes: 3,
|
|
187
|
-
restartDelay: 2000,
|
|
188
|
-
healthCheck: true,
|
|
189
|
-
healthPort: 3001,
|
|
190
|
-
gracefulTimeout: 30000,
|
|
191
|
-
inspect: false,
|
|
192
|
-
inspectBrk: false,
|
|
193
|
-
nodeArgs: undefined
|
|
194
|
-
});
|
|
195
|
-
await startManager.start();
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
if (error instanceof Error) {
|
|
199
|
-
logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Production start failed: ${error.message}`, 'error');
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} An unknown production start error occurred`, 'error');
|
|
94
|
+
logger_manager_js_1.loggerManager.printLine(`${chalk_1.default.red(figures_1.default.cross)} Startup failed`, 'error');
|
|
203
95
|
}
|
|
204
96
|
process.exit(1);
|
|
205
97
|
}
|
|
@@ -219,7 +111,6 @@ function addStartCommands(program) {
|
|
|
219
111
|
// Handle process termination
|
|
220
112
|
const handleExit = (signal) => {
|
|
221
113
|
if (startManager) {
|
|
222
|
-
logger_manager_js_1.loggerManager.printLine(`\n${chalk_1.default.yellow(figures_1.default.warning)} Received ${signal}, stopping server...`, 'info');
|
|
223
114
|
cleanupStart().then(() => {
|
|
224
115
|
process.exit(0);
|
|
225
116
|
}).catch(() => {
|
|
@@ -233,12 +124,7 @@ function addStartCommands(program) {
|
|
|
233
124
|
// Register signal handlers
|
|
234
125
|
process.on('SIGINT', () => handleExit('SIGINT'));
|
|
235
126
|
process.on('SIGTERM', () => handleExit('SIGTERM'));
|
|
236
|
-
process.on('SIGUSR2', () => handleExit('SIGUSR2'));
|
|
237
|
-
process.on('exit', () => {
|
|
238
|
-
if (startManager) {
|
|
239
|
-
cleanupStart();
|
|
240
|
-
}
|
|
241
|
-
});
|
|
127
|
+
process.on('SIGUSR2', () => handleExit('SIGUSR2'));
|
|
242
128
|
return { cleanupStart };
|
|
243
129
|
}
|
|
244
130
|
exports.addStartCommands = addStartCommands;
|
|
@@ -4,11 +4,12 @@ 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
|
+
const figures_1 = __importDefault(require("figures"));
|
|
12
13
|
const path_1 = __importDefault(require("path"));
|
|
13
14
|
const fs_1 = __importDefault(require("fs"));
|
|
14
15
|
const http_1 = __importDefault(require("http"));
|
|
@@ -16,43 +17,16 @@ const lodash_1 = require("lodash");
|
|
|
16
17
|
class StartManager {
|
|
17
18
|
constructor(options) {
|
|
18
19
|
this.workers = new Map();
|
|
19
|
-
this.masterProcess = null;
|
|
20
20
|
this.watcher = null;
|
|
21
21
|
this.healthServer = null;
|
|
22
22
|
this.isShuttingDown = false;
|
|
23
|
-
this.logStream = null;
|
|
24
23
|
this.totalRestarts = 0;
|
|
25
24
|
this.options = options;
|
|
26
25
|
this.startTime = new Date();
|
|
27
26
|
this.debouncedRestart = (0, lodash_1.debounce)(this.restartAll.bind(this), options.restartDelay);
|
|
28
|
-
this.setupLogging();
|
|
29
|
-
}
|
|
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');
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
27
|
}
|
|
47
28
|
log(message, level = 'info') {
|
|
48
|
-
|
|
49
|
-
const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
|
|
50
|
-
if (this.logStream) {
|
|
51
|
-
this.logStream.write(logMessage + '\n');
|
|
52
|
-
}
|
|
53
|
-
if (!this.options.quiet) {
|
|
54
|
-
logger_manager_js_1.loggerManager.printLine(message, level);
|
|
55
|
-
}
|
|
29
|
+
logger_manager_js_1.loggerManager.printLine(message, level);
|
|
56
30
|
}
|
|
57
31
|
loadEnvFile() {
|
|
58
32
|
if (this.options.envFile && fs_1.default.existsSync(this.options.envFile)) {
|
|
@@ -65,18 +39,16 @@ class StartManager {
|
|
|
65
39
|
const [key, ...values] = trimmed.split('=');
|
|
66
40
|
if (key && values.length > 0) {
|
|
67
41
|
const value = values.join('=').trim();
|
|
68
|
-
// Remove quotes if present
|
|
69
42
|
const cleanValue = value.replace(/^["']|["']$/g, '');
|
|
70
43
|
process.env[key.trim()] = cleanValue;
|
|
71
44
|
}
|
|
72
45
|
}
|
|
73
46
|
}
|
|
74
|
-
if (this.options.verbose) {
|
|
75
|
-
this.log(`Loaded environment variables from ${this.options.envFile}`);
|
|
76
|
-
}
|
|
77
47
|
}
|
|
78
48
|
catch (error) {
|
|
79
|
-
this.
|
|
49
|
+
if (this.options.verbose) {
|
|
50
|
+
this.log(`Failed to load environment file: ${error.message}`, 'warn');
|
|
51
|
+
}
|
|
80
52
|
}
|
|
81
53
|
}
|
|
82
54
|
}
|
|
@@ -95,7 +67,8 @@ class StartManager {
|
|
|
95
67
|
getNodeArgs() {
|
|
96
68
|
const args = [];
|
|
97
69
|
if (this.options.memoryLimit) {
|
|
98
|
-
|
|
70
|
+
const memoryMB = this.parseMemoryLimit(this.options.memoryLimit) / (1024 * 1024);
|
|
71
|
+
args.push(`--max-old-space-size=${memoryMB}`);
|
|
99
72
|
}
|
|
100
73
|
if (this.options.inspect) {
|
|
101
74
|
args.push('--inspect');
|
|
@@ -125,32 +98,30 @@ class StartManager {
|
|
|
125
98
|
cwd: this.options.workingDir,
|
|
126
99
|
env,
|
|
127
100
|
execArgv: nodeArgs,
|
|
128
|
-
silent:
|
|
101
|
+
silent: false
|
|
129
102
|
});
|
|
130
103
|
const workerInfo = {
|
|
131
104
|
process: workerProcess,
|
|
132
105
|
pid: workerProcess.pid,
|
|
133
106
|
restarts: 0,
|
|
134
|
-
startTime: new Date()
|
|
135
|
-
memoryUsage: 0,
|
|
136
|
-
cpuUsage: 0
|
|
107
|
+
startTime: new Date()
|
|
137
108
|
};
|
|
138
109
|
this.workers.set(workerId, workerInfo);
|
|
139
110
|
// Handle worker output
|
|
140
111
|
(_a = workerProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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);
|
|
146
117
|
}
|
|
147
118
|
});
|
|
148
119
|
(_b = workerProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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));
|
|
154
125
|
}
|
|
155
126
|
});
|
|
156
127
|
workerProcess.on('error', (error) => {
|
|
@@ -160,20 +131,14 @@ class StartManager {
|
|
|
160
131
|
this.workers.delete(workerId);
|
|
161
132
|
if (!this.isShuttingDown) {
|
|
162
133
|
if (code !== 0) {
|
|
163
|
-
this.log(`Worker ${workerId}
|
|
134
|
+
this.log(`Worker ${workerId} crashed (code: ${code})`, 'error');
|
|
164
135
|
this.restartWorker(workerId);
|
|
165
136
|
}
|
|
166
|
-
else {
|
|
167
|
-
this.log(`Worker ${workerId} exited gracefully`);
|
|
168
|
-
}
|
|
169
137
|
}
|
|
170
138
|
});
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
this.log(`Started worker ${workerId} (PID: ${workerProcess.pid})`);
|
|
139
|
+
if (this.options.verbose) {
|
|
140
|
+
this.log(`Worker ${workerId} started (PID: ${workerProcess.pid})`);
|
|
141
|
+
}
|
|
177
142
|
return workerInfo;
|
|
178
143
|
}
|
|
179
144
|
async restartWorker(workerId) {
|
|
@@ -182,11 +147,12 @@ class StartManager {
|
|
|
182
147
|
workerInfo.restarts++;
|
|
183
148
|
this.totalRestarts++;
|
|
184
149
|
if (workerInfo.restarts >= this.options.maxCrashes) {
|
|
185
|
-
this.log(`Worker ${workerId} reached max crashes
|
|
150
|
+
this.log(`Worker ${workerId} reached max crashes, not restarting`, 'error');
|
|
186
151
|
return;
|
|
187
152
|
}
|
|
188
|
-
this.
|
|
189
|
-
|
|
153
|
+
if (this.options.verbose) {
|
|
154
|
+
this.log(`Restarting worker ${workerId} (attempt ${workerInfo.restarts})`);
|
|
155
|
+
}
|
|
190
156
|
try {
|
|
191
157
|
workerInfo.process.kill('SIGTERM');
|
|
192
158
|
await this.waitForProcessExit(workerInfo.process, 5000);
|
|
@@ -194,7 +160,6 @@ class StartManager {
|
|
|
194
160
|
catch (error) {
|
|
195
161
|
workerInfo.process.kill('SIGKILL');
|
|
196
162
|
}
|
|
197
|
-
// Start new worker
|
|
198
163
|
setTimeout(() => {
|
|
199
164
|
this.startWorker(workerId);
|
|
200
165
|
}, this.options.restartDelay);
|
|
@@ -211,49 +176,7 @@ class StartManager {
|
|
|
211
176
|
});
|
|
212
177
|
});
|
|
213
178
|
}
|
|
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);
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
this.log(`Reached max crashes (${this.options.maxCrashes}), giving up`, 'error');
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
this.log('Process exited gracefully');
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
this.log(`Started process (PID: ${this.masterProcess.pid})`);
|
|
254
|
-
}
|
|
255
179
|
async startCluster() {
|
|
256
|
-
this.log(`Starting cluster with ${this.options.workers} workers`);
|
|
257
180
|
for (let i = 0; i < this.options.workers; i++) {
|
|
258
181
|
await this.startWorker(i + 1);
|
|
259
182
|
}
|
|
@@ -268,11 +191,10 @@ class StartManager {
|
|
|
268
191
|
uptime: Date.now() - this.startTime.getTime(),
|
|
269
192
|
workers: this.workers.size,
|
|
270
193
|
totalRestarts: this.totalRestarts,
|
|
271
|
-
|
|
272
|
-
cpuUsage: process.cpuUsage()
|
|
194
|
+
memory: process.memoryUsage()
|
|
273
195
|
};
|
|
274
196
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
275
|
-
res.end(JSON.stringify(stats
|
|
197
|
+
res.end(JSON.stringify(stats));
|
|
276
198
|
}
|
|
277
199
|
else {
|
|
278
200
|
res.writeHead(404);
|
|
@@ -280,7 +202,9 @@ class StartManager {
|
|
|
280
202
|
}
|
|
281
203
|
});
|
|
282
204
|
this.healthServer.listen(this.options.healthPort, () => {
|
|
283
|
-
|
|
205
|
+
if (this.options.verbose) {
|
|
206
|
+
this.log(`Health endpoint: http://localhost:${this.options.healthPort}/health`);
|
|
207
|
+
}
|
|
284
208
|
});
|
|
285
209
|
}
|
|
286
210
|
setupWatcher() {
|
|
@@ -292,15 +216,8 @@ class StartManager {
|
|
|
292
216
|
`${this.options.workingDir}/**/*.env*`
|
|
293
217
|
];
|
|
294
218
|
this.watcher = (0, chokidar_1.watch)(watchPatterns, {
|
|
295
|
-
ignored: [
|
|
296
|
-
'**/node_modules/**',
|
|
297
|
-
'**/.git/**',
|
|
298
|
-
'**/logs/**',
|
|
299
|
-
'**/*.log'
|
|
300
|
-
],
|
|
219
|
+
ignored: ['**/node_modules/**', '**/.git/**', '**/logs/**'],
|
|
301
220
|
ignoreInitial: true,
|
|
302
|
-
followSymlinks: false,
|
|
303
|
-
usePolling: false,
|
|
304
221
|
atomic: 300
|
|
305
222
|
});
|
|
306
223
|
this.watcher.on('change', (filePath) => {
|
|
@@ -309,94 +226,61 @@ class StartManager {
|
|
|
309
226
|
}
|
|
310
227
|
this.debouncedRestart();
|
|
311
228
|
});
|
|
312
|
-
this.
|
|
313
|
-
this.log(
|
|
314
|
-
}
|
|
315
|
-
this.log('File watcher enabled');
|
|
229
|
+
if (this.options.verbose) {
|
|
230
|
+
this.log('File watching enabled');
|
|
231
|
+
}
|
|
316
232
|
}
|
|
317
233
|
async restartAll() {
|
|
318
234
|
if (this.isShuttingDown)
|
|
319
235
|
return;
|
|
320
|
-
this.log('Restarting
|
|
321
|
-
|
|
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
|
-
}
|
|
336
|
-
}
|
|
337
|
-
else if (this.masterProcess) {
|
|
338
|
-
// Restart single process
|
|
236
|
+
this.log('Restarting due to file changes...');
|
|
237
|
+
for (const [workerId, workerInfo] of this.workers.entries()) {
|
|
339
238
|
try {
|
|
340
|
-
|
|
341
|
-
await this.waitForProcessExit(
|
|
239
|
+
workerInfo.process.kill('SIGTERM');
|
|
240
|
+
await this.waitForProcessExit(workerInfo.process, 5000);
|
|
342
241
|
}
|
|
343
242
|
catch (error) {
|
|
344
|
-
|
|
243
|
+
workerInfo.process.kill('SIGKILL');
|
|
345
244
|
}
|
|
346
245
|
setTimeout(() => {
|
|
347
|
-
this.
|
|
246
|
+
this.startWorker(workerId);
|
|
348
247
|
}, this.options.restartDelay);
|
|
349
248
|
}
|
|
350
249
|
}
|
|
351
|
-
|
|
250
|
+
setupMemoryMonitoring() {
|
|
251
|
+
if (!this.options.maxMemory)
|
|
252
|
+
return;
|
|
253
|
+
const maxMemory = this.parseMemoryLimit(this.options.maxMemory);
|
|
254
|
+
if (!maxMemory)
|
|
255
|
+
return;
|
|
352
256
|
setInterval(() => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
catch (error) {
|
|
367
|
-
// Ignore monitoring errors
|
|
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);
|
|
368
263
|
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
// Ignore monitoring errors
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}, 30000); // Check every 30 seconds
|
|
372
270
|
}
|
|
373
271
|
async start() {
|
|
374
272
|
try {
|
|
375
273
|
this.loadEnvFile();
|
|
376
274
|
this.setupHealthCheck();
|
|
377
275
|
this.setupWatcher();
|
|
378
|
-
this.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
})}`);
|
|
396
|
-
}
|
|
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');
|
|
397
282
|
}
|
|
398
283
|
catch (error) {
|
|
399
|
-
this.log(`Failed to start application: ${error.message}`, 'error');
|
|
400
284
|
throw error;
|
|
401
285
|
}
|
|
402
286
|
}
|
|
@@ -404,18 +288,12 @@ class StartManager {
|
|
|
404
288
|
if (this.isShuttingDown)
|
|
405
289
|
return;
|
|
406
290
|
this.isShuttingDown = true;
|
|
407
|
-
this.log('Stopping application...');
|
|
408
|
-
// Stop watcher
|
|
409
291
|
if (this.watcher) {
|
|
410
292
|
await this.watcher.close();
|
|
411
|
-
this.watcher = null;
|
|
412
293
|
}
|
|
413
|
-
// Stop health server
|
|
414
294
|
if (this.healthServer) {
|
|
415
295
|
this.healthServer.close();
|
|
416
|
-
this.healthServer = null;
|
|
417
296
|
}
|
|
418
|
-
// Stop all workers
|
|
419
297
|
const shutdownPromises = [];
|
|
420
298
|
for (const [workerId, workerInfo] of this.workers.entries()) {
|
|
421
299
|
shutdownPromises.push((async () => {
|
|
@@ -428,29 +306,8 @@ class StartManager {
|
|
|
428
306
|
}
|
|
429
307
|
})());
|
|
430
308
|
}
|
|
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
309
|
await Promise.allSettled(shutdownPromises);
|
|
445
|
-
// Close log stream
|
|
446
|
-
if (this.logStream) {
|
|
447
|
-
this.logStream.end();
|
|
448
|
-
this.logStream = null;
|
|
449
|
-
}
|
|
450
|
-
const uptime = Date.now() - this.startTime.getTime();
|
|
451
|
-
this.log(`Application stopped after ${uptime}ms uptime`);
|
|
452
310
|
this.workers.clear();
|
|
453
|
-
this.masterProcess = null;
|
|
454
311
|
}
|
|
455
312
|
}
|
|
456
313
|
exports.StartManager = StartManager;
|