BREAKING CHANGE(daemon): Refactor daemon lifecycle and service management: remove IPC auto-spawn, add TspmServiceManager and CLI enable/disable

This commit is contained in:
2025-08-28 10:39:35 +00:00
parent 35b6a6a8d0
commit d33a001edc
8 changed files with 291 additions and 133 deletions

153
ts/cli.ts
View File

@@ -1,6 +1,7 @@
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import { tspmIpcClient } from './classes.ipcclient.js';
import { TspmServiceManager } from './classes.servicemanager.js';
import { Logger, LogLevel } from './utils.errorhandler.js';
import type { IProcessConfig } from './classes.tspm.js';
@@ -51,6 +52,21 @@ function formatMemory(bytes: number): string {
}
}
// Helper function to handle daemon connection errors
function handleDaemonError(error: any, action: string): void {
if (error.message?.includes('daemon is not running') ||
error.message?.includes('Not connected') ||
error.message?.includes('ECONNREFUSED')) {
console.error(`Error: Cannot ${action} - TSPM daemon is not running.`);
console.log('\nTo start the daemon, run one of:');
console.log(' tspm daemon start - Start for this session only');
console.log(' tspm enable - Enable as system service (recommended)');
} else {
console.error(`Error ${action}:`, error.message);
}
process.exit(1);
}
// Helper function for padding strings
function pad(str: string, length: number): string {
return str.length > length
@@ -79,7 +95,10 @@ export const run = async (): Promise<void> => {
`TSPM - TypeScript Process Manager v${tspmProjectinfo.npm.version}`,
);
console.log('Usage: tspm [command] [options]');
console.log('\nCommands:');
console.log('\nService Management:');
console.log(' enable Enable TSPM as system service (systemd)');
console.log(' disable Disable TSPM system service');
console.log('\nProcess Commands:');
console.log(' start <script> Start a process');
console.log(' list List all processes');
console.log(' stop <id> Stop a process');
@@ -91,8 +110,8 @@ export const run = async (): Promise<void> => {
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 start Start daemon manually (current session)');
console.log(' daemon stop Stop the daemon');
console.log(' daemon status Show daemon status');
console.log(
'\nUse tspm [command] --help for more information about a command.',
@@ -139,9 +158,10 @@ export const run = async (): Promise<void> => {
);
}
} catch (error) {
console.error(
'Error: Could not connect to TSPM daemon. Use "tspm daemon start" to start it.',
);
console.error('Error: TSPM daemon is not running.');
console.log('\nTo start the daemon, run one of:');
console.log(' tspm daemon start - Start for this session only');
console.log(' tspm enable - Enable as system service (recommended)');
}
},
error: (err) => {
@@ -217,8 +237,7 @@ export const run = async (): Promise<void> => {
console.log(` PID: ${response.pid || 'N/A'}`);
console.log(` Status: ${response.status}`);
} catch (error) {
console.error('Error starting process:', error.message);
process.exit(1);
handleDaemonError(error, 'start process');
}
},
error: (err) => {
@@ -247,8 +266,7 @@ export const run = async (): Promise<void> => {
console.error(`✗ Failed to stop process: ${response.message}`);
}
} catch (error) {
console.error('Error stopping process:', error.message);
process.exit(1);
handleDaemonError(error, 'stop process');
}
},
error: (err) => {
@@ -276,8 +294,7 @@ export const run = async (): Promise<void> => {
console.log(` PID: ${response.pid || 'N/A'}`);
console.log(` Status: ${response.status}`);
} catch (error) {
console.error('Error restarting process:', error.message);
process.exit(1);
handleDaemonError(error, 'restart process');
}
},
error: (err) => {
@@ -306,8 +323,7 @@ export const run = async (): Promise<void> => {
console.error(`✗ Failed to delete process: ${response.message}`);
}
} catch (error) {
console.error('Error deleting process:', error.message);
process.exit(1);
handleDaemonError(error, 'delete process');
}
},
error: (err) => {
@@ -356,8 +372,7 @@ export const run = async (): Promise<void> => {
);
}
} catch (error) {
console.error('Error listing processes:', error.message);
process.exit(1);
handleDaemonError(error, 'list processes');
}
},
error: (err) => {
@@ -409,8 +424,7 @@ export const run = async (): Promise<void> => {
}
}
} catch (error) {
console.error('Error describing process:', error.message);
process.exit(1);
handleDaemonError(error, 'describe process');
}
},
error: (err) => {
@@ -506,8 +520,7 @@ export const run = async (): Promise<void> => {
await new Promise(() => {}); // Block forever until interrupted
}
} catch (error) {
console.error('Error getting logs:', error.message);
process.exit(1);
handleDaemonError(error, 'get logs');
}
},
error: (err) => {
@@ -537,8 +550,7 @@ export const run = async (): Promise<void> => {
}
}
} catch (error) {
console.error('Error starting all processes:', error.message);
process.exit(1);
handleDaemonError(error, 'start all processes');
}
},
error: (err) => {
@@ -568,8 +580,7 @@ export const run = async (): Promise<void> => {
}
}
} catch (error) {
console.error('Error stopping all processes:', error.message);
process.exit(1);
handleDaemonError(error, 'stop all processes');
}
},
error: (err) => {
@@ -601,8 +612,7 @@ export const run = async (): Promise<void> => {
}
}
} catch (error) {
console.error('Error restarting all processes:', error.message);
process.exit(1);
handleDaemonError(error, 'restart all processes');
}
},
error: (err) => {
@@ -630,19 +640,49 @@ export const run = async (): Promise<void> => {
return;
}
console.log('Starting TSPM daemon...');
await tspmIpcClient.connect();
console.log('✓ TSPM daemon started successfully');
console.log('Starting TSPM daemon manually...');
// Import spawn to start daemon process
const { spawn } = await import('child_process');
const daemonScript = plugins.path.join(
paths.packageDir,
'dist_ts',
'daemon.js',
);
// Start daemon as a regular background process (not detached)
const daemonProcess = spawn(process.execPath, [daemonScript], {
stdio: 'ignore',
env: {
...process.env,
TSPM_DAEMON_MODE: 'true',
},
});
console.log(`Started daemon with PID: ${daemonProcess.pid}`);
// Wait for daemon to be ready
await new Promise(resolve => setTimeout(resolve, 2000));
const newStatus = await tspmIpcClient.getDaemonStatus();
if (newStatus) {
console.log('✓ TSPM daemon started successfully');
console.log(` PID: ${newStatus.pid}`);
console.log('\nNote: This daemon will run until you stop it or logout.');
console.log('For automatic startup, use "tspm enable" instead.');
}
} catch (error) {
console.error('Error starting daemon:', error.message);
process.exit(1);
}
break;
case 'start-service':
// This is called by systemd - start the daemon directly
console.log('Starting TSPM daemon for systemd service...');
const { startDaemon } = await import('./classes.daemon.js');
await startDaemon();
break;
case 'stop':
try {
@@ -676,6 +716,9 @@ export const run = async (): Promise<void> => {
`Memory: ${formatMemory(status.memoryUsage || 0)}`,
);
console.log(`CPU: ${status.cpuUsage?.toFixed(1) || 0}s`);
// Disconnect from daemon after getting status
await tspmIpcClient.disconnect();
} catch (error) {
console.error('Error getting daemon status:', error.message);
process.exit(1);
@@ -697,6 +740,58 @@ export const run = async (): Promise<void> => {
complete: () => {},
});
// Enable command - Enable TSPM daemon as systemd service
smartcliInstance.addCommand('enable').subscribe({
next: async (argvArg: CliArguments) => {
try {
const serviceManager = new TspmServiceManager();
console.log('Enabling TSPM daemon as system service...');
await serviceManager.enableService();
console.log('✓ TSPM daemon enabled and started as system service');
console.log(' The daemon will now start automatically on system boot');
console.log(' Use "tspm disable" to remove the service');
} catch (error) {
console.error('Error enabling service:', error.message);
if (error.message.includes('permission') || error.message.includes('denied')) {
console.log('\nNote: You may need to run this command with sudo');
}
process.exit(1);
}
},
error: (err) => {
cliLogger.error(err);
},
complete: () => {},
});
// Disable command - Disable TSPM daemon systemd service
smartcliInstance.addCommand('disable').subscribe({
next: async (argvArg: CliArguments) => {
try {
const serviceManager = new TspmServiceManager();
console.log('Disabling TSPM daemon service...');
await serviceManager.disableService();
console.log('✓ TSPM daemon service disabled');
console.log(' The daemon will no longer start on system boot');
console.log(' Use "tspm enable" to re-enable the service');
} catch (error) {
console.error('Error disabling service:', error.message);
if (error.message.includes('permission') || error.message.includes('denied')) {
console.log('\nNote: You may need to run this command with sudo');
}
process.exit(1);
}
},
error: (err) => {
cliLogger.error(err);
},
complete: () => {},
});
// Start parsing commands
smartcliInstance.startParse();
};