neex 0.1.3 → 0.1.5
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 +225 -38
- package/dist/src/cli.js +493 -8
- package/dist/src/dev-runner.js +203 -0
- package/dist/src/index.js +3 -1
- package/dist/src/logger.js +36 -0
- package/dist/src/process-manager.js +330 -0
- package/dist/src/runner.js +112 -78
- package/dist/src/utils.js +10 -0
- package/dist/src/watcher.js +245 -0
- package/feet.txt +16 -0
- package/package.json +1 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DevRunner = void 0;
|
|
7
|
+
// src/dev-runner.ts - Development runner with file watching (nodemon functionality)
|
|
8
|
+
const watcher_1 = require("./watcher");
|
|
9
|
+
const runner_1 = require("./runner");
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const figures_1 = __importDefault(require("figures"));
|
|
12
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
13
|
+
class DevRunner {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.commands = [];
|
|
16
|
+
this.isRunning = false;
|
|
17
|
+
this.restartCount = 0;
|
|
18
|
+
this.startTime = new Date();
|
|
19
|
+
const defaultOptions = {
|
|
20
|
+
parallel: false,
|
|
21
|
+
printOutput: true,
|
|
22
|
+
color: true,
|
|
23
|
+
showTiming: true,
|
|
24
|
+
prefix: true,
|
|
25
|
+
stopOnError: false,
|
|
26
|
+
minimalOutput: false,
|
|
27
|
+
groupOutput: false,
|
|
28
|
+
isServerMode: false,
|
|
29
|
+
restartOnChange: true,
|
|
30
|
+
clearConsole: false,
|
|
31
|
+
signal: 'SIGTERM',
|
|
32
|
+
watch: ['./'],
|
|
33
|
+
ignore: [
|
|
34
|
+
'node_modules/**',
|
|
35
|
+
'.git/**',
|
|
36
|
+
'*.log',
|
|
37
|
+
'dist/**',
|
|
38
|
+
'build/**',
|
|
39
|
+
'coverage/**',
|
|
40
|
+
'.nyc_output/**',
|
|
41
|
+
'*.tmp',
|
|
42
|
+
'*.temp'
|
|
43
|
+
],
|
|
44
|
+
ext: ['js', 'mjs', 'json', 'ts', 'tsx', 'jsx'],
|
|
45
|
+
delay: 1000,
|
|
46
|
+
verbose: false,
|
|
47
|
+
};
|
|
48
|
+
this.options = {
|
|
49
|
+
...defaultOptions,
|
|
50
|
+
...options
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
setupFileWatcher() {
|
|
54
|
+
const watchOptions = {
|
|
55
|
+
watch: this.options.watch || ['./'],
|
|
56
|
+
ignore: this.options.ignore,
|
|
57
|
+
ext: this.options.ext,
|
|
58
|
+
delay: this.options.delay,
|
|
59
|
+
verbose: this.options.verbose
|
|
60
|
+
};
|
|
61
|
+
this.fileWatcher = new watcher_1.FileWatcher(watchOptions);
|
|
62
|
+
this.fileWatcher.on('change', (event) => {
|
|
63
|
+
if (this.options.restartOnChange && this.isRunning) {
|
|
64
|
+
this.handleFileChange(event);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async handleFileChange(event) {
|
|
69
|
+
logger_1.default.printLine(`File changed: ${chalk_1.default.yellow(event.relativePath)}`, 'info');
|
|
70
|
+
if (this.options.clearConsole) {
|
|
71
|
+
console.clear();
|
|
72
|
+
}
|
|
73
|
+
await this.restart();
|
|
74
|
+
}
|
|
75
|
+
async runCommands() {
|
|
76
|
+
if (this.commands.length === 0) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
this.runner = new runner_1.Runner(this.options);
|
|
80
|
+
try {
|
|
81
|
+
const results = await this.runner.run(this.commands);
|
|
82
|
+
return results;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger_1.default.printLine(`Execution failed: ${error.message}`, 'error');
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
printDevBanner() {
|
|
90
|
+
var _a;
|
|
91
|
+
const uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
|
|
92
|
+
const uptimeStr = this.formatUptime(uptime);
|
|
93
|
+
console.log('\n' + chalk_1.default.bgGreen.black(' DEV MODE ') + '\n');
|
|
94
|
+
if (this.restartCount > 0) {
|
|
95
|
+
console.log(chalk_1.default.green(`${figures_1.default.arrowUp} Restarted ${this.restartCount} times`));
|
|
96
|
+
}
|
|
97
|
+
console.log(chalk_1.default.blue(`${figures_1.default.info} Uptime: ${uptimeStr}`));
|
|
98
|
+
console.log(chalk_1.default.blue(`${figures_1.default.info} Watching: ${((_a = this.options.watch) === null || _a === void 0 ? void 0 : _a.join(', ')) || 'current directory'}`));
|
|
99
|
+
if (this.options.ext && this.options.ext.length > 0) {
|
|
100
|
+
console.log(chalk_1.default.blue(`${figures_1.default.info} Extensions: ${this.options.ext.join(', ')}`));
|
|
101
|
+
}
|
|
102
|
+
console.log('');
|
|
103
|
+
}
|
|
104
|
+
formatUptime(seconds) {
|
|
105
|
+
if (seconds < 60) {
|
|
106
|
+
return `${seconds}s`;
|
|
107
|
+
}
|
|
108
|
+
else if (seconds < 3600) {
|
|
109
|
+
const minutes = Math.floor(seconds / 60);
|
|
110
|
+
const remainingSeconds = seconds % 60;
|
|
111
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const hours = Math.floor(seconds / 3600);
|
|
115
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
116
|
+
return `${hours}h ${minutes}m`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async start(commands) {
|
|
120
|
+
this.commands = commands;
|
|
121
|
+
this.isRunning = true;
|
|
122
|
+
this.startTime = new Date();
|
|
123
|
+
// Setup file watcher
|
|
124
|
+
this.setupFileWatcher();
|
|
125
|
+
// Print development banner
|
|
126
|
+
this.printDevBanner();
|
|
127
|
+
// Start file watcher
|
|
128
|
+
if (this.fileWatcher) {
|
|
129
|
+
await this.fileWatcher.start();
|
|
130
|
+
}
|
|
131
|
+
// Run initial commands
|
|
132
|
+
logger_1.default.printLine('Starting development server...', 'info');
|
|
133
|
+
await this.runCommands();
|
|
134
|
+
// Set up graceful shutdown
|
|
135
|
+
this.setupGracefulShutdown();
|
|
136
|
+
logger_1.default.printLine('Development server started. Watching for changes...', 'info');
|
|
137
|
+
logger_1.default.printLine(`Press ${chalk_1.default.cyan('Ctrl+C')} to stop`, 'info');
|
|
138
|
+
}
|
|
139
|
+
async restart() {
|
|
140
|
+
if (!this.isRunning) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
logger_1.default.printLine('Restarting due to file changes...', 'info');
|
|
144
|
+
this.restartCount++;
|
|
145
|
+
// Stop current processes
|
|
146
|
+
if (this.runner) {
|
|
147
|
+
this.runner.cleanup(this.options.signal);
|
|
148
|
+
}
|
|
149
|
+
// Wait a moment before restarting
|
|
150
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
151
|
+
// Print restart banner
|
|
152
|
+
this.printDevBanner();
|
|
153
|
+
// Run commands again
|
|
154
|
+
await this.runCommands();
|
|
155
|
+
logger_1.default.printLine('Restart completed. Watching for changes...', 'info');
|
|
156
|
+
}
|
|
157
|
+
async stop() {
|
|
158
|
+
if (!this.isRunning) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
logger_1.default.printLine('Stopping development server...', 'info');
|
|
162
|
+
this.isRunning = false;
|
|
163
|
+
// Stop file watcher
|
|
164
|
+
if (this.fileWatcher) {
|
|
165
|
+
this.fileWatcher.stop();
|
|
166
|
+
}
|
|
167
|
+
// Stop current processes
|
|
168
|
+
if (this.runner) {
|
|
169
|
+
this.runner.cleanup(this.options.signal);
|
|
170
|
+
}
|
|
171
|
+
const uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1000);
|
|
172
|
+
const uptimeStr = this.formatUptime(uptime);
|
|
173
|
+
logger_1.default.printLine(`Development server stopped after ${uptimeStr}`, 'info');
|
|
174
|
+
if (this.restartCount > 0) {
|
|
175
|
+
logger_1.default.printLine(`Total restarts: ${this.restartCount}`, 'info');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
setupGracefulShutdown() {
|
|
179
|
+
const handleSignal = (signal) => {
|
|
180
|
+
console.log(`\n${chalk_1.default.yellow(`${figures_1.default.warning} Received ${signal}. Shutting down development server...`)}`);
|
|
181
|
+
this.stop().then(() => {
|
|
182
|
+
process.exit(0);
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
process.on('SIGINT', () => handleSignal('SIGINT'));
|
|
186
|
+
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
187
|
+
process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
|
|
188
|
+
}
|
|
189
|
+
isActive() {
|
|
190
|
+
return this.isRunning;
|
|
191
|
+
}
|
|
192
|
+
getUptime() {
|
|
193
|
+
return Math.floor((Date.now() - this.startTime.getTime()) / 1000);
|
|
194
|
+
}
|
|
195
|
+
getRestartCount() {
|
|
196
|
+
return this.restartCount;
|
|
197
|
+
}
|
|
198
|
+
getWatchedFiles() {
|
|
199
|
+
var _a;
|
|
200
|
+
return ((_a = this.fileWatcher) === null || _a === void 0 ? void 0 : _a.getWatchedFiles()) || [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
exports.DevRunner = DevRunner;
|
package/dist/src/index.js
CHANGED
|
@@ -23,7 +23,9 @@ async function run(commands, options) {
|
|
|
23
23
|
stopOnError: (_f = options === null || options === void 0 ? void 0 : options.stopOnError) !== null && _f !== void 0 ? _f : false,
|
|
24
24
|
minimalOutput: (_g = options === null || options === void 0 ? void 0 : options.minimalOutput) !== null && _g !== void 0 ? _g : false,
|
|
25
25
|
groupOutput: (_h = options === null || options === void 0 ? void 0 : options.groupOutput) !== null && _h !== void 0 ? _h : false,
|
|
26
|
-
isServerMode: (_j = options === null || options === void 0 ? void 0 : options.isServerMode) !== null && _j !== void 0 ? _j : false
|
|
26
|
+
isServerMode: (_j = options === null || options === void 0 ? void 0 : options.isServerMode) !== null && _j !== void 0 ? _j : false,
|
|
27
|
+
retry: options === null || options === void 0 ? void 0 : options.retry,
|
|
28
|
+
retryDelay: options === null || options === void 0 ? void 0 : options.retryDelay
|
|
27
29
|
};
|
|
28
30
|
const runner = new runner_1.Runner(runOptions);
|
|
29
31
|
if (options === null || options === void 0 ? void 0 : options.registerCleanup) {
|
package/dist/src/logger.js
CHANGED
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const figures_1 = __importDefault(require("figures"));
|
|
9
9
|
const string_width_1 = __importDefault(require("string-width"));
|
|
10
|
+
const utils_1 = require("./utils");
|
|
10
11
|
class Logger {
|
|
11
12
|
constructor() {
|
|
12
13
|
this.prefixLength = 0;
|
|
@@ -120,6 +121,9 @@ class Logger {
|
|
|
120
121
|
// Clear buffer after printing
|
|
121
122
|
this.outputBuffer.set(command, []);
|
|
122
123
|
}
|
|
124
|
+
clearBuffer(command) {
|
|
125
|
+
this.outputBuffer.set(command, []);
|
|
126
|
+
}
|
|
123
127
|
printLine(message, level = 'info') {
|
|
124
128
|
if (level === 'error') {
|
|
125
129
|
console.error(chalk_1.default.red(`${figures_1.default.cross} ${message}`));
|
|
@@ -136,6 +140,12 @@ class Logger {
|
|
|
136
140
|
this.startTimes.set(command, new Date());
|
|
137
141
|
const prefix = this.formatPrefix(command);
|
|
138
142
|
const color = this.commandColors.get(command) || chalk_1.default.white;
|
|
143
|
+
// Stop any previous spinner for this command (e.g. if retrying)
|
|
144
|
+
this.stopSpinner(command);
|
|
145
|
+
// Clear the line before printing "Starting..."
|
|
146
|
+
if (this.isSpinnerActive) { // Check if any spinner was active to avoid clearing unnecessarily
|
|
147
|
+
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
148
|
+
}
|
|
139
149
|
console.log(`${prefix} ${color('Starting...')}`);
|
|
140
150
|
// Start spinner for this command
|
|
141
151
|
this.startSpinner(command);
|
|
@@ -197,6 +207,32 @@ class Logger {
|
|
|
197
207
|
console.error(`${prefix} ${chalk_1.default.red(error.message)}`);
|
|
198
208
|
}
|
|
199
209
|
}
|
|
210
|
+
printEnd(result, minimalOutput) {
|
|
211
|
+
this.stopSpinner(result.command);
|
|
212
|
+
const prefix = this.formatPrefix(result.command); // Corrected to formatPrefix
|
|
213
|
+
let durationDisplay = '';
|
|
214
|
+
if (result.duration !== null) {
|
|
215
|
+
// Ensure result.duration is treated as a number here
|
|
216
|
+
durationDisplay = `(${(0, utils_1.formatDuration)(result.duration)})`;
|
|
217
|
+
}
|
|
218
|
+
const duration = durationDisplay;
|
|
219
|
+
if (minimalOutput) {
|
|
220
|
+
if (!result.success) {
|
|
221
|
+
const status = result.code !== null ? `failed (code ${result.code})` : 'failed';
|
|
222
|
+
this.printLine(`${prefix} ${chalk_1.default.red(figures_1.default.cross)} ${result.command} ${status} ${duration}`, 'error');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
if (result.success) {
|
|
227
|
+
this.printLine(`${prefix} ${chalk_1.default.green(figures_1.default.tick)} Command "${result.command}" finished successfully ${duration}`, 'info');
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
const errorCode = result.code !== null ? ` (code ${result.code})` : '';
|
|
231
|
+
const errorMessage = result.error ? `: ${result.error.message}` : '';
|
|
232
|
+
this.printLine(`${prefix} ${chalk_1.default.red(figures_1.default.cross)} Command "${result.command}" failed${errorCode}${errorMessage} ${duration}`, 'error');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
200
236
|
printSummary(results) {
|
|
201
237
|
// Stop any remaining spinners
|
|
202
238
|
this.stopAllSpinners();
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.ProcessManager = void 0;
|
|
30
|
+
// src/process-manager.ts - PM2 alternative process manager
|
|
31
|
+
const child_process_1 = require("child_process");
|
|
32
|
+
const fs = __importStar(require("fs/promises"));
|
|
33
|
+
const path = __importStar(require("path"));
|
|
34
|
+
const events_1 = require("events");
|
|
35
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
36
|
+
class ProcessManager extends events_1.EventEmitter {
|
|
37
|
+
constructor(configPath) {
|
|
38
|
+
super();
|
|
39
|
+
this.processes = new Map();
|
|
40
|
+
this.isMonitoring = false;
|
|
41
|
+
this.configPath = configPath || path.join(process.cwd(), '.neex', 'processes.json');
|
|
42
|
+
this.ensureConfigDir();
|
|
43
|
+
this.startMonitoring();
|
|
44
|
+
}
|
|
45
|
+
async ensureConfigDir() {
|
|
46
|
+
const dir = path.dirname(this.configPath);
|
|
47
|
+
try {
|
|
48
|
+
await fs.mkdir(dir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
// Directory might already exist
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async saveConfig() {
|
|
55
|
+
try {
|
|
56
|
+
const config = Array.from(this.processes.values()).map(proc => ({
|
|
57
|
+
...proc.config,
|
|
58
|
+
status: proc.status,
|
|
59
|
+
created_at: proc.created_at,
|
|
60
|
+
restarts: proc.restarts
|
|
61
|
+
}));
|
|
62
|
+
await fs.writeFile(this.configPath, JSON.stringify(config, null, 2));
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
logger_1.default.printLine(`Failed to save config: ${error.message}`, 'error');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async loadConfig() {
|
|
69
|
+
try {
|
|
70
|
+
const data = await fs.readFile(this.configPath, 'utf-8');
|
|
71
|
+
const configs = JSON.parse(data);
|
|
72
|
+
for (const config of configs) {
|
|
73
|
+
if (config.id && !this.processes.has(config.id)) {
|
|
74
|
+
const processInfo = {
|
|
75
|
+
id: config.id,
|
|
76
|
+
name: config.name,
|
|
77
|
+
status: 'stopped',
|
|
78
|
+
uptime: 0,
|
|
79
|
+
restarts: 0,
|
|
80
|
+
memory: 0,
|
|
81
|
+
cpu: 0,
|
|
82
|
+
created_at: new Date(),
|
|
83
|
+
config
|
|
84
|
+
};
|
|
85
|
+
this.processes.set(config.id, processInfo);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// Config file might not exist yet
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
generateId(name) {
|
|
94
|
+
let id = name.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
95
|
+
let counter = 0;
|
|
96
|
+
while (this.processes.has(id)) {
|
|
97
|
+
counter++;
|
|
98
|
+
id = `${name.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${counter}`;
|
|
99
|
+
}
|
|
100
|
+
return id;
|
|
101
|
+
}
|
|
102
|
+
async startProcess(processInfo) {
|
|
103
|
+
const { config } = processInfo;
|
|
104
|
+
try {
|
|
105
|
+
processInfo.status = 'launching';
|
|
106
|
+
this.emit('process:launching', processInfo);
|
|
107
|
+
const args = config.args || [];
|
|
108
|
+
const env = {
|
|
109
|
+
...process.env,
|
|
110
|
+
...config.env,
|
|
111
|
+
NEEX_PROCESS_ID: processInfo.id,
|
|
112
|
+
NEEX_PROCESS_NAME: processInfo.name
|
|
113
|
+
};
|
|
114
|
+
const childProcess = (0, child_process_1.spawn)(config.script, args, {
|
|
115
|
+
cwd: config.cwd || process.cwd(),
|
|
116
|
+
env,
|
|
117
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
118
|
+
detached: true
|
|
119
|
+
});
|
|
120
|
+
processInfo.process = childProcess;
|
|
121
|
+
processInfo.pid = childProcess.pid;
|
|
122
|
+
processInfo.status = 'online';
|
|
123
|
+
processInfo.created_at = new Date();
|
|
124
|
+
this.emit('process:start', processInfo);
|
|
125
|
+
// Handle stdout
|
|
126
|
+
if (childProcess.stdout) {
|
|
127
|
+
childProcess.stdout.on('data', (data) => {
|
|
128
|
+
this.emit('process:log', {
|
|
129
|
+
id: processInfo.id,
|
|
130
|
+
type: 'stdout',
|
|
131
|
+
data: data.toString()
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Handle stderr
|
|
136
|
+
if (childProcess.stderr) {
|
|
137
|
+
childProcess.stderr.on('data', (data) => {
|
|
138
|
+
this.emit('process:log', {
|
|
139
|
+
id: processInfo.id,
|
|
140
|
+
type: 'stderr',
|
|
141
|
+
data: data.toString()
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
// Handle process exit
|
|
146
|
+
childProcess.on('exit', (code, signal) => {
|
|
147
|
+
processInfo.status = code === 0 ? 'stopped' : 'errored';
|
|
148
|
+
processInfo.process = undefined;
|
|
149
|
+
processInfo.pid = undefined;
|
|
150
|
+
this.emit('process:exit', { processInfo, code, signal });
|
|
151
|
+
// Auto-restart if enabled
|
|
152
|
+
if (config.autorestart && processInfo.restarts < (config.max_restarts || 10)) {
|
|
153
|
+
const delay = config.restart_delay || 1000;
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
if (processInfo.status !== 'stopped') {
|
|
156
|
+
this.restart(processInfo.id);
|
|
157
|
+
}
|
|
158
|
+
}, delay);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
// Handle process error
|
|
162
|
+
childProcess.on('error', (error) => {
|
|
163
|
+
processInfo.status = 'errored';
|
|
164
|
+
this.emit('process:error', { processInfo, error });
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
processInfo.status = 'errored';
|
|
169
|
+
this.emit('process:error', { processInfo, error });
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
startMonitoring() {
|
|
174
|
+
if (this.isMonitoring)
|
|
175
|
+
return;
|
|
176
|
+
this.isMonitoring = true;
|
|
177
|
+
this.monitoringInterval = setInterval(() => {
|
|
178
|
+
this.updateProcessStats();
|
|
179
|
+
}, 5000); // Update every 5 seconds
|
|
180
|
+
}
|
|
181
|
+
stopMonitoring() {
|
|
182
|
+
if (this.monitoringInterval) {
|
|
183
|
+
clearInterval(this.monitoringInterval);
|
|
184
|
+
this.monitoringInterval = undefined;
|
|
185
|
+
}
|
|
186
|
+
this.isMonitoring = false;
|
|
187
|
+
}
|
|
188
|
+
async updateProcessStats() {
|
|
189
|
+
for (const processInfo of this.processes.values()) {
|
|
190
|
+
if (processInfo.status === 'online' && processInfo.pid) {
|
|
191
|
+
try {
|
|
192
|
+
// Update uptime
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
processInfo.uptime = Math.floor((now - processInfo.created_at.getTime()) / 1000);
|
|
195
|
+
// TODO: Implement memory and CPU monitoring
|
|
196
|
+
// This would require platform-specific code or external libraries
|
|
197
|
+
// For now, we'll set placeholder values
|
|
198
|
+
processInfo.memory = 0;
|
|
199
|
+
processInfo.cpu = 0;
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
// Process might have died
|
|
203
|
+
if (processInfo.status === 'online') {
|
|
204
|
+
processInfo.status = 'stopped';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async start(config) {
|
|
211
|
+
const id = this.generateId(config.name);
|
|
212
|
+
const processInfo = {
|
|
213
|
+
id,
|
|
214
|
+
name: config.name,
|
|
215
|
+
status: 'stopped',
|
|
216
|
+
uptime: 0,
|
|
217
|
+
restarts: 0,
|
|
218
|
+
memory: 0,
|
|
219
|
+
cpu: 0,
|
|
220
|
+
created_at: new Date(),
|
|
221
|
+
config: { ...config, id }
|
|
222
|
+
};
|
|
223
|
+
this.processes.set(id, processInfo);
|
|
224
|
+
await this.startProcess(processInfo);
|
|
225
|
+
await this.saveConfig();
|
|
226
|
+
return id;
|
|
227
|
+
}
|
|
228
|
+
async stop(id) {
|
|
229
|
+
const processInfo = this.processes.get(id);
|
|
230
|
+
if (!processInfo) {
|
|
231
|
+
throw new Error(`Process ${id} not found`);
|
|
232
|
+
}
|
|
233
|
+
if (processInfo.status === 'stopped') {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
processInfo.status = 'stopping';
|
|
237
|
+
if (processInfo.process && processInfo.pid) {
|
|
238
|
+
try {
|
|
239
|
+
// Try graceful shutdown first
|
|
240
|
+
processInfo.process.kill('SIGTERM');
|
|
241
|
+
// Force kill after timeout
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
if (processInfo.process && !processInfo.process.killed) {
|
|
244
|
+
processInfo.process.kill('SIGKILL');
|
|
245
|
+
}
|
|
246
|
+
}, 5000);
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
// Process might already be dead
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
processInfo.status = 'stopped';
|
|
253
|
+
processInfo.process = undefined;
|
|
254
|
+
processInfo.pid = undefined;
|
|
255
|
+
await this.saveConfig();
|
|
256
|
+
this.emit('process:stop', processInfo);
|
|
257
|
+
}
|
|
258
|
+
async restart(id) {
|
|
259
|
+
const processInfo = this.processes.get(id);
|
|
260
|
+
if (!processInfo) {
|
|
261
|
+
throw new Error(`Process ${id} not found`);
|
|
262
|
+
}
|
|
263
|
+
if (processInfo.status === 'online') {
|
|
264
|
+
await this.stop(id);
|
|
265
|
+
// Wait a bit before restarting
|
|
266
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
267
|
+
}
|
|
268
|
+
processInfo.restarts++;
|
|
269
|
+
processInfo.last_restart = new Date();
|
|
270
|
+
await this.startProcess(processInfo);
|
|
271
|
+
await this.saveConfig();
|
|
272
|
+
}
|
|
273
|
+
async delete(id) {
|
|
274
|
+
const processInfo = this.processes.get(id);
|
|
275
|
+
if (!processInfo) {
|
|
276
|
+
throw new Error(`Process ${id} not found`);
|
|
277
|
+
}
|
|
278
|
+
if (processInfo.status === 'online') {
|
|
279
|
+
await this.stop(id);
|
|
280
|
+
}
|
|
281
|
+
this.processes.delete(id);
|
|
282
|
+
await this.saveConfig();
|
|
283
|
+
this.emit('process:delete', processInfo);
|
|
284
|
+
}
|
|
285
|
+
async list() {
|
|
286
|
+
return Array.from(this.processes.values());
|
|
287
|
+
}
|
|
288
|
+
async status(id) {
|
|
289
|
+
if (id) {
|
|
290
|
+
const processInfo = this.processes.get(id);
|
|
291
|
+
if (!processInfo) {
|
|
292
|
+
throw new Error(`Process ${id} not found`);
|
|
293
|
+
}
|
|
294
|
+
return processInfo;
|
|
295
|
+
}
|
|
296
|
+
return this.list();
|
|
297
|
+
}
|
|
298
|
+
async logs(id, lines = 100) {
|
|
299
|
+
// TODO: Implement log file reading
|
|
300
|
+
// For now, return empty array
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
async save() {
|
|
304
|
+
await this.saveConfig();
|
|
305
|
+
}
|
|
306
|
+
async load() {
|
|
307
|
+
await this.loadConfig();
|
|
308
|
+
}
|
|
309
|
+
async stopAll() {
|
|
310
|
+
const promises = Array.from(this.processes.keys()).map(id => this.stop(id));
|
|
311
|
+
await Promise.all(promises);
|
|
312
|
+
}
|
|
313
|
+
async restartAll() {
|
|
314
|
+
const promises = Array.from(this.processes.keys()).map(id => this.restart(id));
|
|
315
|
+
await Promise.all(promises);
|
|
316
|
+
}
|
|
317
|
+
async deleteAll() {
|
|
318
|
+
await this.stopAll();
|
|
319
|
+
this.processes.clear();
|
|
320
|
+
await this.saveConfig();
|
|
321
|
+
}
|
|
322
|
+
getProcess(id) {
|
|
323
|
+
return this.processes.get(id);
|
|
324
|
+
}
|
|
325
|
+
dispose() {
|
|
326
|
+
this.stopMonitoring();
|
|
327
|
+
this.stopAll();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
exports.ProcessManager = ProcessManager;
|