import * as plugins from './plugins.js'; import * as paths from './paths.js'; import { ProcessMonitor, type IMonitorConfig } from './classes.processmonitor.js'; import { TspmConfig } from './classes.config.js'; export interface IProcessConfig extends IMonitorConfig { id: string; // Unique identifier for the process autorestart: boolean; // Whether to restart the process automatically on crash watch?: boolean; // Whether to watch for file changes and restart watchPaths?: string[]; // Paths to watch for changes } export interface IProcessInfo { id: string; pid?: number; status: 'online' | 'stopped' | 'errored'; memory: number; cpu?: number; uptime?: number; restarts: number; } export interface IProcessLog { timestamp: Date; type: 'stdout' | 'stderr' | 'system'; message: string; } export class Tspm { private processes: Map = new Map(); private processConfigs: Map = new Map(); private processInfo: Map = new Map(); private config: TspmConfig; private configStorageKey = 'processes'; constructor() { this.config = new TspmConfig(); this.loadProcessConfigs(); } /** * Start a new process with the given configuration */ public async start(config: IProcessConfig): Promise { // Check if process with this id already exists if (this.processes.has(config.id)) { throw new Error(`Process with id '${config.id}' already exists`); } // Create and store process config this.processConfigs.set(config.id, config); // Initialize process info this.processInfo.set(config.id, { id: config.id, status: 'stopped', memory: 0, restarts: 0 }); // Create and start process monitor const monitor = new ProcessMonitor({ name: config.name || config.id, projectDir: config.projectDir, command: config.command, args: config.args, memoryLimitBytes: config.memoryLimitBytes, monitorIntervalMs: config.monitorIntervalMs }); this.processes.set(config.id, monitor); monitor.start(); // Update process info this.updateProcessInfo(config.id, { status: 'online' }); // Save updated configs await this.saveProcessConfigs(); } /** * Stop a process by id */ public async stop(id: string): Promise { const monitor = this.processes.get(id); if (!monitor) { throw new Error(`Process with id '${id}' not found`); } monitor.stop(); this.updateProcessInfo(id, { status: 'stopped' }); // Don't remove from the maps, just mark as stopped // This allows it to be restarted later } /** * Restart a process by id */ public async restart(id: string): Promise { const monitor = this.processes.get(id); const config = this.processConfigs.get(id); if (!monitor || !config) { throw new Error(`Process with id '${id}' not found`); } // Stop and then start the process monitor.stop(); // Create a new monitor instance const newMonitor = new ProcessMonitor({ name: config.name || config.id, projectDir: config.projectDir, command: config.command, args: config.args, memoryLimitBytes: config.memoryLimitBytes, monitorIntervalMs: config.monitorIntervalMs }); this.processes.set(id, newMonitor); newMonitor.start(); // Update restart count const info = this.processInfo.get(id); if (info) { this.updateProcessInfo(id, { status: 'online', restarts: info.restarts + 1 }); } } /** * Delete a process by id */ public async delete(id: string): Promise { // Stop the process if it's running try { await this.stop(id); } catch (error) { // Ignore errors if the process is not running } // Remove from all maps this.processes.delete(id); this.processConfigs.delete(id); this.processInfo.delete(id); // Save updated configs await this.saveProcessConfigs(); } /** * Get a list of all process infos */ public list(): IProcessInfo[] { return Array.from(this.processInfo.values()); } /** * Get detailed info for a specific process */ public describe(id: string): { config: IProcessConfig; info: IProcessInfo } | null { const config = this.processConfigs.get(id); const info = this.processInfo.get(id); if (!config || !info) { return null; } return { config, info }; } /** * Get process logs */ public getLogs(id: string, limit?: number): IProcessLog[] { const monitor = this.processes.get(id); if (!monitor) { return []; } return monitor.getLogs(limit); } /** * Start all saved processes */ public async startAll(): Promise { for (const [id, config] of this.processConfigs.entries()) { if (!this.processes.has(id)) { await this.start(config); } } } /** * Stop all running processes */ public async stopAll(): Promise { for (const id of this.processes.keys()) { await this.stop(id); } } /** * Restart all processes */ public async restartAll(): Promise { for (const id of this.processes.keys()) { await this.restart(id); } } /** * Update the info for a process */ private updateProcessInfo(id: string, update: Partial): void { const info = this.processInfo.get(id); if (info) { this.processInfo.set(id, { ...info, ...update }); } } /** * Save all process configurations to config storage */ private async saveProcessConfigs(): Promise { const configs = Array.from(this.processConfigs.values()); await this.config.writeKey(this.configStorageKey, JSON.stringify(configs)); } /** * Load process configurations from config storage */ private async loadProcessConfigs(): Promise { try { const configsJson = await this.config.readKey(this.configStorageKey); if (configsJson) { const configs = JSON.parse(configsJson) as IProcessConfig[]; for (const config of configs) { this.processConfigs.set(config.id, config); // Initialize process info this.processInfo.set(config.id, { id: config.id, status: 'stopped', memory: 0, restarts: 0 }); } } } catch (error) { // If no configs found or error reading, just continue with empty configs console.log('No saved process configurations found'); } } }