Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
e507b75c40 | |||
97a8377a75 | |||
3676bff04c | |||
dfe0677cab |
0
.tspm_home/.npmextra/kv/@git.zone__tspm.json
Normal file
0
.tspm_home/.npmextra/kv/@git.zone__tspm.json
Normal file
16
changelog.md
16
changelog.md
@@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-08-29 - 4.4.2 - fix(daemon)
|
||||
Fix daemon IPC id handling, reload configs on demand and correct CLI daemon start path
|
||||
|
||||
- Normalize process IDs in daemon IPC handlers (trim strings) to avoid lookup mismatches
|
||||
- Attempt to reload saved process configurations when a startById request cannot find a config (handles races/stale state)
|
||||
- Use normalized IDs in responses and messages for stop/restart/delete/remove/describe handlers
|
||||
- Fix CLI daemon start path to point at dist_ts/daemon/tspm.daemon.js when launching the background daemon
|
||||
- Ensure the IPC client disconnects after showing CLI version/status to avoid leaked connections
|
||||
|
||||
## 2025-08-29 - 4.4.1 - fix(cli)
|
||||
Use server-side start-by-id flow for starting processes
|
||||
|
||||
- CLI: 'tspm start <id>' now calls a new 'startById' IPC method instead of fetching the full config via 'describe' and submitting it back to 'start'.
|
||||
- Daemon: Added server-side handler for 'startById' which resolves the stored process config and starts the process on the daemon.
|
||||
- Protocol: Added StartByIdRequest/StartByIdResponse types and registered 'startById' in the IPC method map.
|
||||
|
||||
## 2025-08-29 - 4.4.0 - feat(daemon)
|
||||
Persist desired process states and add daemon restart command
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@git.zone/tspm",
|
||||
"version": "4.4.0",
|
||||
"version": "4.4.2",
|
||||
"private": false,
|
||||
"description": "a no fuzz process manager",
|
||||
"main": "dist_ts/index.js",
|
||||
|
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tspm',
|
||||
version: '4.4.0',
|
||||
version: '4.4.2',
|
||||
description: 'a no fuzz process manager'
|
||||
}
|
||||
|
@@ -33,7 +33,8 @@ export function registerDaemonCommand(smartcli: plugins.smartcli.Smartcli) {
|
||||
const daemonScript = plugins.path.join(
|
||||
paths.packageDir,
|
||||
'dist_ts',
|
||||
'daemon.js',
|
||||
'daemon',
|
||||
'tspm.daemon.js',
|
||||
);
|
||||
|
||||
// Start daemon as a detached background process
|
||||
|
@@ -17,14 +17,8 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
|
||||
return;
|
||||
}
|
||||
|
||||
const desc = await tspmIpcClient.request('describe', { id }).catch(() => null);
|
||||
if (!desc) {
|
||||
console.error(`Process with id '${id}' not found. Use 'tspm add' first.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Starting process id ${id} (${desc.config.name || id})...`);
|
||||
const response = await tspmIpcClient.request('start', { config: desc.config });
|
||||
console.log(`Starting process id ${id}...`);
|
||||
const response = await tspmIpcClient.request('startById', { id });
|
||||
console.log('✓ Process started');
|
||||
console.log(` ID: ${response.processId}`);
|
||||
console.log(` PID: ${response.pid || 'N/A'}`);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { tspmIpcClient } from '../client/tspm.ipcclient.js';
|
||||
import * as paths from '../paths.js';
|
||||
import { Logger, LogLevel } from '../shared/common/utils.errorhandler.js';
|
||||
|
||||
@@ -38,6 +39,24 @@ export const run = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
const smartcliInstance = new plugins.smartcli.Smartcli();
|
||||
// Intercept -v/--version to show CLI and daemon versions
|
||||
const args = process.argv.slice(2);
|
||||
if (args.includes('-v') || args.includes('--version')) {
|
||||
const cliVersion = tspmProjectinfo.npm.version;
|
||||
console.log(`tspm CLI: ${cliVersion}`);
|
||||
const status = await tspmIpcClient.getDaemonStatus();
|
||||
if (status) {
|
||||
console.log(
|
||||
`Daemon: running v${status.version || 'unknown'} (pid ${status.pid})`,
|
||||
);
|
||||
} else {
|
||||
console.log('Daemon: not running');
|
||||
}
|
||||
// Ensure we disconnect any IPC client connection used for status
|
||||
try { await tspmIpcClient.disconnect(); } catch {}
|
||||
return; // do not start parser
|
||||
}
|
||||
// Keep Smartcli version info for help output but not used for -v now
|
||||
smartcliInstance.addVersion(tspmProjectinfo.npm.version);
|
||||
|
||||
// Register all commands
|
||||
|
@@ -20,12 +20,20 @@ export class TspmDaemon {
|
||||
private socketPath: string;
|
||||
private heartbeatInterval: NodeJS.Timeout | null = null;
|
||||
private daemonPidFile: string;
|
||||
private version: string;
|
||||
|
||||
constructor() {
|
||||
this.tspmInstance = new ProcessManager();
|
||||
this.socketPath = plugins.path.join(paths.tspmDir, 'tspm.sock');
|
||||
this.daemonPidFile = plugins.path.join(paths.tspmDir, 'daemon.pid');
|
||||
this.startTime = Date.now();
|
||||
// Determine daemon version from package metadata
|
||||
try {
|
||||
const proj = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
||||
this.version = proj.npm.version || 'unknown';
|
||||
} catch {
|
||||
this.version = 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,15 +136,45 @@ export class TspmDaemon {
|
||||
},
|
||||
);
|
||||
|
||||
// Start by id (resolve config on server)
|
||||
this.ipcServer.onMessage(
|
||||
'startById',
|
||||
async (request: RequestForMethod<'startById'>) => {
|
||||
try {
|
||||
const id = String(request.id).trim();
|
||||
let config = this.tspmInstance.processConfigs.get(id);
|
||||
if (!config) {
|
||||
// Try to reload configs if not found (handles races or stale state)
|
||||
await this.tspmInstance.loadProcessConfigs();
|
||||
config = this.tspmInstance.processConfigs.get(id) || null as any;
|
||||
}
|
||||
if (!config) {
|
||||
throw new Error(`Process ${id} not found`);
|
||||
}
|
||||
await this.tspmInstance.setDesiredState(id, 'online');
|
||||
await this.tspmInstance.start(config);
|
||||
const processInfo = this.tspmInstance.processInfo.get(id);
|
||||
return {
|
||||
processId: id,
|
||||
pid: processInfo?.pid,
|
||||
status: processInfo?.status || 'stopped',
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to start process: ${error.message}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.ipcServer.onMessage(
|
||||
'stop',
|
||||
async (request: RequestForMethod<'stop'>) => {
|
||||
try {
|
||||
await this.tspmInstance.setDesiredState(request.id, 'stopped');
|
||||
await this.tspmInstance.stop(request.id);
|
||||
const id = String(request.id).trim();
|
||||
await this.tspmInstance.setDesiredState(id, 'stopped');
|
||||
await this.tspmInstance.stop(id);
|
||||
return {
|
||||
success: true,
|
||||
message: `Process ${request.id} stopped successfully`,
|
||||
message: `Process ${id} stopped successfully`,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to stop process: ${error.message}`);
|
||||
@@ -148,11 +186,12 @@ export class TspmDaemon {
|
||||
'restart',
|
||||
async (request: RequestForMethod<'restart'>) => {
|
||||
try {
|
||||
await this.tspmInstance.setDesiredState(request.id, 'online');
|
||||
await this.tspmInstance.restart(request.id);
|
||||
const processInfo = this.tspmInstance.processInfo.get(request.id);
|
||||
const id = String(request.id).trim();
|
||||
await this.tspmInstance.setDesiredState(id, 'online');
|
||||
await this.tspmInstance.restart(id);
|
||||
const processInfo = this.tspmInstance.processInfo.get(id);
|
||||
return {
|
||||
processId: request.id,
|
||||
processId: id,
|
||||
pid: processInfo?.pid,
|
||||
status: processInfo?.status || 'stopped',
|
||||
};
|
||||
@@ -166,10 +205,11 @@ export class TspmDaemon {
|
||||
'delete',
|
||||
async (request: RequestForMethod<'delete'>) => {
|
||||
try {
|
||||
await this.tspmInstance.delete(request.id);
|
||||
const id = String(request.id).trim();
|
||||
await this.tspmInstance.delete(id);
|
||||
return {
|
||||
success: true,
|
||||
message: `Process ${request.id} deleted successfully`,
|
||||
message: `Process ${id} deleted successfully`,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to delete process: ${error.message}`);
|
||||
@@ -195,8 +235,9 @@ export class TspmDaemon {
|
||||
'remove',
|
||||
async (request: RequestForMethod<'remove'>) => {
|
||||
try {
|
||||
await this.tspmInstance.delete(request.id);
|
||||
return { success: true, message: `Process ${request.id} deleted successfully` };
|
||||
const id = String(request.id).trim();
|
||||
await this.tspmInstance.delete(id);
|
||||
return { success: true, message: `Process ${id} deleted successfully` };
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to remove process: ${error.message}`);
|
||||
}
|
||||
@@ -214,9 +255,10 @@ export class TspmDaemon {
|
||||
this.ipcServer.onMessage(
|
||||
'describe',
|
||||
async (request: RequestForMethod<'describe'>) => {
|
||||
const result = await this.tspmInstance.describe(request.id);
|
||||
const id = String(request.id).trim();
|
||||
const result = await this.tspmInstance.describe(id);
|
||||
if (!result) {
|
||||
throw new Error(`Process ${request.id} not found`);
|
||||
throw new Error(`Process ${id} not found`);
|
||||
}
|
||||
// Return correctly shaped response
|
||||
return {
|
||||
@@ -321,6 +363,7 @@ export class TspmDaemon {
|
||||
processCount: this.tspmInstance.processes.size,
|
||||
memoryUsage: memUsage.heapUsed,
|
||||
cpuUsage: process.cpuUsage().user / 1000000, // Convert to seconds
|
||||
version: this.version,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
@@ -66,6 +66,17 @@ export interface StartResponse {
|
||||
status: 'online' | 'stopped' | 'errored';
|
||||
}
|
||||
|
||||
// Start by id (server resolves config)
|
||||
export interface StartByIdRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface StartByIdResponse {
|
||||
processId: string;
|
||||
pid?: number;
|
||||
status: 'online' | 'stopped' | 'errored';
|
||||
}
|
||||
|
||||
// Stop command
|
||||
export interface StopRequest {
|
||||
id: string;
|
||||
@@ -191,6 +202,7 @@ export interface DaemonStatusResponse {
|
||||
processCount: number;
|
||||
memoryUsage?: number;
|
||||
cpuUsage?: number;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
// Daemon shutdown command
|
||||
@@ -238,6 +250,7 @@ export interface RemoveResponse {
|
||||
// Type mappings for methods
|
||||
export type IpcMethodMap = {
|
||||
start: { request: StartRequest; response: StartResponse };
|
||||
startById: { request: StartByIdRequest; response: StartByIdResponse };
|
||||
stop: { request: StopRequest; response: StopResponse };
|
||||
restart: { request: RestartRequest; response: RestartResponse };
|
||||
delete: { request: DeleteRequest; response: DeleteResponse };
|
||||
|
Reference in New Issue
Block a user