This commit is contained in:
2025-08-28 15:47:59 +00:00
parent 1c2310c185
commit 33fb02733d
30 changed files with 1061 additions and 1070 deletions

View File

@@ -0,0 +1,24 @@
import * as plugins from '../../../plugins.js';
import { tspmIpcClient } from '../../../classes.ipcclient.js';
import type { CliArguments } from '../../types.js';
import { registerIpcCommand } from '../../registration/index.js';
export function registerDeleteCommand(smartcli: plugins.smartcli.Smartcli) {
registerIpcCommand(smartcli, 'delete', async (argvArg: CliArguments) => {
const id = argvArg._[1];
if (!id) {
console.error('Error: Please provide a process ID');
console.log('Usage: tspm delete <id>');
return;
}
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}`);
}
}, { actionLabel: 'delete process' });
}

View File

@@ -0,0 +1,38 @@
import * as plugins from '../../../plugins.js';
import { tspmIpcClient } from '../../../classes.ipcclient.js';
import type { CliArguments } from '../../types.js';
import { registerIpcCommand } from '../../registration/index.js';
import { formatMemory } from '../../helpers/memory.js';
export function registerDescribeCommand(smartcli: plugins.smartcli.Smartcli) {
registerIpcCommand(smartcli, 'describe', async (argvArg: CliArguments) => {
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(', ')}`);
}
}
}, { actionLabel: 'describe process' });
}

View File

@@ -0,0 +1,37 @@
import * as plugins from '../../../plugins.js';
import { tspmIpcClient } from '../../../classes.ipcclient.js';
import type { CliArguments } from '../../types.js';
import { registerIpcCommand } from '../../registration/index.js';
import { pad } from '../../helpers/formatting.js';
import { formatMemory } from '../../helpers/memory.js';
export function registerListCommand(smartcli: plugins.smartcli.Smartcli) {
registerIpcCommand(smartcli, 'list', async (_argvArg: CliArguments) => {
const response = await tspmIpcClient.request('list', {});
const processes = response.processes;
if (processes.length === 0) {
console.log('No processes running.');
return;
}
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('└─────────┴─────────────┴───────────┴───────────┴──────────┴──────────┘');
}, { actionLabel: 'list processes' });
}

View File

@@ -0,0 +1,73 @@
import * as plugins from '../../../plugins.js';
import { tspmIpcClient } from '../../../classes.ipcclient.js';
import type { CliArguments } from '../../types.js';
import { registerIpcCommand } from '../../registration/index.js';
import { getBool, getNumber } from '../../helpers/argv.js';
import { formatLog } from '../../helpers/formatting.js';
import { withStreamingLifecycle } from '../../helpers/lifecycle.js';
export function registerLogsCommand(smartcli: plugins.smartcli.Smartcli) {
registerIpcCommand(smartcli, 'logs', async (argvArg: CliArguments) => {
const id = argvArg._[1];
if (!id) {
console.error('Error: Please provide a process ID');
console.log('Usage: tspm logs <id> [options]');
console.log('\nOptions:');
console.log(' --lines <n> Number of lines to show (default: 50)');
console.log(' --follow Stream logs in real-time (like tail -f)');
return;
}
const lines = getNumber(argvArg, 'lines', 50);
const follow = getBool(argvArg, 'follow', 'f');
const response = await tspmIpcClient.request('getLogs', { id, lines });
if (!follow) {
// One-shot mode - auto-disconnect handled by registerIpcCommand
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]' : log.type === 'stderr' ? '[ERR]' : '[SYS]';
console.log(`${timestamp} ${prefix} ${log.message}`);
}
return;
}
// Streaming mode
console.log(`Logs for process: ${id} (streaming...)`);
console.log('─'.repeat(60));
let lastSeq = 0;
for (const log of response.logs) {
const timestamp = new Date(log.timestamp).toLocaleTimeString();
const prefix = log.type === 'stdout' ? '[OUT]' : log.type === 'stderr' ? '[ERR]' : '[SYS]';
console.log(`${timestamp} ${prefix} ${log.message}`);
if (log.seq !== undefined) lastSeq = Math.max(lastSeq, log.seq);
}
await withStreamingLifecycle(
async () => {
await tspmIpcClient.subscribe(id, (log: any) => {
if (log.seq !== undefined && log.seq <= lastSeq) return;
if (log.seq !== undefined && log.seq > lastSeq + 1) {
console.log(`[WARNING] Log gap detected: expected seq ${lastSeq + 1}, got ${log.seq}`);
}
const timestamp = new Date(log.timestamp).toLocaleTimeString();
const prefix = log.type === 'stdout' ? '[OUT]' : log.type === 'stderr' ? '[ERR]' : '[SYS]';
console.log(`${timestamp} ${prefix} ${log.message}`);
if (log.seq !== undefined) lastSeq = log.seq;
});
},
async () => {
console.log('\n\nStopping log stream...');
try { await tspmIpcClient.unsubscribe(id); } catch {}
try { await tspmIpcClient.disconnect(); } catch {}
}
);
}, {
actionLabel: 'get logs',
keepAlive: (argv) => getBool(argv, 'follow', 'f')
});
}

View File

@@ -0,0 +1,23 @@
import * as plugins from '../../../plugins.js';
import { tspmIpcClient } from '../../../classes.ipcclient.js';
import type { CliArguments } from '../../types.js';
import { registerIpcCommand } from '../../registration/index.js';
export function registerRestartCommand(smartcli: plugins.smartcli.Smartcli) {
registerIpcCommand(smartcli, 'restart', async (argvArg: CliArguments) => {
const id = argvArg._[1];
if (!id) {
console.error('Error: Please provide a process ID');
console.log('Usage: tspm restart <id>');
return;
}
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}`);
}, { actionLabel: 'restart process' });
}

View File

@@ -0,0 +1,83 @@
import * as plugins from '../../../plugins.js';
import { tspmIpcClient } from '../../../classes.ipcclient.js';
import type { IProcessConfig } from '../../../classes.tspm.js';
import type { CliArguments } from '../../types.js';
import { parseMemoryString, formatMemory } from '../../helpers/memory.js';
import { registerIpcCommand } from '../../registration/index.js';
export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
registerIpcCommand(smartcli, 'start', async (argvArg: CliArguments) => {
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;
}
const memoryLimit = argvArg.memory ? parseMemoryString(argvArg.memory) : 512 * 1024 * 1024;
const projectDir = argvArg.cwd || process.cwd();
// Direct .ts support via tsx (bundled with TSPM)
let actualCommand = script;
let commandArgs: string[] | undefined = undefined;
if (script.endsWith('.ts')) {
try {
const tsxPath = await (async () => {
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
return require.resolve('tsx/dist/cli.mjs');
})();
const scriptPath = plugins.path.isAbsolute(script) ? script : plugins.path.join(projectDir, script);
actualCommand = tsxPath;
commandArgs = [scriptPath];
} catch {
actualCommand = 'tsx';
commandArgs = [script];
}
}
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: actualCommand,
args: commandArgs,
projectDir,
memoryLimitBytes: memoryLimit,
autorestart,
watch,
watchPaths,
};
console.log(`Starting process: ${name}`);
console.log(` Command: ${script}${script.endsWith('.ts') ? ' (via tsx)' : ''}`);
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}`);
}, { actionLabel: 'start process' });
}

View File

@@ -0,0 +1,24 @@
import * as plugins from '../../../plugins.js';
import { tspmIpcClient } from '../../../classes.ipcclient.js';
import type { CliArguments } from '../../types.js';
import { registerIpcCommand } from '../../registration/index.js';
export function registerStopCommand(smartcli: plugins.smartcli.Smartcli) {
registerIpcCommand(smartcli, 'stop', async (argvArg: CliArguments) => {
const id = argvArg._[1];
if (!id) {
console.error('Error: Please provide a process ID');
console.log('Usage: tspm stop <id>');
return;
}
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}`);
}
}, { actionLabel: 'stop process' });
}