neex 0.6.20 → 0.6.21
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/README.md +1 -1
- package/dist/src/commands/build-commands.js +11 -1
- package/dist/src/commands/start-commands.js +45 -62
- package/dist/src/start-manager.js +121 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,7 +195,17 @@ function addBuildCommands(program) {
|
|
|
195
195
|
isServerMode: false
|
|
196
196
|
});
|
|
197
197
|
// Start the build process
|
|
198
|
-
|
|
198
|
+
try {
|
|
199
|
+
await buildManager.start();
|
|
200
|
+
console.log(chalk_1.default.green(`${figures_1.default.tick} Build completed successfully`));
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} Build failed`));
|
|
204
|
+
if (error instanceof Error) {
|
|
205
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
206
|
+
}
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
199
209
|
}
|
|
200
210
|
catch (error) {
|
|
201
211
|
console.error(chalk_1.default.red(`${figures_1.default.cross} neex build: Fatal error occurred`));
|
|
@@ -27,6 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
exports.addStartCommands = void 0;
|
|
30
|
+
const start_manager_js_1 = require("../start-manager.js");
|
|
30
31
|
const chalk_1 = __importDefault(require("chalk"));
|
|
31
32
|
const figures_1 = __importDefault(require("figures"));
|
|
32
33
|
const path = __importStar(require("path"));
|
|
@@ -99,74 +100,56 @@ function addStartCommands(program) {
|
|
|
99
100
|
program
|
|
100
101
|
.command('start <file>')
|
|
101
102
|
.alias('s')
|
|
102
|
-
.description('Start
|
|
103
|
+
.description('Start a production application')
|
|
103
104
|
.option('-c, --no-color', 'Disable colored output')
|
|
104
105
|
.option('-t, --no-timing', 'Hide timing information')
|
|
105
|
-
.option('-
|
|
106
|
-
.option('-
|
|
107
|
-
.option('
|
|
108
|
-
.option('
|
|
109
|
-
.option('
|
|
110
|
-
.option('
|
|
111
|
-
.option('
|
|
112
|
-
.option('--
|
|
113
|
-
.option('--restart-delay <ms>', 'Delay between restarts in milliseconds', parseInt)
|
|
114
|
-
.option('--verbose', 'Verbose output')
|
|
115
|
-
.option('--info', 'Show detailed information during startup')
|
|
116
|
-
.option('--signal <signal>', 'Signal to send to processes on restart', 'SIGTERM')
|
|
117
|
-
.option('--env <env>', 'Environment mode', 'production')
|
|
118
|
-
.option('--cluster', 'Enable cluster mode (spawn multiple processes)')
|
|
119
|
-
.option('--cluster-instances <number>', 'Number of cluster instances', parseInt)
|
|
120
|
-
.option('--memory-limit <mb>', 'Memory limit in MB', parseInt)
|
|
121
|
-
.option('--cpu-limit <percent>', 'CPU limit percentage', parseInt)
|
|
106
|
+
.option('-v, --verbose', 'Show detailed logs')
|
|
107
|
+
.option('-w, --watch', 'Watch for file changes')
|
|
108
|
+
.option('--delay <ms>', 'Delay between file changes (ms)', '1000')
|
|
109
|
+
.option('--environment <env>', 'Application environment', 'production')
|
|
110
|
+
.option('--cluster', 'Enable cluster mode')
|
|
111
|
+
.option('--instances <num>', 'Number of cluster instances', '1')
|
|
112
|
+
.option('--memory <mb>', 'Memory limit (MB)', '0')
|
|
113
|
+
.option('--cpu <percent>', 'CPU limit (%)', '0')
|
|
122
114
|
.action(async (file, options) => {
|
|
123
115
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
'.nyc_output/**',
|
|
156
|
-
'*.tmp',
|
|
157
|
-
'*.temp',
|
|
158
|
-
'.DS_Store',
|
|
159
|
-
'Thumbs.db'
|
|
160
|
-
];
|
|
161
|
-
const extensions = options.ext || ['js', 'mjs', 'json'];
|
|
162
|
-
// Log configuration only if --info flag is set
|
|
163
|
-
if (showInfo) {
|
|
164
|
-
console.log(chalk_1.default.blue(`${figures_1.default.info} neex start: Configuration:`));
|
|
165
|
-
console;
|
|
166
|
-
}
|
|
116
|
+
// Get start command configuration
|
|
117
|
+
const startConfig = await getStartCommand(file, true);
|
|
118
|
+
// Create StartManager instance
|
|
119
|
+
const startManager = new start_manager_js_1.StartManager({
|
|
120
|
+
runnerName: 'neex start',
|
|
121
|
+
targetFile: file,
|
|
122
|
+
command: startConfig.command,
|
|
123
|
+
processName: startConfig.processName,
|
|
124
|
+
runtime: startConfig.runtime,
|
|
125
|
+
environment: options.environment,
|
|
126
|
+
showInfo: true,
|
|
127
|
+
color: !options.color,
|
|
128
|
+
showTiming: !options.timing,
|
|
129
|
+
watch: options.watch,
|
|
130
|
+
delay: parseInt(options.delay),
|
|
131
|
+
verbose: options.verbose,
|
|
132
|
+
cluster: options.cluster,
|
|
133
|
+
clusterInstances: parseInt(options.instances),
|
|
134
|
+
memoryLimit: parseInt(options.memory),
|
|
135
|
+
cpuLimit: parseInt(options.cpu),
|
|
136
|
+
parallel: false,
|
|
137
|
+
printOutput: true,
|
|
138
|
+
prefix: true,
|
|
139
|
+
stopOnError: false,
|
|
140
|
+
minimalOutput: false,
|
|
141
|
+
groupOutput: false,
|
|
142
|
+
isServerMode: true
|
|
143
|
+
});
|
|
144
|
+
// Start the application
|
|
145
|
+
await startManager.start();
|
|
146
|
+
console.log(chalk_1.default.green(`${figures_1.default.tick} Application started successfully`));
|
|
167
147
|
}
|
|
168
148
|
catch (error) {
|
|
169
|
-
console.error(chalk_1.default.red(`${figures_1.default.cross}
|
|
149
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} Failed to start application`));
|
|
150
|
+
if (error instanceof Error) {
|
|
151
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
152
|
+
}
|
|
170
153
|
process.exit(1);
|
|
171
154
|
}
|
|
172
155
|
});
|
|
@@ -45,6 +45,9 @@ class StartManager {
|
|
|
45
45
|
cpuUsage: 0,
|
|
46
46
|
uptime: 0
|
|
47
47
|
};
|
|
48
|
+
if (!options.targetFile || !options.command || !options.processName || !options.runtime) {
|
|
49
|
+
throw new Error('Missing required options: targetFile, command, processName, and runtime are required');
|
|
50
|
+
}
|
|
48
51
|
const defaultOptions = {
|
|
49
52
|
parallel: false,
|
|
50
53
|
printOutput: true,
|
|
@@ -81,10 +84,31 @@ class StartManager {
|
|
|
81
84
|
cluster: false,
|
|
82
85
|
clusterInstances: os.cpus().length
|
|
83
86
|
};
|
|
87
|
+
// Validate numeric values
|
|
88
|
+
const validateNumber = (value, name) => {
|
|
89
|
+
const num = parseInt(value);
|
|
90
|
+
if (isNaN(num) || num < 0) {
|
|
91
|
+
throw new Error(`Invalid ${name}: must be a non-negative number`);
|
|
92
|
+
}
|
|
93
|
+
return num;
|
|
94
|
+
};
|
|
84
95
|
this.options = {
|
|
85
96
|
...defaultOptions,
|
|
86
|
-
...options
|
|
97
|
+
...options,
|
|
98
|
+
delay: validateNumber(options.delay || defaultOptions.delay, 'delay'),
|
|
99
|
+
clusterInstances: validateNumber(options.clusterInstances || defaultOptions.clusterInstances, 'clusterInstances'),
|
|
100
|
+
memoryLimit: options.memoryLimit ? validateNumber(options.memoryLimit, 'memoryLimit') : undefined,
|
|
101
|
+
cpuLimit: options.cpuLimit ? validateNumber(options.cpuLimit, 'cpuLimit') : undefined,
|
|
102
|
+
maxRestarts: options.maxRestarts ? validateNumber(options.maxRestarts, 'maxRestarts') : undefined,
|
|
103
|
+
restartDelay: validateNumber(options.restartDelay || defaultOptions.restartDelay, 'restartDelay')
|
|
87
104
|
};
|
|
105
|
+
// Validate environment
|
|
106
|
+
if (this.options.environment) {
|
|
107
|
+
this.options.environment = this.options.environment.toLowerCase();
|
|
108
|
+
if (!['production', 'development', 'staging', 'test'].includes(this.options.environment)) {
|
|
109
|
+
throw new Error(`Invalid environment: ${this.options.environment}. Must be one of: production, development, staging, test`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
88
112
|
}
|
|
89
113
|
setupFileWatcher() {
|
|
90
114
|
var _a;
|
|
@@ -171,20 +195,59 @@ class StartManager {
|
|
|
171
195
|
logger_1.default.printLine(`${prefix} ${chalk_1.default.red(`${figures_1.default.cross} Application crashed`)}`, 'error');
|
|
172
196
|
logger_1.default.printLine(`${prefix} ${chalk_1.default.yellow(`${figures_1.default.warning} Attempting restart in ${this.options.restartDelay}ms...`)}`, 'info');
|
|
173
197
|
}
|
|
198
|
+
// Check if we should restart
|
|
199
|
+
if (!this.shouldRestart()) {
|
|
200
|
+
if (this.options.showInfo) {
|
|
201
|
+
logger_1.default.printLine(`${prefix} ${chalk_1.default.red(`${figures_1.default.cross} Max restarts reached (${this.options.maxRestarts}). Stopping application.`)}`, 'error');
|
|
202
|
+
}
|
|
203
|
+
await this.stop();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
174
206
|
// Wait before restarting
|
|
175
207
|
await new Promise(resolve => setTimeout(resolve, this.options.restartDelay));
|
|
176
|
-
|
|
208
|
+
try {
|
|
209
|
+
await this.gracefulRestart();
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
logger_1.default.printLine(`Failed to restart application: ${error.message}`, 'error');
|
|
213
|
+
if (this.options.stopOnError) {
|
|
214
|
+
await this.stop();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
177
217
|
}
|
|
178
218
|
collectProcessStats() {
|
|
219
|
+
var _a;
|
|
179
220
|
if (!this.options.showInfo) {
|
|
180
221
|
return;
|
|
181
222
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
223
|
+
try {
|
|
224
|
+
const memUsage = process.memoryUsage();
|
|
225
|
+
this.processStats.memoryUsage = Math.round(memUsage.heapUsed / 1024 / 1024);
|
|
226
|
+
this.processStats.uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
|
|
227
|
+
// More accurate CPU usage calculation
|
|
228
|
+
const cpuUsage = process.cpuUsage();
|
|
229
|
+
const totalCpuTime = cpuUsage.user + cpuUsage.system;
|
|
230
|
+
const timeDiff = Date.now() - (((_a = this.lastRestartTime) === null || _a === void 0 ? void 0 : _a.getTime()) || this.startTime.getTime());
|
|
231
|
+
this.processStats.cpuUsage = Math.round((totalCpuTime / timeDiff) * 100);
|
|
232
|
+
// Check resource limits
|
|
233
|
+
if (this.options.memoryLimit && this.processStats.memoryUsage > this.options.memoryLimit) {
|
|
234
|
+
const prefix = chalk_1.default.cyan(`[${this.options.runnerName}]`);
|
|
235
|
+
logger_1.default.printLine(`${prefix} ${chalk_1.default.red(`${figures_1.default.warning} Memory limit exceeded: ${this.processStats.memoryUsage}MB > ${this.options.memoryLimit}MB`)}`, 'warn');
|
|
236
|
+
if (this.options.stopOnError) {
|
|
237
|
+
this.stop();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (this.options.cpuLimit && this.processStats.cpuUsage > this.options.cpuLimit) {
|
|
241
|
+
const prefix = chalk_1.default.cyan(`[${this.options.runnerName}]`);
|
|
242
|
+
logger_1.default.printLine(`${prefix} ${chalk_1.default.red(`${figures_1.default.warning} CPU limit exceeded: ${this.processStats.cpuUsage}% > ${this.options.cpuLimit}%`)}`, 'warn');
|
|
243
|
+
if (this.options.stopOnError) {
|
|
244
|
+
this.stop();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
logger_1.default.printLine(`Failed to collect process stats: ${error.message}`, 'error');
|
|
250
|
+
}
|
|
188
251
|
}
|
|
189
252
|
printStartBanner() {
|
|
190
253
|
var _a, _b;
|
|
@@ -319,6 +382,35 @@ class StartManager {
|
|
|
319
382
|
logger_1.default.printLine(`${prefix} ${chalk_1.default.green(`${figures_1.default.tick} ${this.options.processName} restarted successfully`)}`, 'info');
|
|
320
383
|
}
|
|
321
384
|
}
|
|
385
|
+
setupGracefulShutdown() {
|
|
386
|
+
const handleSignal = async (signal) => {
|
|
387
|
+
if (!this.isRunning) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
if (this.options.showInfo) {
|
|
392
|
+
console.log(`\n${chalk_1.default.yellow(`${figures_1.default.warning} Received ${signal}. Gracefully shutting down ${this.options.processName}...`)}`);
|
|
393
|
+
}
|
|
394
|
+
await this.stop();
|
|
395
|
+
process.exit(0);
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
logger_1.default.printLine(`Failed to handle ${signal}: ${error.message}`, 'error');
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
process.on('SIGINT', () => handleSignal('SIGINT'));
|
|
403
|
+
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
404
|
+
process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
|
|
405
|
+
process.on('uncaughtException', (error) => {
|
|
406
|
+
logger_1.default.printLine(`Uncaught Exception: ${error.message}`, 'error');
|
|
407
|
+
handleSignal('SIGTERM');
|
|
408
|
+
});
|
|
409
|
+
process.on('unhandledRejection', (reason) => {
|
|
410
|
+
logger_1.default.printLine(`Unhandled Rejection: ${reason}`, 'error');
|
|
411
|
+
handleSignal('SIGTERM');
|
|
412
|
+
});
|
|
413
|
+
}
|
|
322
414
|
async stop() {
|
|
323
415
|
const prefix = chalk_1.default.cyan(`[${this.options.runnerName}]`);
|
|
324
416
|
if (!this.isRunning) {
|
|
@@ -328,34 +420,29 @@ class StartManager {
|
|
|
328
420
|
logger_1.default.printLine(`${prefix} ${chalk_1.default.yellow(`${figures_1.default.warning} Stopping ${this.options.processName}...`)}`, 'info');
|
|
329
421
|
}
|
|
330
422
|
this.isRunning = false;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
this.fileWatcher
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
this.runner
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
logger_1.default.printLine(`${prefix} ${chalk_1.default.green(`${figures_1.default.tick} ${this.options.processName} stopped successfully`)}`, 'info');
|
|
344
|
-
logger_1.default.printLine(`${prefix} Final stats: ${uptimeStr} uptime, ${this.restartCount} restarts, ${this.crashCount} crashes`, 'info');
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
setupGracefulShutdown() {
|
|
348
|
-
const handleSignal = (signal) => {
|
|
423
|
+
try {
|
|
424
|
+
// Stop file watcher
|
|
425
|
+
if (this.fileWatcher) {
|
|
426
|
+
await this.fileWatcher.stop();
|
|
427
|
+
}
|
|
428
|
+
// Stop application gracefully
|
|
429
|
+
if (this.runner) {
|
|
430
|
+
await this.runner.cleanup(this.options.signal);
|
|
431
|
+
}
|
|
432
|
+
// Calculate final stats
|
|
433
|
+
const uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
|
|
434
|
+
const uptimeStr = this.formatUptime(uptime);
|
|
349
435
|
if (this.options.showInfo) {
|
|
350
|
-
|
|
436
|
+
logger_1.default.printLine(`${prefix} ${chalk_1.default.green(`${figures_1.default.tick} ${this.options.processName} stopped successfully`)}`, 'info');
|
|
437
|
+
logger_1.default.printLine(`${prefix} Final stats: ${uptimeStr} uptime, ${this.restartCount} restarts, ${this.crashCount} crashes`, 'info');
|
|
351
438
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
logger_1.default.printLine(`Failed to stop application: ${error.message}`, 'error');
|
|
442
|
+
if (this.options.stopOnError) {
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
359
446
|
}
|
|
360
447
|
isActive() {
|
|
361
448
|
return this.isRunning;
|