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

View File

@@ -1,6 +1,6 @@
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import { spawn } from 'child_process';
import type {
IpcMethodMap,
RequestForMethod,
@@ -34,10 +34,12 @@ export class TspmIpcClient {
const daemonRunning = await this.isDaemonRunning();
if (!daemonRunning) {
console.log('Daemon not running, starting it...');
await this.startDaemon();
// Wait a bit for daemon to initialize
await new Promise((resolve) => setTimeout(resolve, 1000));
throw new Error(
'TSPM daemon is not running.\n\n' +
'To start the daemon, run one of:\n' +
' tspm daemon start - Start daemon for this session\n' +
' tspm enable - Enable daemon as system service (recommended)\n'
);
}
// Create IPC client
@@ -75,7 +77,7 @@ export class TspmIpcClient {
} catch (error) {
console.error('Failed to connect to daemon:', error);
throw new Error(
'Could not connect to TSPM daemon. Please try running "tspm daemon start" manually.',
'Could not connect to TSPM daemon. Please try running "tspm daemon start" or "tspm enable".',
);
}
}
@@ -99,7 +101,10 @@ export class TspmIpcClient {
params: RequestForMethod<M>,
): Promise<ResponseForMethod<M>> {
if (!this.isConnected || !this.ipcClient) {
await this.connect();
throw new Error(
'Not connected to TSPM daemon.\n' +
'Run "tspm daemon start" or "tspm enable" first.'
);
}
try {
@@ -110,22 +115,7 @@ export class TspmIpcClient {
return response;
} catch (error) {
// Handle connection errors by trying to reconnect once
if (
error.message?.includes('ECONNREFUSED') ||
error.message?.includes('ENOENT')
) {
console.log('Connection lost, attempting to reconnect...');
this.isConnected = false;
await this.connect();
// Retry the request
return await this.ipcClient!.request<
RequestForMethod<M>,
ResponseForMethod<M>
>(method, params);
}
// Don't try to auto-reconnect, just throw the error
throw error;
}
}
@@ -195,41 +185,7 @@ export class TspmIpcClient {
}
}
/**
* Start the daemon process
*/
private async startDaemon(): Promise<void> {
const daemonScript = plugins.path.join(
paths.packageDir,
'dist_ts',
'daemon.js',
);
// Spawn the daemon as a detached process
const daemonProcess = spawn(process.execPath, [daemonScript], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore'],
env: {
...process.env,
TSPM_DAEMON_MODE: 'true',
},
});
// Unref the process so the parent can exit
daemonProcess.unref();
console.log(`Started daemon process with PID: ${daemonProcess.pid}`);
// Wait for daemon to be ready using SmartIPC's helper
try {
await plugins.smartipc.SmartIpc.waitForServer({
socketPath: this.socketPath,
timeoutMs: 15000,
});
} catch (error) {
throw new Error(`Daemon failed to start: ${error.message}`);
}
}
/**
* Stop the daemon