feat(daemon): Add central TSPM daemon and IPC client; refactor CLI to use daemon and improve monitoring/error handling
This commit is contained in:
831
ts/cli.ts
831
ts/cli.ts
@@ -1,26 +1,66 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { Tspm, type IProcessConfig } from './classes.tspm.js';
|
||||
import {
|
||||
Logger,
|
||||
LogLevel,
|
||||
handleError,
|
||||
TspmError,
|
||||
ProcessError,
|
||||
ConfigError,
|
||||
ValidationError
|
||||
} from './utils.errorhandler.js';
|
||||
import { tspmIpcClient } from './classes.ipcclient.js';
|
||||
import { Logger, LogLevel } from './utils.errorhandler.js';
|
||||
import type { IProcessConfig } from './classes.tspm.js';
|
||||
|
||||
// Define interface for CLI arguments
|
||||
interface CliArguments {
|
||||
_: (string | number)[];
|
||||
export interface CliArguments {
|
||||
verbose?: boolean;
|
||||
watch?: boolean;
|
||||
memory?: string;
|
||||
cwd?: string;
|
||||
daemon?: boolean;
|
||||
test?: boolean;
|
||||
name?: string;
|
||||
autorestart?: boolean;
|
||||
watchPaths?: string[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// Helper function to parse memory strings (e.g., "512MB", "2GB")
|
||||
function parseMemoryString(memStr: string): number {
|
||||
const units = {
|
||||
KB: 1024,
|
||||
MB: 1024 * 1024,
|
||||
GB: 1024 * 1024 * 1024,
|
||||
};
|
||||
|
||||
const match = memStr.toUpperCase().match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)?$/);
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
`Invalid memory format: ${memStr}. Use format like "512MB" or "2GB"`,
|
||||
);
|
||||
}
|
||||
|
||||
const value = parseFloat(match[1]);
|
||||
const unit = (match[2] || 'MB') as keyof typeof units;
|
||||
|
||||
return Math.floor(value * units[unit]);
|
||||
}
|
||||
|
||||
// Helper function to format memory for display
|
||||
function formatMemory(bytes: number): string {
|
||||
if (bytes >= 1024 * 1024 * 1024) {
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
||||
} else if (bytes >= 1024 * 1024) {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
} else if (bytes >= 1024) {
|
||||
return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
} else {
|
||||
return `${bytes} B`;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for padding strings
|
||||
function pad(str: string, length: number): string {
|
||||
return str.length > length
|
||||
? str.substring(0, length - 3) + '...'
|
||||
: str.padEnd(length);
|
||||
}
|
||||
|
||||
export const run = async (): Promise<void> => {
|
||||
const cliLogger = new Logger('CLI');
|
||||
const tspmProjectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
||||
const tspm = new Tspm();
|
||||
|
||||
// Check if debug mode is enabled
|
||||
const debugMode = process.env.TSPM_DEBUG === 'true';
|
||||
@@ -31,341 +71,568 @@ export const run = async (): Promise<void> => {
|
||||
|
||||
const smartcliInstance = new plugins.smartcli.Smartcli();
|
||||
smartcliInstance.addVersion(tspmProjectinfo.npm.version);
|
||||
|
||||
|
||||
// Default command - show help and list processes
|
||||
smartcliInstance.standardCommand().subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
console.log(`TSPM - TypeScript Process Manager v${tspmProjectinfo.npm.version}`);
|
||||
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.');
|
||||
|
||||
console.log(' logs <id> Show logs for a process');
|
||||
console.log(' start-all Start all saved processes');
|
||||
console.log(' stop-all Stop all processes');
|
||||
console.log(' restart-all Restart all processes');
|
||||
console.log('\nDaemon Commands:');
|
||||
console.log(' daemon start Start the TSPM daemon');
|
||||
console.log(' daemon stop Stop the TSPM daemon');
|
||||
console.log(' daemon status Show daemon status');
|
||||
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)} │`);
|
||||
|
||||
try {
|
||||
const response = await tspmIpcClient.request('list', {});
|
||||
const processes = response.processes;
|
||||
|
||||
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) {
|
||||
const statusColor =
|
||||
proc.status === 'online'
|
||||
? '\x1b[32m'
|
||||
: proc.status === 'errored'
|
||||
? '\x1b[31m'
|
||||
: '\x1b[33m';
|
||||
const resetColor = '\x1b[0m';
|
||||
|
||||
console.log(
|
||||
`│ ${pad(proc.id, 7)} │ ${pad(proc.id, 11)} │ ${statusColor}${pad(proc.status, 9)}${resetColor} │ ${pad(formatMemory(proc.memory), 9)} │ ${pad(proc.restarts.toString(), 8)} │`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
'└─────────┴─────────────┴───────────┴───────────┴──────────┘',
|
||||
);
|
||||
}
|
||||
|
||||
console.log('└─────────┴─────────────┴───────────┴───────────┴──────────┘');
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error: Could not connect to TSPM daemon. Use "tspm daemon start" to start it.',
|
||||
);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// Start command - start a new process
|
||||
// Start command
|
||||
smartcliInstance.addCommand('start').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
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 {
|
||||
cliLogger.debug(`Starting process with script: ${script}`);
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
cliLogger.debug(`Created process config: ${JSON.stringify(processConfig)}`);
|
||||
|
||||
await tspm.start(processConfig);
|
||||
console.log(`Process ${processConfig.id} started successfully.`);
|
||||
} catch (error: Error | unknown) {
|
||||
const tspmError = handleError(error);
|
||||
|
||||
if (tspmError instanceof ValidationError) {
|
||||
console.error(`Validation error: ${tspmError.message}`);
|
||||
} else if (tspmError instanceof ProcessError) {
|
||||
console.error(`Process error: ${tspmError.message}`);
|
||||
if (debugMode) {
|
||||
console.error(`Error details: ${JSON.stringify(tspmError.details)}`);
|
||||
}
|
||||
} else {
|
||||
console.error(`Error starting process: ${tspmError.message}`);
|
||||
const script = argvArg._[1];
|
||||
if (!script) {
|
||||
console.error('Error: Please provide a script to run');
|
||||
console.log('Usage: tspm start <script> [options]');
|
||||
console.log('\nOptions:');
|
||||
console.log(' --name <name> Name for the process');
|
||||
console.log(
|
||||
' --memory <size> Memory limit (e.g., "512MB", "2GB")',
|
||||
);
|
||||
console.log(' --cwd <path> Working directory');
|
||||
console.log(
|
||||
' --watch Watch for file changes and restart',
|
||||
);
|
||||
console.log(' --watch-paths <paths> Comma-separated paths to watch');
|
||||
console.log(' --autorestart Auto-restart on crash');
|
||||
return;
|
||||
}
|
||||
|
||||
cliLogger.error(tspmError);
|
||||
|
||||
const memoryLimit = argvArg.memory
|
||||
? parseMemoryString(argvArg.memory)
|
||||
: 512 * 1024 * 1024; // Default 512MB
|
||||
const projectDir = argvArg.cwd || process.cwd();
|
||||
const name = argvArg.name || script;
|
||||
const watch = argvArg.watch || false;
|
||||
const autorestart = argvArg.autorestart !== false; // Default true
|
||||
const watchPaths = argvArg.watchPaths
|
||||
? typeof argvArg.watchPaths === 'string'
|
||||
? (argvArg.watchPaths as string).split(',')
|
||||
: argvArg.watchPaths
|
||||
: undefined;
|
||||
|
||||
const processConfig: IProcessConfig = {
|
||||
id: name.replace(/[^a-zA-Z0-9-_]/g, '_'),
|
||||
name,
|
||||
command: script,
|
||||
projectDir,
|
||||
memoryLimitBytes: memoryLimit,
|
||||
autorestart,
|
||||
watch,
|
||||
watchPaths,
|
||||
};
|
||||
|
||||
console.log(`Starting process: ${name}`);
|
||||
console.log(` Command: ${script}`);
|
||||
console.log(` Directory: ${projectDir}`);
|
||||
console.log(` Memory limit: ${formatMemory(memoryLimit)}`);
|
||||
console.log(` Auto-restart: ${autorestart}`);
|
||||
if (watch) {
|
||||
console.log(` Watch mode: enabled`);
|
||||
if (watchPaths) {
|
||||
console.log(` Watch paths: ${watchPaths.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await tspmIpcClient.request('start', {
|
||||
config: processConfig,
|
||||
});
|
||||
|
||||
console.log(`✓ Process started successfully`);
|
||||
console.log(` ID: ${response.processId}`);
|
||||
console.log(` PID: ${response.pid || 'N/A'}`);
|
||||
console.log(` Status: ${response.status}`);
|
||||
} catch (error) {
|
||||
console.error('Error starting process:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Start as daemon command
|
||||
smartcliInstance.addCommand('startAsDaemon').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
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.`);
|
||||
}
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// Stop command
|
||||
smartcliInstance.addCommand('stop').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
|
||||
if (!id) {
|
||||
console.error('Error: Missing process ID. Usage: tspm stop <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
cliLogger.debug(`Stopping process: ${id}`);
|
||||
await tspm.stop(id);
|
||||
console.log(`Process ${id} stopped.`);
|
||||
} catch (error: Error | unknown) {
|
||||
const tspmError = handleError(error);
|
||||
|
||||
if (tspmError instanceof ValidationError) {
|
||||
console.error(`Validation error: ${tspmError.message}`);
|
||||
} else {
|
||||
console.error(`Error stopping process: ${tspmError.message}`);
|
||||
const id = argvArg._[1];
|
||||
if (!id) {
|
||||
console.error('Error: Please provide a process ID');
|
||||
console.log('Usage: tspm stop <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
cliLogger.error(tspmError);
|
||||
|
||||
console.log(`Stopping process: ${id}`);
|
||||
const response = await tspmIpcClient.request('stop', { id });
|
||||
|
||||
if (response.success) {
|
||||
console.log(`✓ ${response.message}`);
|
||||
} else {
|
||||
console.error(`✗ Failed to stop process: ${response.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error stopping process:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// Restart command
|
||||
smartcliInstance.addCommand('restart').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
|
||||
if (!id) {
|
||||
console.error('Error: Missing process ID. Usage: tspm restart <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
cliLogger.debug(`Restarting process: ${id}`);
|
||||
await tspm.restart(id);
|
||||
console.log(`Process ${id} restarted.`);
|
||||
} catch (error: Error | unknown) {
|
||||
const tspmError = handleError(error);
|
||||
|
||||
if (tspmError instanceof ValidationError) {
|
||||
console.error(`Validation error: ${tspmError.message}`);
|
||||
} else if (tspmError instanceof ProcessError) {
|
||||
console.error(`Process error: ${tspmError.message}`);
|
||||
} else {
|
||||
console.error(`Error restarting process: ${tspmError.message}`);
|
||||
const id = argvArg._[1];
|
||||
if (!id) {
|
||||
console.error('Error: Please provide a process ID');
|
||||
console.log('Usage: tspm restart <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
cliLogger.error(tspmError);
|
||||
|
||||
console.log(`Restarting process: ${id}`);
|
||||
const response = await tspmIpcClient.request('restart', { id });
|
||||
|
||||
console.log(`✓ Process restarted successfully`);
|
||||
console.log(` ID: ${response.processId}`);
|
||||
console.log(` PID: ${response.pid || 'N/A'}`);
|
||||
console.log(` Status: ${response.status}`);
|
||||
} catch (error) {
|
||||
console.error('Error restarting process:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// Delete command
|
||||
smartcliInstance.addCommand('delete').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
|
||||
if (!id) {
|
||||
console.error('Error: Missing process ID. Usage: tspm delete <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
cliLogger.debug(`Deleting process: ${id}`);
|
||||
await tspm.delete(id);
|
||||
console.log(`Process ${id} deleted.`);
|
||||
} catch (error: Error | unknown) {
|
||||
const tspmError = handleError(error);
|
||||
|
||||
if (tspmError instanceof ValidationError) {
|
||||
console.error(`Validation error: ${tspmError.message}`);
|
||||
} else if (tspmError instanceof ConfigError) {
|
||||
console.error(`Configuration error: ${tspmError.message}`);
|
||||
} else {
|
||||
console.error(`Error deleting process: ${tspmError.message}`);
|
||||
const id = argvArg._[1];
|
||||
if (!id) {
|
||||
console.error('Error: Please provide a process ID');
|
||||
console.log('Usage: tspm delete <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
cliLogger.error(tspmError);
|
||||
|
||||
console.log(`Deleting process: ${id}`);
|
||||
const response = await tspmIpcClient.request('delete', { id });
|
||||
|
||||
if (response.success) {
|
||||
console.log(`✓ ${response.message}`);
|
||||
} else {
|
||||
console.error(`✗ Failed to delete process: ${response.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting process:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// List command
|
||||
smartcliInstance.addCommand('list').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
const processes = tspm.list();
|
||||
|
||||
if (processes.length === 0) {
|
||||
console.log('No processes running.');
|
||||
return;
|
||||
try {
|
||||
const response = await tspmIpcClient.request('list', {});
|
||||
const processes = response.processes;
|
||||
|
||||
if (processes.length === 0) {
|
||||
console.log('No processes running.');
|
||||
} else {
|
||||
console.log('Process List:');
|
||||
console.log(
|
||||
'┌─────────┬─────────────┬───────────┬───────────┬──────────┬──────────┐',
|
||||
);
|
||||
console.log(
|
||||
'│ ID │ Name │ Status │ PID │ Memory │ Restarts │',
|
||||
);
|
||||
console.log(
|
||||
'├─────────┼─────────────┼───────────┼───────────┼──────────┼──────────┤',
|
||||
);
|
||||
|
||||
for (const proc of processes) {
|
||||
const statusColor =
|
||||
proc.status === 'online'
|
||||
? '\x1b[32m'
|
||||
: proc.status === 'errored'
|
||||
? '\x1b[31m'
|
||||
: '\x1b[33m';
|
||||
const resetColor = '\x1b[0m';
|
||||
|
||||
console.log(
|
||||
`│ ${pad(proc.id, 7)} │ ${pad(proc.id, 11)} │ ${statusColor}${pad(proc.status, 9)}${resetColor} │ ${pad((proc.pid || '-').toString(), 9)} │ ${pad(formatMemory(proc.memory), 8)} │ ${pad(proc.restarts.toString(), 8)} │`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
'└─────────┴─────────────┴───────────┴───────────┴──────────┴──────────┘',
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error listing processes:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
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('└─────────┴─────────────┴───────────┴───────────┴──────────┘');
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// Describe command
|
||||
smartcliInstance.addCommand('describe').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
|
||||
|
||||
if (!id) {
|
||||
console.error('Error: Missing process ID. Usage: tspm describe <id>');
|
||||
return;
|
||||
try {
|
||||
const id = argvArg._[1];
|
||||
if (!id) {
|
||||
console.error('Error: Please provide a process ID');
|
||||
console.log('Usage: tspm describe <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await tspmIpcClient.request('describe', { id });
|
||||
|
||||
console.log(`Process Details: ${id}`);
|
||||
console.log('─'.repeat(40));
|
||||
console.log(`Status: ${response.processInfo.status}`);
|
||||
console.log(`PID: ${response.processInfo.pid || 'N/A'}`);
|
||||
console.log(
|
||||
`Memory: ${formatMemory(response.processInfo.memory)}`,
|
||||
);
|
||||
console.log(
|
||||
`CPU: ${response.processInfo.cpu ? response.processInfo.cpu.toFixed(1) + '%' : 'N/A'}`,
|
||||
);
|
||||
console.log(
|
||||
`Uptime: ${response.processInfo.uptime ? Math.floor(response.processInfo.uptime / 1000) + 's' : 'N/A'}`,
|
||||
);
|
||||
console.log(`Restarts: ${response.processInfo.restarts}`);
|
||||
console.log('\nConfiguration:');
|
||||
console.log(`Command: ${response.config.command}`);
|
||||
console.log(`Directory: ${response.config.projectDir}`);
|
||||
console.log(
|
||||
`Memory Limit: ${formatMemory(response.config.memoryLimitBytes)}`,
|
||||
);
|
||||
console.log(`Auto-restart: ${response.config.autorestart}`);
|
||||
if (response.config.watch) {
|
||||
console.log(`Watch: enabled`);
|
||||
if (response.config.watchPaths) {
|
||||
console.log(
|
||||
`Watch Paths: ${response.config.watchPaths.join(', ')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error describing process:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
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(' ')}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
|
||||
// Logs command
|
||||
smartcliInstance.addCommand('logs').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
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;
|
||||
try {
|
||||
const id = argvArg._[1];
|
||||
if (!id) {
|
||||
console.error('Error: Please provide a process ID');
|
||||
console.log('Usage: tspm logs <id>');
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = argvArg.lines || 50;
|
||||
const response = await tspmIpcClient.request('getLogs', { id, lines });
|
||||
|
||||
console.log(`Logs for process: ${id} (last ${lines} lines)`);
|
||||
console.log('─'.repeat(60));
|
||||
|
||||
for (const log of response.logs) {
|
||||
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
||||
const prefix = log.type === 'stdout' ? '[OUT]' : '[ERR]';
|
||||
console.log(`${timestamp} ${prefix} ${log.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting logs:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// Start parsing
|
||||
// Start-all command
|
||||
smartcliInstance.addCommand('start-all').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
try {
|
||||
console.log('Starting all processes...');
|
||||
const response = await tspmIpcClient.request('startAll', {});
|
||||
|
||||
if (response.started.length > 0) {
|
||||
console.log(`✓ Started ${response.started.length} processes:`);
|
||||
for (const id of response.started) {
|
||||
console.log(` - ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.failed.length > 0) {
|
||||
console.log(`✗ Failed to start ${response.failed.length} processes:`);
|
||||
for (const failure of response.failed) {
|
||||
console.log(` - ${failure.id}: ${failure.error}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error starting all processes:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// Stop-all command
|
||||
smartcliInstance.addCommand('stop-all').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
try {
|
||||
console.log('Stopping all processes...');
|
||||
const response = await tspmIpcClient.request('stopAll', {});
|
||||
|
||||
if (response.stopped.length > 0) {
|
||||
console.log(`✓ Stopped ${response.stopped.length} processes:`);
|
||||
for (const id of response.stopped) {
|
||||
console.log(` - ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.failed.length > 0) {
|
||||
console.log(`✗ Failed to stop ${response.failed.length} processes:`);
|
||||
for (const failure of response.failed) {
|
||||
console.log(` - ${failure.id}: ${failure.error}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error stopping all processes:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// Restart-all command
|
||||
smartcliInstance.addCommand('restart-all').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
try {
|
||||
console.log('Restarting all processes...');
|
||||
const response = await tspmIpcClient.request('restartAll', {});
|
||||
|
||||
if (response.restarted.length > 0) {
|
||||
console.log(`✓ Restarted ${response.restarted.length} processes:`);
|
||||
for (const id of response.restarted) {
|
||||
console.log(` - ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.failed.length > 0) {
|
||||
console.log(
|
||||
`✗ Failed to restart ${response.failed.length} processes:`,
|
||||
);
|
||||
for (const failure of response.failed) {
|
||||
console.log(` - ${failure.id}: ${failure.error}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error restarting all processes:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// Daemon commands
|
||||
smartcliInstance.addCommand('daemon').subscribe({
|
||||
next: async (argvArg: CliArguments) => {
|
||||
const subCommand = argvArg._[1];
|
||||
|
||||
switch (subCommand) {
|
||||
case 'start':
|
||||
try {
|
||||
const status = await tspmIpcClient.getDaemonStatus();
|
||||
if (status) {
|
||||
console.log('TSPM daemon is already running');
|
||||
console.log(` PID: ${status.pid}`);
|
||||
console.log(
|
||||
` Uptime: ${Math.floor((status.uptime || 0) / 1000)}s`,
|
||||
);
|
||||
console.log(` Processes: ${status.processCount}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Starting TSPM daemon...');
|
||||
await tspmIpcClient.connect();
|
||||
console.log('✓ TSPM daemon started successfully');
|
||||
|
||||
const newStatus = await tspmIpcClient.getDaemonStatus();
|
||||
if (newStatus) {
|
||||
console.log(` PID: ${newStatus.pid}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error starting daemon:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
try {
|
||||
console.log('Stopping TSPM daemon...');
|
||||
await tspmIpcClient.stopDaemon(true);
|
||||
console.log('✓ TSPM daemon stopped successfully');
|
||||
} catch (error) {
|
||||
console.error('Error stopping daemon:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
try {
|
||||
const status = await tspmIpcClient.getDaemonStatus();
|
||||
if (!status) {
|
||||
console.log('TSPM daemon is not running');
|
||||
console.log('Use "tspm daemon start" to start it');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('TSPM Daemon Status:');
|
||||
console.log('─'.repeat(40));
|
||||
console.log(`Status: ${status.status}`);
|
||||
console.log(`PID: ${status.pid}`);
|
||||
console.log(
|
||||
`Uptime: ${Math.floor((status.uptime || 0) / 1000)}s`,
|
||||
);
|
||||
console.log(`Processes: ${status.processCount}`);
|
||||
console.log(
|
||||
`Memory: ${formatMemory(status.memoryUsage || 0)}`,
|
||||
);
|
||||
console.log(`CPU: ${status.cpuUsage?.toFixed(1) || 0}s`);
|
||||
} catch (error) {
|
||||
console.error('Error getting daemon status:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('Usage: tspm daemon <command>');
|
||||
console.log('\nCommands:');
|
||||
console.log(' start Start the TSPM daemon');
|
||||
console.log(' stop Stop the TSPM daemon');
|
||||
console.log(' status Show daemon status');
|
||||
break;
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
cliLogger.error(err);
|
||||
},
|
||||
complete: () => {},
|
||||
});
|
||||
|
||||
// Start parsing commands
|
||||
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);
|
||||
}
|
Reference in New Issue
Block a user