feat(core): Introduced process management features using ProcessWrapper and enhanced configuration.
This commit is contained in:
		@@ -3,6 +3,6 @@
 | 
			
		||||
 */
 | 
			
		||||
export const commitinfo = {
 | 
			
		||||
  name: '@git.zone/tspm',
 | 
			
		||||
  version: '1.3.1',
 | 
			
		||||
  version: '1.4.0',
 | 
			
		||||
  description: 'a no fuzz process manager'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								ts/classes.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								ts/classes.config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
import * as plugins from './plugins.js';
 | 
			
		||||
 | 
			
		||||
export class TspmConfig {
 | 
			
		||||
  public npmextraInstance = new plugins.npmextra.KeyValueStore({
 | 
			
		||||
    identityArg: '@git.zone__tspm',
 | 
			
		||||
    typeArg: 'userHomeDir',
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  public async readKey(keyArg: string): Promise<string> {
 | 
			
		||||
    return await this.npmextraInstance.readKey(keyArg);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async writeKey(keyArg: string, value: string): Promise<void> {
 | 
			
		||||
    return await this.npmextraInstance.writeKey(keyArg, value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async deleteKey(keyArg: string): Promise<void> {
 | 
			
		||||
    return await this.npmextraInstance.deleteKey(keyArg);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import * as plugins from './plugins.js';
 | 
			
		||||
import { ProcessWrapper } from './classes.processwrapper.js';
 | 
			
		||||
 | 
			
		||||
export interface IMonitorConfig {
 | 
			
		||||
  name?: string;                 // Optional name to identify the instance
 | 
			
		||||
@@ -7,13 +8,16 @@ export interface IMonitorConfig {
 | 
			
		||||
  args?: string[];               // Optional: arguments for the command
 | 
			
		||||
  memoryLimitBytes: number;      // Maximum allowed memory (in bytes) for the process group
 | 
			
		||||
  monitorIntervalMs?: number;    // Interval (in ms) at which memory is checked (default: 5000)
 | 
			
		||||
  env?: NodeJS.ProcessEnv;       // Optional: custom environment variables
 | 
			
		||||
  logBufferSize?: number;        // Optional: number of log lines to keep (default: 100)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ProcessMonitor {
 | 
			
		||||
  private child: plugins.childProcess.ChildProcess | null = null;
 | 
			
		||||
  private processWrapper: ProcessWrapper | null = null;
 | 
			
		||||
  private config: IMonitorConfig;
 | 
			
		||||
  private intervalId: NodeJS.Timeout | null = null;
 | 
			
		||||
  private stopped: boolean = true; // Initially stopped until start() is called
 | 
			
		||||
  private restartCount: number = 0;
 | 
			
		||||
 | 
			
		||||
  constructor(config: IMonitorConfig) {
 | 
			
		||||
    this.config = config;
 | 
			
		||||
@@ -23,59 +27,64 @@ export class ProcessMonitor {
 | 
			
		||||
    // Reset the stopped flag so that new processes can spawn.
 | 
			
		||||
    this.stopped = false;
 | 
			
		||||
    this.log(`Starting process monitor.`);
 | 
			
		||||
    this.spawnChild();
 | 
			
		||||
    this.spawnProcess();
 | 
			
		||||
 | 
			
		||||
    // Set the monitoring interval.
 | 
			
		||||
    const interval = this.config.monitorIntervalMs || 5000;
 | 
			
		||||
    this.intervalId = setInterval(() => {
 | 
			
		||||
      if (this.child && this.child.pid) {
 | 
			
		||||
        this.monitorProcessGroup(this.child.pid, this.config.memoryLimitBytes);
 | 
			
		||||
      if (this.processWrapper && this.processWrapper.getPid()) {
 | 
			
		||||
        this.monitorProcessGroup(this.processWrapper.getPid()!, this.config.memoryLimitBytes);
 | 
			
		||||
      }
 | 
			
		||||
    }, interval);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private spawnChild(): void {
 | 
			
		||||
  private spawnProcess(): void {
 | 
			
		||||
    // Don't spawn if the monitor has been stopped.
 | 
			
		||||
    if (this.stopped) return;
 | 
			
		||||
 | 
			
		||||
    if (this.config.args && this.config.args.length > 0) {
 | 
			
		||||
      this.log(
 | 
			
		||||
        `Spawning command "${this.config.command}" with args [${this.config.args.join(
 | 
			
		||||
          ', '
 | 
			
		||||
        )}] in directory: ${this.config.projectDir}`
 | 
			
		||||
      );
 | 
			
		||||
      this.child = plugins.childProcess.spawn(this.config.command, this.config.args, {
 | 
			
		||||
        cwd: this.config.projectDir,
 | 
			
		||||
        detached: true,
 | 
			
		||||
        stdio: 'inherit',
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      this.log(
 | 
			
		||||
        `Spawning command "${this.config.command}" in directory: ${this.config.projectDir}`
 | 
			
		||||
      );
 | 
			
		||||
      // Use shell mode to allow a full command string.
 | 
			
		||||
      this.child = plugins.childProcess.spawn(this.config.command, {
 | 
			
		||||
        cwd: this.config.projectDir,
 | 
			
		||||
        detached: true,
 | 
			
		||||
        stdio: 'inherit',
 | 
			
		||||
        shell: true,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    // Create a new process wrapper
 | 
			
		||||
    this.processWrapper = new ProcessWrapper({
 | 
			
		||||
      name: this.config.name || 'unnamed-process',
 | 
			
		||||
      command: this.config.command,
 | 
			
		||||
      args: this.config.args,
 | 
			
		||||
      cwd: this.config.projectDir,
 | 
			
		||||
      env: this.config.env,
 | 
			
		||||
      logBuffer: this.config.logBufferSize,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.log(`Spawned process with PID ${this.child.pid}`);
 | 
			
		||||
 | 
			
		||||
    // When the child process exits, restart it if the monitor isn't stopped.
 | 
			
		||||
    this.child.on('exit', (code, signal) => {
 | 
			
		||||
      this.log(`Child process exited with code ${code}, signal ${signal}.`);
 | 
			
		||||
      if (!this.stopped) {
 | 
			
		||||
        this.log('Restarting process...');
 | 
			
		||||
        this.spawnChild();
 | 
			
		||||
    // Set up event handlers
 | 
			
		||||
    this.processWrapper.on('log', (log) => {
 | 
			
		||||
      // Here we could add handlers to send logs somewhere
 | 
			
		||||
      // For now, we just log system messages to the console
 | 
			
		||||
      if (log.type === 'system') {
 | 
			
		||||
        this.log(log.message);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.processWrapper.on('exit', (code, signal) => {
 | 
			
		||||
      this.log(`Process exited with code ${code}, signal ${signal}.`);
 | 
			
		||||
      if (!this.stopped) {
 | 
			
		||||
        this.log('Restarting process...');
 | 
			
		||||
        this.restartCount++;
 | 
			
		||||
        this.spawnProcess();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.processWrapper.on('error', (error) => {
 | 
			
		||||
      this.log(`Process error: ${error.message}`);
 | 
			
		||||
      if (!this.stopped) {
 | 
			
		||||
        this.log('Restarting process due to error...');
 | 
			
		||||
        this.restartCount++;
 | 
			
		||||
        this.spawnProcess();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Start the process
 | 
			
		||||
    this.processWrapper.start();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Monitor the process group’s memory usage. If the total memory exceeds the limit,
 | 
			
		||||
   * Monitor the process group's memory usage. If the total memory exceeds the limit,
 | 
			
		||||
   * kill the process group so that the 'exit' handler can restart it.
 | 
			
		||||
   */
 | 
			
		||||
  private async monitorProcessGroup(pid: number, memoryLimit: number): Promise<void> {
 | 
			
		||||
@@ -92,8 +101,10 @@ export class ProcessMonitor {
 | 
			
		||||
            memoryUsage
 | 
			
		||||
          )} exceeds limit of ${this.humanReadableBytes(memoryLimit)}. Restarting process.`
 | 
			
		||||
        );
 | 
			
		||||
        // Kill the entire process group by sending a signal to -PID.
 | 
			
		||||
        process.kill(-pid, 'SIGKILL');
 | 
			
		||||
        // Stop the process wrapper, which will trigger the exit handler and restart
 | 
			
		||||
        if (this.processWrapper) {
 | 
			
		||||
          this.processWrapper.stop();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      this.log('Error monitoring process group: ' + error);
 | 
			
		||||
@@ -142,10 +153,48 @@ export class ProcessMonitor {
 | 
			
		||||
    if (this.intervalId) {
 | 
			
		||||
      clearInterval(this.intervalId);
 | 
			
		||||
    }
 | 
			
		||||
    if (this.child && this.child.pid) {
 | 
			
		||||
      process.kill(-this.child.pid, 'SIGKILL');
 | 
			
		||||
    if (this.processWrapper) {
 | 
			
		||||
      this.processWrapper.stop();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the current logs from the process
 | 
			
		||||
   */
 | 
			
		||||
  public getLogs(limit?: number): Array<{ timestamp: Date, type: string, message: string }> {
 | 
			
		||||
    if (!this.processWrapper) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
    return this.processWrapper.getLogs(limit);
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the number of times the process has been restarted
 | 
			
		||||
   */
 | 
			
		||||
  public getRestartCount(): number {
 | 
			
		||||
    return this.restartCount;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the process ID if running
 | 
			
		||||
   */
 | 
			
		||||
  public getPid(): number | null {
 | 
			
		||||
    return this.processWrapper?.getPid() || null;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Get process uptime in milliseconds
 | 
			
		||||
   */
 | 
			
		||||
  public getUptime(): number {
 | 
			
		||||
    return this.processWrapper?.getUptime() || 0;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Check if the process is currently running
 | 
			
		||||
   */
 | 
			
		||||
  public isRunning(): boolean {
 | 
			
		||||
    return this.processWrapper?.isRunning() || false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method for logging messages with the instance name.
 | 
			
		||||
@@ -154,4 +203,4 @@ export class ProcessMonitor {
 | 
			
		||||
    const prefix = this.config.name ? `[${this.config.name}] ` : '';
 | 
			
		||||
    console.log(prefix + message);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										207
									
								
								ts/classes.processwrapper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								ts/classes.processwrapper.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,207 @@
 | 
			
		||||
import * as plugins from './plugins.js';
 | 
			
		||||
import { EventEmitter } from 'events';
 | 
			
		||||
 | 
			
		||||
export interface IProcessWrapperOptions {
 | 
			
		||||
  command: string;
 | 
			
		||||
  args?: string[];
 | 
			
		||||
  cwd: string;
 | 
			
		||||
  env?: NodeJS.ProcessEnv;
 | 
			
		||||
  name: string;
 | 
			
		||||
  logBuffer?: number; // Number of log lines to keep in memory (default: 100)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IProcessLog {
 | 
			
		||||
  timestamp: Date;
 | 
			
		||||
  type: 'stdout' | 'stderr' | 'system';
 | 
			
		||||
  message: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ProcessWrapper extends EventEmitter {
 | 
			
		||||
  private process: plugins.childProcess.ChildProcess | null = null;
 | 
			
		||||
  private options: IProcessWrapperOptions;
 | 
			
		||||
  private logs: IProcessLog[] = [];
 | 
			
		||||
  private logBufferSize: number;
 | 
			
		||||
  private startTime: Date | null = null;
 | 
			
		||||
  
 | 
			
		||||
  constructor(options: IProcessWrapperOptions) {
 | 
			
		||||
    super();
 | 
			
		||||
    this.options = options;
 | 
			
		||||
    this.logBufferSize = options.logBuffer || 100;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Start the wrapped process
 | 
			
		||||
   */
 | 
			
		||||
  public start(): void {
 | 
			
		||||
    this.addSystemLog('Starting process...');
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      if (this.options.args && this.options.args.length > 0) {
 | 
			
		||||
        this.process = plugins.childProcess.spawn(this.options.command, this.options.args, {
 | 
			
		||||
          cwd: this.options.cwd,
 | 
			
		||||
          env: this.options.env || process.env,
 | 
			
		||||
          stdio: ['ignore', 'pipe', 'pipe'], // We need to pipe stdout and stderr
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        // Use shell mode to allow a full command string
 | 
			
		||||
        this.process = plugins.childProcess.spawn(this.options.command, {
 | 
			
		||||
          cwd: this.options.cwd,
 | 
			
		||||
          env: this.options.env || process.env,
 | 
			
		||||
          stdio: ['ignore', 'pipe', 'pipe'], // We need to pipe stdout and stderr
 | 
			
		||||
          shell: true,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      this.startTime = new Date();
 | 
			
		||||
      
 | 
			
		||||
      // Handle process exit
 | 
			
		||||
      this.process.on('exit', (code, signal) => {
 | 
			
		||||
        this.addSystemLog(`Process exited with code ${code}, signal ${signal}`);
 | 
			
		||||
        this.emit('exit', code, signal);
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      // Handle errors
 | 
			
		||||
      this.process.on('error', (error) => {
 | 
			
		||||
        this.addSystemLog(`Process error: ${error.message}`);
 | 
			
		||||
        this.emit('error', error);
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      // Capture stdout
 | 
			
		||||
      if (this.process.stdout) {
 | 
			
		||||
        this.process.stdout.on('data', (data) => {
 | 
			
		||||
          const lines = data.toString().split('\n');
 | 
			
		||||
          for (const line of lines) {
 | 
			
		||||
            if (line.trim()) {
 | 
			
		||||
              this.addLog('stdout', line);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Capture stderr
 | 
			
		||||
      if (this.process.stderr) {
 | 
			
		||||
        this.process.stderr.on('data', (data) => {
 | 
			
		||||
          const lines = data.toString().split('\n');
 | 
			
		||||
          for (const line of lines) {
 | 
			
		||||
            if (line.trim()) {
 | 
			
		||||
              this.addLog('stderr', line);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      this.addSystemLog(`Process started with PID ${this.process.pid}`);
 | 
			
		||||
      this.emit('start', this.process.pid);
 | 
			
		||||
      
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      this.addSystemLog(`Failed to start process: ${error.message}`);
 | 
			
		||||
      this.emit('error', error);
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Stop the wrapped process
 | 
			
		||||
   */
 | 
			
		||||
  public stop(): void {
 | 
			
		||||
    if (!this.process) {
 | 
			
		||||
      this.addSystemLog('No process running');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    this.addSystemLog('Stopping process...');
 | 
			
		||||
    
 | 
			
		||||
    // First try SIGTERM for graceful shutdown
 | 
			
		||||
    if (this.process.pid) {
 | 
			
		||||
      try {
 | 
			
		||||
        process.kill(this.process.pid, 'SIGTERM');
 | 
			
		||||
        
 | 
			
		||||
        // Give it 5 seconds to shut down gracefully
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          if (this.process && this.process.pid) {
 | 
			
		||||
            this.addSystemLog('Process did not exit gracefully, force killing...');
 | 
			
		||||
            try {
 | 
			
		||||
              process.kill(this.process.pid, 'SIGKILL');
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
              // Process might have exited between checks
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }, 5000);
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        this.addSystemLog(`Error stopping process: ${error.message}`);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the process ID if running
 | 
			
		||||
   */
 | 
			
		||||
  public getPid(): number | null {
 | 
			
		||||
    return this.process?.pid || null;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the current logs
 | 
			
		||||
   */
 | 
			
		||||
  public getLogs(limit: number = this.logBufferSize): IProcessLog[] {
 | 
			
		||||
    // Return the most recent logs up to the limit
 | 
			
		||||
    return this.logs.slice(-limit);
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Get uptime in milliseconds
 | 
			
		||||
   */
 | 
			
		||||
  public getUptime(): number {
 | 
			
		||||
    if (!this.startTime) return 0;
 | 
			
		||||
    return Date.now() - this.startTime.getTime();
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Check if the process is currently running
 | 
			
		||||
   */
 | 
			
		||||
  public isRunning(): boolean {
 | 
			
		||||
    return this.process !== null && typeof this.process.exitCode !== 'number';
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Add a log entry from stdout or stderr
 | 
			
		||||
   */
 | 
			
		||||
  private addLog(type: 'stdout' | 'stderr', message: string): void {
 | 
			
		||||
    const log: IProcessLog = {
 | 
			
		||||
      timestamp: new Date(),
 | 
			
		||||
      type,
 | 
			
		||||
      message,
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    this.logs.push(log);
 | 
			
		||||
    
 | 
			
		||||
    // Trim logs if they exceed buffer size
 | 
			
		||||
    if (this.logs.length > this.logBufferSize) {
 | 
			
		||||
      this.logs = this.logs.slice(-this.logBufferSize);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Emit log event for potential handlers
 | 
			
		||||
    this.emit('log', log);
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Add a system log entry (not from the process itself)
 | 
			
		||||
   */
 | 
			
		||||
  private addSystemLog(message: string): void {
 | 
			
		||||
    const log: IProcessLog = {
 | 
			
		||||
      timestamp: new Date(),
 | 
			
		||||
      type: 'system',
 | 
			
		||||
      message,
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    this.logs.push(log);
 | 
			
		||||
    
 | 
			
		||||
    // Trim logs if they exceed buffer size
 | 
			
		||||
    if (this.logs.length > this.logBufferSize) {
 | 
			
		||||
      this.logs = this.logs.slice(-this.logBufferSize);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Emit log event for potential handlers
 | 
			
		||||
    this.emit('log', log);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,259 @@
 | 
			
		||||
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<string, ProcessMonitor> = new Map();
 | 
			
		||||
  private processConfigs: Map<string, IProcessConfig> = new Map();
 | 
			
		||||
  private processInfo: Map<string, IProcessInfo> = 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<void> {
 | 
			
		||||
    // 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<void> {
 | 
			
		||||
    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<void> {
 | 
			
		||||
    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<void> {
 | 
			
		||||
    // 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<void> {
 | 
			
		||||
    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<void> {
 | 
			
		||||
    for (const id of this.processes.keys()) {
 | 
			
		||||
      await this.stop(id);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Restart all processes
 | 
			
		||||
   */
 | 
			
		||||
  public async restartAll(): Promise<void> {
 | 
			
		||||
    for (const id of this.processes.keys()) {
 | 
			
		||||
      await this.restart(id);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Update the info for a process
 | 
			
		||||
   */
 | 
			
		||||
  private updateProcessInfo(id: string, update: Partial<IProcessInfo>): 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<void> {
 | 
			
		||||
    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<void> {
 | 
			
		||||
    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');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -17,5 +17,13 @@ export const run = async () => {
 | 
			
		||||
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  smartcliInstance.addCommand('startAsDaemon').subscribe({
 | 
			
		||||
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  smartcliInstance.addCommand('stop').subscribe({
 | 
			
		||||
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  smartcliInstance.startParse();
 | 
			
		||||
}
 | 
			
		||||
@@ -8,11 +8,13 @@ export {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// @push.rocks scope
 | 
			
		||||
import * as npmextra from '@push.rocks/npmextra';
 | 
			
		||||
import * as projectinfo from '@push.rocks/projectinfo';
 | 
			
		||||
import * as smartpath from '@push.rocks/smartpath';
 | 
			
		||||
import * as smartcli from '@push.rocks/smartcli';
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  npmextra,
 | 
			
		||||
  projectinfo,
 | 
			
		||||
  smartpath,
 | 
			
		||||
  smartcli,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user