neex 0.1.8 → 0.2.6

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.
@@ -1,669 +1,208 @@
1
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.ProcessManager = void 0;
30
- // src/process-manager.ts - Enhanced PM2-like process manager
3
+ exports.processManager = void 0;
31
4
  const child_process_1 = require("child_process");
32
- const fs = __importStar(require("fs"));
5
+ const chokidar_1 = require("chokidar");
33
6
  const fs_1 = require("fs");
34
- const path = __importStar(require("path"));
7
+ const path_1 = require("path");
35
8
  const events_1 = require("events");
36
- const chalk_1 = __importDefault(require("chalk"));
37
- const figures_1 = __importDefault(require("figures"));
38
- const logger_js_1 = __importDefault(require("./logger.js"));
39
9
  class ProcessManager extends events_1.EventEmitter {
40
- constructor(configPath) {
10
+ constructor() {
41
11
  super();
42
12
  this.processes = new Map();
43
- this.logStreams = new Map();
44
- this.isMonitoring = false;
45
- this.isDisposed = false;
46
- const baseDir = path.join(process.cwd(), '.neex');
47
- this.configPath = configPath || path.join(baseDir, 'processes.json');
48
- this.logDir = path.join(baseDir, 'logs');
49
- this.pidDir = path.join(baseDir, 'pids');
50
- this.ensureDirectories();
51
- this.startMonitoring();
13
+ this.watchers = new Map();
14
+ this.defaultMaxRestarts = 5;
15
+ this.defaultRestartDelay = 1000; // 1 second
16
+ this.logDir = (0, path_1.resolve)(process.cwd(), '.neex-logs');
17
+ this.configFile = (0, path_1.resolve)(process.cwd(), '.neex-config.json');
18
+ this.initialize();
52
19
  }
53
- async ensureDirectories() {
54
- try {
55
- const dirs = [
56
- path.dirname(this.configPath),
57
- this.logDir,
58
- this.pidDir
59
- ];
60
- for (const dir of dirs) {
61
- await fs_1.promises.mkdir(dir, { recursive: true });
62
- }
63
- }
64
- catch (error) {
65
- logger_js_1.default.printLine(`Failed to create directories: ${error.message}`, 'error');
20
+ initialize() {
21
+ if (!(0, fs_1.existsSync)(this.logDir)) {
22
+ (0, fs_1.mkdirSync)(this.logDir, { recursive: true });
66
23
  }
24
+ this.loadConfig();
67
25
  }
68
- async saveConfig() {
69
- try {
70
- const config = Array.from(this.processes.values()).map(proc => {
71
- var _a, _b;
72
- return ({
73
- ...proc.config,
74
- status: proc.status,
75
- created_at: proc.created_at.toISOString(),
76
- started_at: (_a = proc.started_at) === null || _a === void 0 ? void 0 : _a.toISOString(),
77
- restarts: proc.restarts,
78
- last_restart: (_b = proc.last_restart) === null || _b === void 0 ? void 0 : _b.toISOString()
79
- });
80
- });
81
- await fs_1.promises.writeFile(this.configPath, JSON.stringify(config, null, 2));
82
- }
83
- catch (error) {
84
- logger_js_1.default.printLine(`Failed to save config: ${error.message}`, 'error');
26
+ loadConfig() {
27
+ if ((0, fs_1.existsSync)(this.configFile)) {
28
+ const config = JSON.parse((0, fs_1.readFileSync)(this.configFile, 'utf-8'));
29
+ this.processes = new Map(Object.entries(config));
85
30
  }
86
31
  }
87
- async load() {
88
- try {
89
- const data = await fs_1.promises.readFile(this.configPath, 'utf-8');
90
- const configs = JSON.parse(data);
91
- for (const config of configs) {
92
- if (config.id && !this.processes.has(config.id)) {
93
- const processInfo = {
94
- id: config.id,
95
- name: config.name,
96
- status: 'stopped',
97
- uptime: 0,
98
- restarts: config.restarts || 0,
99
- memory: 0,
100
- cpu: 0,
101
- created_at: config.created_at ? new Date(config.created_at) : new Date(),
102
- started_at: config.started_at ? new Date(config.started_at) : undefined,
103
- last_restart: config.last_restart ? new Date(config.last_restart) : undefined,
104
- config: {
105
- ...config,
106
- // Ensure required fields have defaults
107
- autorestart: config.autorestart !== false,
108
- max_restarts: config.max_restarts || 10,
109
- restart_delay: config.restart_delay || 1000,
110
- instances: config.instances || 1
111
- }
112
- };
113
- this.processes.set(config.id, processInfo);
114
- // Check if process is actually running
115
- if (config.pid) {
116
- const isRunning = await this.isProcessRunning(config.pid);
117
- if (isRunning) {
118
- processInfo.status = 'online';
119
- processInfo.pid = config.pid;
120
- processInfo.started_at = processInfo.started_at || new Date();
121
- }
122
- }
123
- }
124
- }
125
- }
126
- catch (error) {
127
- // Config file might not exist yet, that's fine
128
- }
32
+ saveConfig() {
33
+ const config = Object.fromEntries(this.processes);
34
+ (0, fs_1.writeFileSync)(this.configFile, JSON.stringify(config, null, 2));
129
35
  }
130
- async isProcessRunning(pid) {
131
- try {
132
- process.kill(pid, 0);
133
- return true;
134
- }
135
- catch (error) {
136
- return false;
137
- }
138
- }
139
- generateId(name) {
140
- let id = name.toLowerCase().replace(/[^a-z0-9-_]/g, '-');
141
- let counter = 0;
142
- while (this.processes.has(id)) {
143
- counter++;
144
- id = `${name.toLowerCase().replace(/[^a-z0-9-_]/g, '-')}-${counter}`;
145
- }
146
- return id;
147
- }
148
- setupLogStreams(processInfo) {
149
- const { config } = processInfo;
150
- // Close existing streams
151
- this.closeLogStreams(processInfo.id);
152
- // Setup default log paths
153
- if (!config.out_file) {
154
- config.out_file = path.join(this.logDir, `${processInfo.id}-out.log`);
155
- }
156
- if (!config.error_file) {
157
- config.error_file = path.join(this.logDir, `${processInfo.id}-error.log`);
158
- }
159
- try {
160
- const outStream = fs.createWriteStream(config.out_file, { flags: 'a' });
161
- const errStream = fs.createWriteStream(config.error_file, { flags: 'a' });
162
- this.logStreams.set(processInfo.id, { out: outStream, err: errStream });
163
- }
164
- catch (error) {
165
- logger_js_1.default.printLine(`Failed to create log streams for ${processInfo.id}: ${error.message}`, 'error');
166
- }
36
+ generateId() {
37
+ return Math.random().toString(36).substring(2, 15);
167
38
  }
168
- closeLogStreams(id) {
169
- var _a, _b;
170
- const streams = this.logStreams.get(id);
171
- if (streams) {
172
- (_a = streams.out) === null || _a === void 0 ? void 0 : _a.end();
173
- (_b = streams.err) === null || _b === void 0 ? void 0 : _b.end();
174
- this.logStreams.delete(id);
175
- }
39
+ getLogPath(id) {
40
+ return (0, path_1.join)(this.logDir, `${id}.log`);
176
41
  }
177
- async writePidFile(processInfo) {
178
- if (!processInfo.pid)
179
- return;
180
- const pidFile = path.join(this.pidDir, `${processInfo.id}.pid`);
181
- try {
182
- await fs_1.promises.writeFile(pidFile, processInfo.pid.toString());
183
- }
184
- catch (error) {
185
- logger_js_1.default.printLine(`Failed to write PID file: ${error.message}`, 'error');
42
+ async startx(path, options = {}) {
43
+ const id = this.generateId();
44
+ const name = options.name || `process-${id}`;
45
+ const fullPath = (0, path_1.resolve)(process.cwd(), path);
46
+ if (!(0, fs_1.existsSync)(fullPath)) {
47
+ throw new Error(`Path ${path} does not exist`);
186
48
  }
187
- }
188
- async removePidFile(id) {
189
- const pidFile = path.join(this.pidDir, `${id}.pid`);
49
+ const processInfo = {
50
+ id,
51
+ name,
52
+ pid: 0,
53
+ status: 'stopped',
54
+ startTime: Date.now(),
55
+ path: fullPath,
56
+ command: `node ${fullPath}`,
57
+ logs: [],
58
+ memory: 0,
59
+ cpu: 0,
60
+ uptime: 0,
61
+ restarts: 0,
62
+ maxRestarts: options.maxRestarts || this.defaultMaxRestarts,
63
+ restartDelay: options.restartDelay || this.defaultRestartDelay,
64
+ watch: options.watch
65
+ };
190
66
  try {
191
- await fs_1.promises.unlink(pidFile);
67
+ await this.startProcess(processInfo);
68
+ return processInfo;
192
69
  }
193
70
  catch (error) {
194
- // PID file might not exist, ignore
195
- }
196
- }
197
- parseCommand(script) {
198
- // Handle different script formats
199
- if (script.includes(' ')) {
200
- const parts = script.split(' ');
201
- return {
202
- command: parts[0],
203
- args: parts.slice(1)
204
- };
205
- }
206
- // Handle file extensions
207
- if (script.endsWith('.js') || script.endsWith('.mjs') || script.endsWith('.cjs')) {
208
- return {
209
- command: 'node',
210
- args: [script]
211
- };
212
- }
213
- if (script.endsWith('.ts') || script.endsWith('.mts') || script.endsWith('.cts')) {
214
- return {
215
- command: 'npx',
216
- args: ['ts-node', script]
217
- };
71
+ processInfo.status = 'error';
72
+ processInfo.lastError = error.message;
73
+ this.processes.set(id, processInfo);
74
+ this.saveConfig();
75
+ throw error;
218
76
  }
219
- return {
220
- command: script,
221
- args: []
222
- };
223
77
  }
224
78
  async startProcess(processInfo) {
225
- const { config } = processInfo;
226
- try {
227
- processInfo.status = 'launching';
228
- processInfo.started_at = new Date();
229
- // Setup logging
230
- this.setupLogStreams(processInfo);
231
- // Parse command
232
- const { command, args } = this.parseCommand(config.script);
233
- const finalArgs = [...args, ...(config.args || [])];
234
- // Setup environment
235
- const env = {
236
- ...process.env,
237
- ...config.env,
238
- NEEX_PROCESS_ID: processInfo.id,
239
- NEEX_PROCESS_NAME: processInfo.name,
240
- NODE_ENV: process.env.NODE_ENV || 'production'
241
- };
242
- // Spawn process
243
- const childProcess = (0, child_process_1.spawn)(command, finalArgs, {
244
- cwd: config.cwd || process.cwd(),
245
- env,
246
- stdio: ['ignore', 'pipe', 'pipe'],
247
- detached: true // Detach the process to run independently
248
- });
249
- if (!childProcess.pid) {
250
- throw new Error(`Failed to start process: ${config.script}`);
251
- }
252
- processInfo.process = childProcess;
253
- processInfo.pid = childProcess.pid;
254
- processInfo.status = 'online';
255
- // Allow the parent process to exit independently of the child
256
- childProcess.unref();
257
- // Write PID file
258
- await this.writePidFile(processInfo);
259
- console.log(chalk_1.default.green(`${figures_1.default.tick} Process ${processInfo.name} (${processInfo.id}) started with PID ${processInfo.pid}`));
260
- this.emit('process:start', processInfo);
261
- // Setup logging
262
- const streams = this.logStreams.get(processInfo.id);
263
- if (childProcess.stdout && (streams === null || streams === void 0 ? void 0 : streams.out)) {
264
- childProcess.stdout.pipe(streams.out);
265
- childProcess.stdout.on('data', (data) => {
266
- var _a;
267
- const message = data.toString();
268
- if (config.time) {
269
- const timestamp = new Date().toISOString();
270
- (_a = streams.out) === null || _a === void 0 ? void 0 : _a.write(`[${timestamp}] ${message}`);
271
- }
272
- this.emit('process:log', {
273
- id: processInfo.id,
274
- type: 'stdout',
275
- data: message
276
- });
277
- });
278
- }
279
- if (childProcess.stderr && (streams === null || streams === void 0 ? void 0 : streams.err)) {
280
- childProcess.stderr.pipe(streams.err);
281
- childProcess.stderr.on('data', (data) => {
282
- var _a;
283
- const message = data.toString();
284
- if (config.time) {
285
- const timestamp = new Date().toISOString();
286
- (_a = streams.err) === null || _a === void 0 ? void 0 : _a.write(`[${timestamp}] ${message}`);
287
- }
288
- this.emit('process:log', {
289
- id: processInfo.id,
290
- type: 'stderr',
291
- data: message
292
- });
293
- });
294
- }
295
- // Handle process exit
296
- childProcess.on('exit', async (code, signal) => {
297
- processInfo.exit_code = code || undefined;
298
- processInfo.exit_signal = signal || undefined;
299
- processInfo.status = code === 0 ? 'stopped' : 'errored';
300
- processInfo.process = undefined;
301
- processInfo.pid = undefined;
302
- await this.removePidFile(processInfo.id);
303
- const exitMsg = signal
304
- ? `Process ${processInfo.name} killed by signal ${signal}`
305
- : `Process ${processInfo.name} exited with code ${code}`;
306
- console.log(chalk_1.default.yellow(`${figures_1.default.warning} ${exitMsg}`));
307
- this.emit('process:exit', { processInfo, code, signal });
308
- // Auto-restart if enabled and not manually stopped
309
- if (config.autorestart &&
310
- processInfo.status === 'errored' &&
311
- processInfo.restarts < (config.max_restarts || 10)) {
312
- const delay = config.restart_delay || 1000;
313
- console.log(chalk_1.default.blue(`${figures_1.default.info} Restarting ${processInfo.name} in ${delay}ms...`));
314
- setTimeout(async () => {
315
- try {
316
- await this.restart(processInfo.id);
317
- }
318
- catch (error) {
319
- console.error(chalk_1.default.red(`${figures_1.default.cross} Failed to restart ${processInfo.name}: ${error.message}`));
320
- }
321
- }, delay);
322
- }
323
- await this.saveConfig();
79
+ var _a, _b;
80
+ const child = (0, child_process_1.spawn)('node', [processInfo.path], {
81
+ stdio: ['ignore', 'pipe', 'pipe'],
82
+ env: { ...process.env, NODE_ENV: 'production' }
83
+ });
84
+ processInfo.pid = child.pid || 0;
85
+ processInfo.status = 'running';
86
+ processInfo.startTime = Date.now();
87
+ // Setup logging
88
+ const logStream = (0, fs_1.writeFileSync)(this.getLogPath(processInfo.id), '', { flag: 'a' });
89
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
90
+ const log = data.toString();
91
+ processInfo.logs.push(log);
92
+ (0, fs_1.writeFileSync)(this.getLogPath(processInfo.id), log, { flag: 'a' });
93
+ this.emit('log', { id: processInfo.id, log });
94
+ });
95
+ (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
96
+ const log = data.toString();
97
+ processInfo.logs.push(log);
98
+ (0, fs_1.writeFileSync)(this.getLogPath(processInfo.id), log, { flag: 'a' });
99
+ this.emit('error', { id: processInfo.id, log });
100
+ });
101
+ // Setup monitoring
102
+ if (processInfo.watch) {
103
+ const watcher = (0, chokidar_1.watch)(processInfo.path, {
104
+ ignored: /(^|[\/\\])\../,
105
+ persistent: true
324
106
  });
325
- // Handle process error
326
- childProcess.on('error', (error) => {
327
- processInfo.status = 'errored';
328
- console.error(chalk_1.default.red(`${figures_1.default.cross} Process ${processInfo.name} error: ${error.message}`));
329
- this.emit('process:error', { processInfo, error });
107
+ watcher.on('change', () => {
108
+ this.restart(processInfo.id);
330
109
  });
331
- await this.saveConfig();
332
- }
333
- catch (error) {
334
- processInfo.status = 'errored';
335
- console.error(chalk_1.default.red(`${figures_1.default.cross} Failed to start ${processInfo.name}: ${error.message}`));
336
- this.emit('process:error', { processInfo, error });
337
- throw error;
338
- }
339
- }
340
- startMonitoring() {
341
- if (this.isMonitoring || this.isDisposed)
342
- return;
343
- this.isMonitoring = true;
344
- this.monitoringInterval = setInterval(async () => {
345
- await this.updateProcessStats();
346
- }, 5000);
347
- }
348
- stopMonitoring() {
349
- if (this.monitoringInterval) {
350
- clearInterval(this.monitoringInterval);
351
- this.monitoringInterval = undefined;
110
+ this.watchers.set(processInfo.id, watcher);
352
111
  }
353
- this.isMonitoring = false;
354
- }
355
- async updateProcessStats() {
356
- for (const processInfo of this.processes.values()) {
357
- if (processInfo.status === 'online' && processInfo.pid) {
112
+ // Monitor process
113
+ const monitorInterval = setInterval(() => {
114
+ if (child.pid) {
358
115
  try {
359
- // Check if process is still running
360
- const isRunning = await this.isProcessRunning(processInfo.pid);
361
- if (!isRunning) {
362
- processInfo.status = 'stopped';
363
- processInfo.process = undefined;
364
- processInfo.pid = undefined;
365
- await this.removePidFile(processInfo.id);
366
- continue;
367
- }
368
- // Update uptime
369
- if (processInfo.started_at) {
370
- processInfo.uptime = Math.floor((Date.now() - processInfo.started_at.getTime()) / 1000);
371
- }
372
- // Get memory and CPU usage (Unix/Linux only)
373
- if (process.platform !== 'win32') {
374
- try {
375
- const stats = await this.getProcessStats(processInfo.pid);
376
- processInfo.memory = stats.memory;
377
- processInfo.cpu = stats.cpu;
378
- }
379
- catch (error) {
380
- // Process might have died
381
- }
382
- }
116
+ const stats = process.memoryUsage();
117
+ processInfo.memory = stats.heapUsed / 1024 / 1024; // MB
118
+ processInfo.uptime = (Date.now() - processInfo.startTime) / 1000;
119
+ this.emit('stats', { id: processInfo.id, stats: processInfo });
383
120
  }
384
121
  catch (error) {
385
- // Process monitoring error
122
+ console.error(`Error monitoring process ${processInfo.id}:`, error);
386
123
  }
387
124
  }
388
- }
389
- }
390
- async getProcessStats(pid) {
391
- return new Promise((resolve, reject) => {
392
- (0, child_process_1.exec)(`ps -o pid,rss,pcpu -p ${pid}`, (error, stdout) => {
393
- if (error) {
394
- resolve({ memory: 0, cpu: 0 });
395
- return;
125
+ }, 1000);
126
+ child.on('exit', async (code) => {
127
+ clearInterval(monitorInterval);
128
+ if (code !== 0) {
129
+ processInfo.status = 'error';
130
+ processInfo.lastError = `Process exited with code ${code}`;
131
+ this.emit('error', { id: processInfo.id, code });
132
+ // Auto-restart logic
133
+ if (processInfo.restarts < processInfo.maxRestarts) {
134
+ processInfo.status = 'restarting';
135
+ this.emit('restart', { id: processInfo.id, attempt: processInfo.restarts + 1 });
136
+ await new Promise(resolve => setTimeout(resolve, processInfo.restartDelay));
137
+ processInfo.restarts++;
138
+ await this.startProcess(processInfo);
396
139
  }
397
- const lines = stdout.trim().split('\n');
398
- if (lines.length < 2) {
399
- resolve({ memory: 0, cpu: 0 });
400
- return;
401
- }
402
- const stats = lines[1].trim().split(/\s+/);
403
- const memory = parseInt(stats[1]) * 1024; // Convert KB to bytes
404
- const cpu = parseFloat(stats[2]);
405
- resolve({ memory, cpu });
406
- });
407
- });
408
- }
409
- // Public API methods
410
- async start(config) {
411
- const id = this.generateId(config.name);
412
- const processInfo = {
413
- id,
414
- name: config.name,
415
- status: 'stopped',
416
- uptime: 0,
417
- restarts: 0,
418
- memory: 0,
419
- cpu: 0,
420
- created_at: new Date(),
421
- config: { ...config, id }
422
- };
423
- this.processes.set(id, processInfo);
424
- await this.startProcess(processInfo);
425
- return id;
426
- }
427
- async stop(id) {
428
- const processInfo = this.processes.get(id);
429
- if (!processInfo) {
430
- throw new Error(`Process ${id} not found`);
431
- }
432
- if (processInfo.status === 'stopped') {
433
- console.log(chalk_1.default.yellow(`${figures_1.default.info} Process ${id} is already stopped`));
434
- return;
435
- }
436
- processInfo.status = 'stopping';
437
- console.log(chalk_1.default.blue(`${figures_1.default.info} Stopping process ${processInfo.name} (${id})...`));
438
- if (processInfo.process && processInfo.pid) {
439
- try {
440
- // Try graceful shutdown first
441
- processInfo.process.kill('SIGTERM');
442
- // Wait for graceful shutdown
443
- await new Promise(resolve => setTimeout(resolve, 2000));
444
- // Force kill if still running
445
- if (processInfo.process && !processInfo.process.killed) {
446
- processInfo.process.kill('SIGKILL');
140
+ else {
141
+ this.emit('max-restarts', { id: processInfo.id, maxRestarts: processInfo.maxRestarts });
447
142
  }
448
143
  }
449
- catch (error) {
450
- // Process might already be dead
144
+ else {
145
+ processInfo.status = 'stopped';
451
146
  }
452
- }
453
- processInfo.status = 'stopped';
454
- processInfo.process = undefined;
455
- processInfo.pid = undefined;
456
- await this.removePidFile(id);
457
- this.closeLogStreams(id);
458
- await this.saveConfig();
459
- console.log(chalk_1.default.green(`${figures_1.default.tick} Process ${processInfo.name} (${id}) stopped`));
460
- this.emit('process:stop', processInfo);
461
- }
462
- async restart(id) {
463
- const processInfo = this.processes.get(id);
464
- if (!processInfo) {
465
- throw new Error(`Process ${id} not found`);
466
- }
467
- console.log(chalk_1.default.blue(`${figures_1.default.info} Restarting process ${processInfo.name} (${id})...`));
468
- if (processInfo.status === 'online') {
469
- await this.stop(id);
470
- // Wait a bit before restarting
471
- await new Promise(resolve => setTimeout(resolve, 1000));
472
- }
473
- processInfo.restarts++;
474
- processInfo.last_restart = new Date();
475
- await this.startProcess(processInfo);
147
+ this.saveConfig();
148
+ });
149
+ this.processes.set(processInfo.id, processInfo);
150
+ this.saveConfig();
151
+ this.emit('start', { id: processInfo.id, processInfo });
476
152
  }
477
- async delete(id) {
153
+ async stopx(id) {
478
154
  const processInfo = this.processes.get(id);
479
155
  if (!processInfo) {
480
156
  throw new Error(`Process ${id} not found`);
481
157
  }
482
- if (processInfo.status === 'online') {
483
- await this.stop(id);
484
- }
485
- // Remove log files
486
158
  try {
487
- if (processInfo.config.out_file) {
488
- await fs_1.promises.unlink(processInfo.config.out_file);
489
- }
490
- if (processInfo.config.error_file) {
491
- await fs_1.promises.unlink(processInfo.config.error_file);
159
+ process.kill(processInfo.pid);
160
+ const watcher = this.watchers.get(id);
161
+ if (watcher) {
162
+ watcher.close();
163
+ this.watchers.delete(id);
492
164
  }
165
+ this.processes.delete(id);
166
+ this.saveConfig();
167
+ this.emit('stop', { id });
493
168
  }
494
169
  catch (error) {
495
- // Log files might not exist
170
+ throw new Error(`Failed to stop process ${id}: ${error.message}`);
496
171
  }
497
- this.closeLogStreams(id);
498
- this.processes.delete(id);
499
- await this.saveConfig();
500
- console.log(chalk_1.default.green(`${figures_1.default.tick} Process ${processInfo.name} (${id}) deleted`));
501
- this.emit('process:delete', processInfo);
502
172
  }
503
- async list() {
173
+ list() {
504
174
  return Array.from(this.processes.values());
505
175
  }
506
- async logs(id, lines = 15) {
176
+ monit(id) {
507
177
  const processInfo = this.processes.get(id);
508
178
  if (!processInfo) {
509
179
  throw new Error(`Process ${id} not found`);
510
180
  }
511
- return this.readLogFiles(processInfo.config, lines);
512
- }
513
- async readLogFiles(config, lines) {
514
- const logs = [];
515
- const readLastLines = async (filePath) => {
516
- try {
517
- const content = await fs_1.promises.readFile(filePath, 'utf-8');
518
- const fileLines = content.split('\n').filter(line => line.trim() !== '');
519
- return fileLines.slice(-lines);
520
- }
521
- catch (error) {
522
- return [];
523
- }
181
+ return {
182
+ ...processInfo,
183
+ logs: processInfo.logs.slice(-100) // Last 100 logs
524
184
  };
525
- if (config.out_file) {
526
- const outLogs = await readLastLines(config.out_file);
527
- logs.push(...outLogs.map(line => `[OUT] ${line}`));
528
- }
529
- if (config.error_file) {
530
- const errLogs = await readLastLines(config.error_file);
531
- logs.push(...errLogs.map(line => `[ERR] ${line}`));
532
- }
533
- return logs.slice(-lines);
534
185
  }
535
- followLogs(id, onLogEntry) {
186
+ log(id, lines = 100) {
536
187
  const processInfo = this.processes.get(id);
537
188
  if (!processInfo) {
538
- logger_js_1.default.printLine(`Process ${id} not found for log following.`, 'error');
539
- return undefined;
189
+ throw new Error(`Process ${id} not found`);
540
190
  }
541
- const { config } = processInfo;
542
- const logFilesToWatch = [];
543
- // Resolve log file paths, defaulting if not specified (consistent with setupLogStreams)
544
- const outFile = config.out_file || path.join(this.logDir, `${processInfo.id}-out.log`);
545
- const errFile = config.error_file || path.join(this.logDir, `${processInfo.id}-error.log`);
546
- logFilesToWatch.push({ type: 'OUT', path: outFile });
547
- if (errFile !== outFile) { // Avoid watching the same file path twice
548
- logFilesToWatch.push({ type: 'ERR', path: errFile });
191
+ const logPath = this.getLogPath(id);
192
+ if (!(0, fs_1.existsSync)(logPath)) {
193
+ return [];
549
194
  }
550
- const lastReadSizes = {};
551
- const setupWatcherForFile = (filePath, type) => {
552
- try {
553
- const stats = fs.existsSync(filePath) ? fs.statSync(filePath) : { size: 0 };
554
- lastReadSizes[filePath] = stats.size;
555
- }
556
- catch (err) {
557
- lastReadSizes[filePath] = 0;
558
- // logger.printLine(`Could not get initial stats for ${filePath}: ${(err as Error).message}. Assuming size 0.`, 'debug');
559
- }
560
- const listener = (curr, prev) => {
561
- if (curr.mtimeMs <= prev.mtimeMs && curr.size === prev.size) {
562
- return;
563
- }
564
- const newSize = curr.size;
565
- let oldSize = lastReadSizes[filePath];
566
- // Fallback for oldSize, though it should be initialized
567
- if (typeof oldSize !== 'number')
568
- oldSize = 0;
569
- if (newSize > oldSize) {
570
- const stream = fs.createReadStream(filePath, {
571
- start: oldSize,
572
- end: newSize - 1,
573
- encoding: 'utf-8',
574
- });
575
- stream.on('data', (chunk) => {
576
- chunk.split('\n').forEach(line => {
577
- if (line.trim() !== '') {
578
- onLogEntry(`[${type}] ${line}`);
579
- }
580
- });
581
- });
582
- stream.on('error', (streamErr) => {
583
- logger_js_1.default.printLine(`Error reading log chunk for ${filePath}: ${streamErr.message}`, 'error');
584
- });
585
- lastReadSizes[filePath] = newSize;
586
- }
587
- else if (newSize < oldSize) {
588
- // File was truncated or replaced
589
- // logger.printLine(`Log file ${filePath} was truncated. Reading from start.`, 'debug');
590
- lastReadSizes[filePath] = 0;
591
- const stream = fs.createReadStream(filePath, {
592
- start: 0,
593
- end: newSize - 1,
594
- encoding: 'utf-8',
595
- });
596
- stream.on('data', (chunk) => {
597
- chunk.split('\n').forEach(line => {
598
- if (line.trim() !== '') {
599
- onLogEntry(`[${type}] ${line}`);
600
- }
601
- });
602
- });
603
- stream.on('error', (streamErr) => {
604
- logger_js_1.default.printLine(`Error reading truncated log file ${filePath}: ${streamErr.message}`, 'error');
605
- });
606
- lastReadSizes[filePath] = newSize;
607
- }
608
- };
609
- try {
610
- fs.watchFile(filePath, { persistent: true, interval: 500 }, listener);
611
- }
612
- catch (watchError) {
613
- logger_js_1.default.printLine(`Failed to watch log file ${filePath}: ${watchError.message}`, 'error');
614
- }
615
- };
616
- logFilesToWatch.forEach(fileToWatch => {
617
- if (fileToWatch.path) { // Ensure path is defined
618
- setupWatcherForFile(fileToWatch.path, fileToWatch.type);
619
- }
620
- });
621
- const stop = () => {
622
- logFilesToWatch.forEach(fileToWatch => {
623
- if (fileToWatch.path) { // Ensure path is defined before unwatching
624
- try {
625
- fs.unwatchFile(fileToWatch.path);
626
- }
627
- catch (unwatchError) {
628
- // logger.printLine(`Error unwatching ${fileToWatch.path}: ${(unwatchError as Error).message}`, 'debug');
629
- }
630
- }
631
- });
632
- };
633
- return { stop };
634
- }
635
- async save() {
636
- await this.saveConfig();
637
- console.log(chalk_1.default.green(`${figures_1.default.tick} Process configuration saved`));
638
- }
639
- async stopAll() {
640
- const promises = Array.from(this.processes.keys()).map(id => this.stop(id));
641
- await Promise.all(promises);
642
- }
643
- async restartAll() {
644
- const promises = Array.from(this.processes.keys()).map(id => this.restart(id));
645
- await Promise.all(promises);
646
- }
647
- async deleteAll() {
648
- await this.stopAll();
649
- this.processes.clear();
650
- await this.saveConfig();
651
- console.log(chalk_1.default.green(`${figures_1.default.tick} All processes deleted`));
652
- }
653
- getProcess(id) {
654
- return this.processes.get(id);
195
+ const logs = (0, fs_1.readFileSync)(logPath, 'utf-8').split('\n');
196
+ return logs.slice(-lines);
655
197
  }
656
- async dispose() {
657
- if (this.isDisposed)
658
- return;
659
- this.isDisposed = true;
660
- this.stopMonitoring();
661
- // Close all log streams
662
- for (const id of this.logStreams.keys()) {
663
- this.closeLogStreams(id);
198
+ async restart(id) {
199
+ const processInfo = this.processes.get(id);
200
+ if (!processInfo) {
201
+ throw new Error(`Process ${id} not found`);
664
202
  }
665
- // Stop all processes
666
- await this.stopAll();
203
+ await this.stopx(id);
204
+ processInfo.restarts++;
205
+ await this.startx(processInfo.path, { name: processInfo.name, watch: true });
667
206
  }
668
207
  }
669
- exports.ProcessManager = ProcessManager;
208
+ exports.processManager = new ProcessManager();