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