feat(cli): Enhance CLI with new process management commands
This commit is contained in:
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tspm',
|
||||
version: '1.4.0',
|
||||
version: '1.5.0',
|
||||
description: 'a no fuzz process manager'
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { ProcessWrapper } from './classes.processwrapper.js';
|
||||
import { ProcessWrapper, type IProcessLog } from './classes.processwrapper.js';
|
||||
|
||||
export interface IMonitorConfig {
|
||||
name?: string; // Optional name to identify the instance
|
||||
@ -161,7 +161,7 @@ export class ProcessMonitor {
|
||||
/**
|
||||
* Get the current logs from the process
|
||||
*/
|
||||
public getLogs(limit?: number): Array<{ timestamp: Date, type: string, message: string }> {
|
||||
public getLogs(limit?: number): IProcessLog[] {
|
||||
if (!this.processWrapper) {
|
||||
return [];
|
||||
}
|
||||
|
287
ts/cli.ts
287
ts/cli.ts
@ -1,29 +1,300 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { Tspm, IProcessConfig } from './classes.tspm.js';
|
||||
|
||||
export const run = async () => {
|
||||
const tspmProjectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
||||
const tspm = new Tspm();
|
||||
|
||||
const smartcliInstance = new plugins.smartcli.Smartcli();
|
||||
smartcliInstance.addVersion(tspmProjectinfo.npm.version);
|
||||
|
||||
// Default command - show help and list processes
|
||||
smartcliInstance.standardCommand().subscribe({
|
||||
next: (argvArg) => {
|
||||
console.log(`Please specify a command.`)
|
||||
next: async (argvArg) => {
|
||||
console.log(`TSPM - TypeScript Process Manager v${tspmProjectinfo.npm.version}`);
|
||||
console.log('Usage: tspm [command] [options]');
|
||||
console.log('\nCommands:');
|
||||
console.log(' start <script> Start a process');
|
||||
console.log(' startAsDaemon <script> Start a process in daemon mode');
|
||||
console.log(' list List all processes');
|
||||
console.log(' stop <id> Stop a process');
|
||||
console.log(' restart <id> Restart a process');
|
||||
console.log(' delete <id> Delete a process');
|
||||
console.log(' describe <id> Show details for a process');
|
||||
console.log('\nUse tspm [command] --help for more information about a command.');
|
||||
|
||||
// Show current process list
|
||||
console.log('\nProcess List:');
|
||||
const processes = tspm.list();
|
||||
|
||||
if (processes.length === 0) {
|
||||
console.log(' No processes running. Use "tspm start" to start a process.');
|
||||
} else {
|
||||
console.log('┌─────────┬─────────────┬───────────┬───────────┬──────────┐');
|
||||
console.log('│ ID │ Name │ Status │ Memory │ Restarts │');
|
||||
console.log('├─────────┼─────────────┼───────────┼───────────┼──────────┤');
|
||||
|
||||
for (const proc of processes) {
|
||||
console.log(`│ ${pad(proc.id, 8)} │ ${pad(proc.id, 12)} │ ${pad(proc.status, 10)} │ ${pad(formatMemory(proc.memory), 10)} │ ${pad(String(proc.restarts), 9)} │`);
|
||||
}
|
||||
|
||||
console.log('└─────────┴─────────────┴───────────┴───────────┴──────────┘');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
smartcliInstance.addCommand('restart').subscribe({
|
||||
|
||||
})
|
||||
// Start command - start a new process
|
||||
smartcliInstance.addCommand('start').subscribe({
|
||||
next: async (argvArg) => {
|
||||
const script = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
if (!script) {
|
||||
console.error('Error: Missing script argument. Usage: tspm start <script>');
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse additional options
|
||||
const name = argvArg.name || script;
|
||||
const cwd = argvArg.cwd || process.cwd();
|
||||
const memLimit = parseMemoryString(argvArg.memory || '500MB');
|
||||
|
||||
try {
|
||||
const processConfig: IProcessConfig = {
|
||||
id: argvArg.id || name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase(),
|
||||
name: name,
|
||||
projectDir: cwd,
|
||||
command: script,
|
||||
args: argvArg.args ? String(argvArg.args).split(' ') : undefined,
|
||||
memoryLimitBytes: memLimit,
|
||||
monitorIntervalMs: Number(argvArg.interval) || 5000,
|
||||
autorestart: argvArg.autorestart !== 'false',
|
||||
watch: Boolean(argvArg.watch)
|
||||
};
|
||||
|
||||
await tspm.start(processConfig);
|
||||
console.log(`Process ${processConfig.id} started successfully.`);
|
||||
} catch (error) {
|
||||
console.error(`Error starting process: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Start as daemon command
|
||||
smartcliInstance.addCommand('startAsDaemon').subscribe({
|
||||
next: async (argvArg) => {
|
||||
const script = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
if (!script) {
|
||||
console.error('Error: Missing script argument. Usage: tspm startAsDaemon <script>');
|
||||
return;
|
||||
}
|
||||
|
||||
// For daemon mode, we'll detach from the console
|
||||
const daemonProcess = plugins.childProcess.spawn(
|
||||
process.execPath,
|
||||
[
|
||||
...process.execArgv,
|
||||
process.argv[1], // The tspm script path
|
||||
'start',
|
||||
script,
|
||||
...process.argv.slice(3) // Pass other arguments
|
||||
],
|
||||
{
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
cwd: process.cwd()
|
||||
}
|
||||
);
|
||||
|
||||
// Unref to allow parent to exit
|
||||
daemonProcess.unref();
|
||||
|
||||
console.log(`Started process ${script} as daemon.`);
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
// Stop command
|
||||
smartcliInstance.addCommand('stop').subscribe({
|
||||
next: async (argvArg) => {
|
||||
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
|
||||
if (!id) {
|
||||
console.error('Error: Missing process ID. Usage: tspm stop <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await tspm.stop(id);
|
||||
console.log(`Process ${id} stopped.`);
|
||||
} catch (error) {
|
||||
console.error(`Error stopping process: ${error.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
// Restart command
|
||||
smartcliInstance.addCommand('restart').subscribe({
|
||||
next: async (argvArg) => {
|
||||
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
|
||||
if (!id) {
|
||||
console.error('Error: Missing process ID. Usage: tspm restart <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await tspm.restart(id);
|
||||
console.log(`Process ${id} restarted.`);
|
||||
} catch (error) {
|
||||
console.error(`Error restarting process: ${error.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Delete command
|
||||
smartcliInstance.addCommand('delete').subscribe({
|
||||
next: async (argvArg) => {
|
||||
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
|
||||
if (!id) {
|
||||
console.error('Error: Missing process ID. Usage: tspm delete <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await tspm.delete(id);
|
||||
console.log(`Process ${id} deleted.`);
|
||||
} catch (error) {
|
||||
console.error(`Error deleting process: ${error.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// List command
|
||||
smartcliInstance.addCommand('list').subscribe({
|
||||
next: async (argvArg) => {
|
||||
const processes = tspm.list();
|
||||
|
||||
if (processes.length === 0) {
|
||||
console.log('No processes running.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('┌─────────┬─────────────┬───────────┬───────────┬──────────┐');
|
||||
console.log('│ ID │ Name │ Status │ Memory │ Restarts │');
|
||||
console.log('├─────────┼─────────────┼───────────┼───────────┼──────────┤');
|
||||
|
||||
for (const proc of processes) {
|
||||
console.log(`│ ${pad(proc.id, 8)} │ ${pad(proc.id, 12)} │ ${pad(proc.status, 10)} │ ${pad(formatMemory(proc.memory), 10)} │ ${pad(String(proc.restarts), 9)} │`);
|
||||
}
|
||||
|
||||
console.log('└─────────┴─────────────┴───────────┴───────────┴──────────┘');
|
||||
}
|
||||
});
|
||||
|
||||
// Describe command
|
||||
smartcliInstance.addCommand('describe').subscribe({
|
||||
next: async (argvArg) => {
|
||||
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
|
||||
if (!id) {
|
||||
console.error('Error: Missing process ID. Usage: tspm describe <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
const details = tspm.describe(id);
|
||||
|
||||
if (!details) {
|
||||
console.error(`Process with ID '${id}' not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Details for process '${id}':`);
|
||||
console.log(` Status: ${details.info.status}`);
|
||||
console.log(` Memory: ${formatMemory(details.info.memory)}`);
|
||||
console.log(` Restarts: ${details.info.restarts}`);
|
||||
console.log(` Command: ${details.config.command}`);
|
||||
console.log(` Directory: ${details.config.projectDir}`);
|
||||
console.log(` Memory limit: ${formatMemory(details.config.memoryLimitBytes)}`);
|
||||
|
||||
if (details.config.args && details.config.args.length > 0) {
|
||||
console.log(` Arguments: ${details.config.args.join(' ')}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Logs command
|
||||
smartcliInstance.addCommand('logs').subscribe({
|
||||
next: async (argvArg) => {
|
||||
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
|
||||
if (!id) {
|
||||
console.error('Error: Missing process ID. Usage: tspm logs <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = Number(argvArg.lines || argvArg.n) || 20;
|
||||
const logs = tspm.getLogs(id, lines);
|
||||
|
||||
if (logs.length === 0) {
|
||||
console.log(`No logs found for process '${id}'.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Display logs with colors for different log types
|
||||
for (const log of logs) {
|
||||
const timestamp = log.timestamp.toISOString();
|
||||
const prefix = `[${timestamp}] `;
|
||||
|
||||
switch (log.type) {
|
||||
case 'stdout':
|
||||
console.log(`${prefix}${log.message}`);
|
||||
break;
|
||||
case 'stderr':
|
||||
console.error(`${prefix}${log.message}`);
|
||||
break;
|
||||
case 'system':
|
||||
console.log(`${prefix}[SYSTEM] ${log.message}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start parsing
|
||||
smartcliInstance.startParse();
|
||||
};
|
||||
|
||||
// Helper function to format memory usage
|
||||
function formatMemory(bytes: number): string {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// Helper function to parse memory strings like "500MB"
|
||||
function parseMemoryString(memString: string): number {
|
||||
const units = {
|
||||
'B': 1,
|
||||
'KB': 1024,
|
||||
'MB': 1024 * 1024,
|
||||
'GB': 1024 * 1024 * 1024,
|
||||
'TB': 1024 * 1024 * 1024 * 1024
|
||||
};
|
||||
|
||||
const match = memString.match(/^(\d+(?:\.\d+)?)\s*([KMGT]?B)$/i);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid memory format: ${memString}. Use format like 500MB`);
|
||||
}
|
||||
|
||||
const value = parseFloat(match[1]);
|
||||
const unit = match[2].toUpperCase();
|
||||
|
||||
return value * units[unit];
|
||||
}
|
||||
|
||||
// Helper function to pad strings for table display
|
||||
function pad(str: string, length: number): string {
|
||||
return str.padEnd(length);
|
||||
}
|
@ -10,14 +10,16 @@ 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';
|
||||
import * as smartdaemon from '@push.rocks/smartdaemon';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
|
||||
export {
|
||||
npmextra,
|
||||
projectinfo,
|
||||
smartpath,
|
||||
smartcli,
|
||||
smartdaemon,
|
||||
smartpath,
|
||||
}
|
||||
|
||||
// third-party scope
|
||||
|
Reference in New Issue
Block a user