Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
a67d247e9c | |||
f7bc56e676 | |||
7bfda01768 | |||
27384d03c7 | |||
47afd4739a | |||
4db128edaf |
22
changelog.md
22
changelog.md
@@ -1,5 +1,27 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-08-29 - 4.1.1 - fix(daemon)
|
||||||
|
Bump @push.rocks/smartdaemon to ^2.0.9
|
||||||
|
|
||||||
|
- Update @push.rocks/smartdaemon from ^2.0.8 to ^2.0.9 (dependency version bump)
|
||||||
|
|
||||||
|
## 2025-08-29 - 4.1.0 - feat(cli)
|
||||||
|
Add support for restarting all processes from CLI; improve usage message and reporting
|
||||||
|
|
||||||
|
- CLI 'restart' command now accepts 'all' to restart all processes via the daemon (tspm restart all).
|
||||||
|
- Improved usage/help output when no process id is provided.
|
||||||
|
- CLI now prints summaries of restarted process IDs and failed restarts and sets a non-zero exit code when any restarts failed.
|
||||||
|
|
||||||
|
## 2025-08-29 - 4.0.0 - BREAKING CHANGE(cli)
|
||||||
|
Add persistent process registration (tspm add), alias remove, and change start to use saved process IDs (breaking CLI behavior)
|
||||||
|
|
||||||
|
- Add a new CLI command `tspm add` that registers a process configuration without starting it; daemon assigns a sequential numeric ID and returns the stored config.
|
||||||
|
- Change `tspm start` to accept a process ID and start the saved configuration instead of accepting ad-hoc commands/files. This is a breaking change to the CLI contract.
|
||||||
|
- Add `remove` as an alias for the existing `delete` command; both CLI and daemon now support `remove` which stops and deletes the stored process.
|
||||||
|
- Daemon and IPC protocol updated to support `add` and `remove` methods; shared IPC types extended accordingly.
|
||||||
|
- ProcessManager: implemented add() and getNextSequentialId() to persist configs and produce numeric IDs.
|
||||||
|
- CLI registration updated (registerIpcCommand) to accept multiple command names, enabling aliases for commands.
|
||||||
|
|
||||||
## 2025-08-29 - 3.1.3 - fix(client)
|
## 2025-08-29 - 3.1.3 - fix(client)
|
||||||
Improve IPC client robustness and daemon debug logging; update tests and package metadata
|
Improve IPC client robustness and daemon debug logging; update tests and package metadata
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/tspm",
|
"name": "@git.zone/tspm",
|
||||||
"version": "3.1.3",
|
"version": "4.1.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "a no fuzz process manager",
|
"description": "a no fuzz process manager",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"@push.rocks/npmextra": "^5.3.3",
|
"@push.rocks/npmextra": "^5.3.3",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/smartcli": "^4.0.11",
|
"@push.rocks/smartcli": "^4.0.11",
|
||||||
"@push.rocks/smartdaemon": "^2.0.8",
|
"@push.rocks/smartdaemon": "^2.0.9",
|
||||||
"@push.rocks/smartipc": "^2.2.1",
|
"@push.rocks/smartipc": "^2.2.1",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"pidusage": "^4.0.1",
|
"pidusage": "^4.0.1",
|
||||||
|
77
pnpm-lock.yaml
generated
77
pnpm-lock.yaml
generated
@@ -18,8 +18,8 @@ importers:
|
|||||||
specifier: ^4.0.11
|
specifier: ^4.0.11
|
||||||
version: 4.0.11
|
version: 4.0.11
|
||||||
'@push.rocks/smartdaemon':
|
'@push.rocks/smartdaemon':
|
||||||
specifier: ^2.0.8
|
specifier: ^2.0.9
|
||||||
version: 2.0.8
|
version: 2.0.9
|
||||||
'@push.rocks/smartipc':
|
'@push.rocks/smartipc':
|
||||||
specifier: ^2.2.1
|
specifier: ^2.2.1
|
||||||
version: 2.2.1
|
version: 2.2.1
|
||||||
@@ -755,8 +755,8 @@ packages:
|
|||||||
'@push.rocks/smartcrypto@2.0.4':
|
'@push.rocks/smartcrypto@2.0.4':
|
||||||
resolution: {integrity: sha512-1+/5bsjyataf5uUkUNnnVXGRAt+gHVk1KDzozjTqgqJxHvQk1d9fVDohL6CxUhUucTPtu5VR5xNBiV8YCDuGyw==}
|
resolution: {integrity: sha512-1+/5bsjyataf5uUkUNnnVXGRAt+gHVk1KDzozjTqgqJxHvQk1d9fVDohL6CxUhUucTPtu5VR5xNBiV8YCDuGyw==}
|
||||||
|
|
||||||
'@push.rocks/smartdaemon@2.0.8':
|
'@push.rocks/smartdaemon@2.0.9':
|
||||||
resolution: {integrity: sha512-92qCS8XqGhQrCBDrz5L+WrWzlAggy93mXacVx9zEzGK41QwxRxZSMfxEMTxq4FO9YD4Kymffesav7S3ivCuJeQ==}
|
resolution: {integrity: sha512-TJd2N/vMAY3qpuy7ub0btNsSqdy7oU/hF/D+BbmfJVAiTKpvlgtCXKE5POwfuee03SONyh8LuH5Ey1ycIpsEHA==}
|
||||||
|
|
||||||
'@push.rocks/smartdata@5.16.4':
|
'@push.rocks/smartdata@5.16.4':
|
||||||
resolution: {integrity: sha512-COiKw8yk9iAcLN44WmZHG8Gi0v+HGkgM8Osoq7Cns+UsOA+grPepqbN2r0XPG1fm5vOdJcaydi2ZU0xrnbGVvQ==}
|
resolution: {integrity: sha512-COiKw8yk9iAcLN44WmZHG8Gi0v+HGkgM8Osoq7Cns+UsOA+grPepqbN2r0XPG1fm5vOdJcaydi2ZU0xrnbGVvQ==}
|
||||||
@@ -842,6 +842,9 @@ packages:
|
|||||||
'@push.rocks/smartmongo@2.0.12':
|
'@push.rocks/smartmongo@2.0.12':
|
||||||
resolution: {integrity: sha512-NglYiO14BikxnlvW6JF18FtopBtaWQEGAtPxHmmSCbyhU8Mi0aEFO7VgCasE9Kguba/wcR597qhcDEdcpBg1eQ==}
|
resolution: {integrity: sha512-NglYiO14BikxnlvW6JF18FtopBtaWQEGAtPxHmmSCbyhU8Mi0aEFO7VgCasE9Kguba/wcR597qhcDEdcpBg1eQ==}
|
||||||
|
|
||||||
|
'@push.rocks/smartnetwork@3.0.2':
|
||||||
|
resolution: {integrity: sha512-s6CNGzQ1n/d/6cOKXbxeW6/tO//dr1woLqI01g7XhqTriw0nsm2G2kWaZh2J0VOguGNWBgQVCIpR0LjdRNWb3g==}
|
||||||
|
|
||||||
'@push.rocks/smartnetwork@4.1.2':
|
'@push.rocks/smartnetwork@4.1.2':
|
||||||
resolution: {integrity: sha512-TjucG72ooHgzAUpNu2LAv4iFoettmZq2aEWhhzIa7AKcOvt4yxsk3Vl73guhKRohTfhdRauPcH5OHISLUHJbYA==}
|
resolution: {integrity: sha512-TjucG72ooHgzAUpNu2LAv4iFoettmZq2aEWhhzIa7AKcOvt4yxsk3Vl73guhKRohTfhdRauPcH5OHISLUHJbYA==}
|
||||||
|
|
||||||
@@ -923,8 +926,8 @@ packages:
|
|||||||
'@push.rocks/smartstring@4.0.15':
|
'@push.rocks/smartstring@4.0.15':
|
||||||
resolution: {integrity: sha512-NTNeOjWyg+aHtBTiQEyXamr7oTvYZ3wS1fudHo9ua7CLrykpK+i+RxFyJaLg1zB5x9xQF3NLEQecB14HPFX8Cg==}
|
resolution: {integrity: sha512-NTNeOjWyg+aHtBTiQEyXamr7oTvYZ3wS1fudHo9ua7CLrykpK+i+RxFyJaLg1zB5x9xQF3NLEQecB14HPFX8Cg==}
|
||||||
|
|
||||||
'@push.rocks/smartsystem@3.0.1':
|
'@push.rocks/smartsystem@3.0.7':
|
||||||
resolution: {integrity: sha512-+W9AiSJWcRAjthqDFT8rDli2+5k3bk8c9Psndy3uKN2YbaQkMZwWptZRI3WgpXMG9NhsjF8XrkyiH/xHv9AxzQ==}
|
resolution: {integrity: sha512-FSzrJKY+pAIxlPR1cQgUd/Edy82UDusl4n2aA+Fe564Qf7KHfFY9sTapjX1JJU6zP/hmBKWzApKa7/m+qF6Tog==}
|
||||||
|
|
||||||
'@push.rocks/smarttime@4.1.1':
|
'@push.rocks/smarttime@4.1.1':
|
||||||
resolution: {integrity: sha512-Ha/3J/G+zfTl4ahpZgF6oUOZnUjpLhrBja0OQ2cloFxF9sKT8I1COaSqIfBGDtoK2Nly4UD4aTJ3JcJNOg/kgA==}
|
resolution: {integrity: sha512-Ha/3J/G+zfTl4ahpZgF6oUOZnUjpLhrBja0OQ2cloFxF9sKT8I1COaSqIfBGDtoK2Nly4UD4aTJ3JcJNOg/kgA==}
|
||||||
@@ -998,10 +1001,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-PLvBNVeuY9BERNLq3PFDkhnHHc0RpilEGHd4aUI5XRFlZF++LETdLxPbxw+DHbvHlkUf/nep09f7rrL9Tqub1Q==}
|
resolution: {integrity: sha512-PLvBNVeuY9BERNLq3PFDkhnHHc0RpilEGHd4aUI5XRFlZF++LETdLxPbxw+DHbvHlkUf/nep09f7rrL9Tqub1Q==}
|
||||||
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartmatch
|
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartmatch
|
||||||
|
|
||||||
'@pushrocks/smartnetwork@3.0.2':
|
|
||||||
resolution: {integrity: sha512-XKVeTzf22IRgAvY9m8naFlsjh5yYVCU4/Dqi7XnxQUVfrnrcNIJVo+9JIYjQetLbHiUOHAnthlZVP5yXppOxyw==}
|
|
||||||
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartnetwork
|
|
||||||
|
|
||||||
'@pushrocks/smartping@1.0.8':
|
'@pushrocks/smartping@1.0.8':
|
||||||
resolution: {integrity: sha512-VM2gfS1sTuycj/jHyDa0lDntkPe7/JT0b2kGsy265RkichAJZkoEp3fboRJH/WAdzM8T4Du64JYgZkc8v2HHQg==}
|
resolution: {integrity: sha512-VM2gfS1sTuycj/jHyDa0lDntkPe7/JT0b2kGsy265RkichAJZkoEp3fboRJH/WAdzM8T4Du64JYgZkc8v2HHQg==}
|
||||||
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartping
|
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartping
|
||||||
@@ -4140,14 +4139,14 @@ packages:
|
|||||||
symbol-tree@3.2.4:
|
symbol-tree@3.2.4:
|
||||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||||
|
|
||||||
systeminformation@5.25.11:
|
systeminformation@5.27.7:
|
||||||
resolution: {integrity: sha512-jI01fn/t47rrLTQB0FTlMCC+5dYx8o0RRF+R4BPiUNsvg5OdY0s9DKMFmJGrx5SwMZQ4cag0Gl6v8oycso9b/g==}
|
resolution: {integrity: sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
|
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
systeminformation@5.27.7:
|
systeminformation@5.27.8:
|
||||||
resolution: {integrity: sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==}
|
resolution: {integrity: sha512-d3Z0gaQO1MlUxzDUKsmXz5y4TOBCMZ8IyijzaYOykV3AcNOTQ7mT+tpndUOXYNSxzLK3la8G32xiUFvZ0/s6PA==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
|
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -6074,16 +6073,16 @@ snapshots:
|
|||||||
'@types/node-forge': 1.3.14
|
'@types/node-forge': 1.3.14
|
||||||
node-forge: 1.3.1
|
node-forge: 1.3.1
|
||||||
|
|
||||||
'@push.rocks/smartdaemon@2.0.8':
|
'@push.rocks/smartdaemon@2.0.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.1.0
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartfile': 11.2.0
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smartfm': 2.2.2
|
'@push.rocks/smartfm': 2.2.2
|
||||||
'@push.rocks/smartlog': 3.0.7
|
'@push.rocks/smartlog': 3.1.8
|
||||||
'@push.rocks/smartlog-destination-local': 9.0.2
|
'@push.rocks/smartlog-destination-local': 9.0.2
|
||||||
'@push.rocks/smartpath': 5.1.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartshell': 3.2.3
|
'@push.rocks/smartshell': 3.3.0
|
||||||
'@push.rocks/smartsystem': 3.0.1
|
'@push.rocks/smartsystem': 3.0.7
|
||||||
|
|
||||||
'@push.rocks/smartdata@5.16.4(@aws-sdk/credential-providers@3.758.0)(socks@2.8.7)':
|
'@push.rocks/smartdata@5.16.4(@aws-sdk/credential-providers@3.758.0)(socks@2.8.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6318,6 +6317,16 @@ snapshots:
|
|||||||
- socks
|
- socks
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@push.rocks/smartnetwork@3.0.2':
|
||||||
|
dependencies:
|
||||||
|
'@pushrocks/smartping': 1.0.8
|
||||||
|
'@pushrocks/smartpromise': 3.1.10
|
||||||
|
'@pushrocks/smartstring': 4.0.7
|
||||||
|
'@types/default-gateway': 3.0.1
|
||||||
|
isopen: 1.3.0
|
||||||
|
public-ip: 6.0.2
|
||||||
|
systeminformation: 5.27.8
|
||||||
|
|
||||||
'@push.rocks/smartnetwork@4.1.2':
|
'@push.rocks/smartnetwork@4.1.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartping': 1.0.8
|
'@push.rocks/smartping': 1.0.8
|
||||||
@@ -6559,13 +6568,13 @@ snapshots:
|
|||||||
strip-indent: 4.0.0
|
strip-indent: 4.0.0
|
||||||
url: 0.11.4
|
url: 0.11.4
|
||||||
|
|
||||||
'@push.rocks/smartsystem@3.0.1':
|
'@push.rocks/smartsystem@3.0.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@pushrocks/lik': 6.0.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@pushrocks/smartenv': 5.0.5
|
'@push.rocks/smartenv': 5.0.13
|
||||||
'@pushrocks/smartnetwork': 3.0.2
|
'@push.rocks/smartnetwork': 3.0.2
|
||||||
'@pushrocks/smartpromise': 3.1.10
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
systeminformation: 5.25.11
|
systeminformation: 5.27.8
|
||||||
|
|
||||||
'@push.rocks/smarttime@4.1.1':
|
'@push.rocks/smarttime@4.1.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6721,16 +6730,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
matcher: 5.0.0
|
matcher: 5.0.0
|
||||||
|
|
||||||
'@pushrocks/smartnetwork@3.0.2':
|
|
||||||
dependencies:
|
|
||||||
'@pushrocks/smartping': 1.0.8
|
|
||||||
'@pushrocks/smartpromise': 3.1.10
|
|
||||||
'@pushrocks/smartstring': 4.0.7
|
|
||||||
'@types/default-gateway': 3.0.1
|
|
||||||
isopen: 1.3.0
|
|
||||||
public-ip: 6.0.2
|
|
||||||
systeminformation: 5.25.11
|
|
||||||
|
|
||||||
'@pushrocks/smartping@1.0.8':
|
'@pushrocks/smartping@1.0.8':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/ping': 0.4.4
|
'@types/ping': 0.4.4
|
||||||
@@ -10503,10 +10502,10 @@ snapshots:
|
|||||||
|
|
||||||
symbol-tree@3.2.4: {}
|
symbol-tree@3.2.4: {}
|
||||||
|
|
||||||
systeminformation@5.25.11: {}
|
|
||||||
|
|
||||||
systeminformation@5.27.7: {}
|
systeminformation@5.27.7: {}
|
||||||
|
|
||||||
|
systeminformation@5.27.8: {}
|
||||||
|
|
||||||
tar-fs@3.1.0:
|
tar-fs@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pump: 3.0.3
|
pump: 3.0.3
|
||||||
|
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/tspm',
|
name: '@git.zone/tspm',
|
||||||
version: '3.1.3',
|
version: '4.1.1',
|
||||||
description: 'a no fuzz process manager'
|
description: 'a no fuzz process manager'
|
||||||
}
|
}
|
||||||
|
91
ts/cli/commands/process/add.ts
Normal file
91
ts/cli/commands/process/add.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import * as plugins from '../../../plugins.js';
|
||||||
|
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
|
||||||
|
import type { CliArguments } from '../../types.js';
|
||||||
|
import { parseMemoryString, formatMemory } from '../../helpers/memory.js';
|
||||||
|
import { registerIpcCommand } from '../../registration/index.js';
|
||||||
|
|
||||||
|
export function registerAddCommand(smartcli: plugins.smartcli.Smartcli) {
|
||||||
|
registerIpcCommand(
|
||||||
|
smartcli,
|
||||||
|
'add',
|
||||||
|
async (argvArg: CliArguments) => {
|
||||||
|
const args = argvArg._.slice(1);
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.error('Error: Please provide a command or .ts file');
|
||||||
|
console.log('Usage: tspm add <command|file.ts> [options]');
|
||||||
|
console.log('\nOptions:');
|
||||||
|
console.log(' --name <name> Optional name');
|
||||||
|
console.log(' --memory <size> Memory limit (e.g., 512MB, 2GB)');
|
||||||
|
console.log(' --cwd <path> Working directory');
|
||||||
|
console.log(' --watch Watch for file changes');
|
||||||
|
console.log(' --watch-paths <paths> Comma-separated paths');
|
||||||
|
console.log(' --autorestart Auto-restart on crash (default true)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = args.join(' ');
|
||||||
|
const projectDir = argvArg.cwd || process.cwd();
|
||||||
|
const memoryLimit = argvArg.memory
|
||||||
|
? parseMemoryString(argvArg.memory)
|
||||||
|
: 512 * 1024 * 1024;
|
||||||
|
|
||||||
|
// Resolve .ts single-file execution via tsx if needed
|
||||||
|
const parts = script.split(' ');
|
||||||
|
const first = parts[0];
|
||||||
|
let command = script;
|
||||||
|
let cmdArgs: string[] | undefined;
|
||||||
|
if (parts.length === 1 && first.endsWith('.ts')) {
|
||||||
|
try {
|
||||||
|
const { createRequire } = await import('module');
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const tsxPath = require.resolve('tsx/dist/cli.mjs');
|
||||||
|
const filePath = plugins.path.isAbsolute(first)
|
||||||
|
? first
|
||||||
|
: plugins.path.join(projectDir, first);
|
||||||
|
command = tsxPath;
|
||||||
|
cmdArgs = [filePath];
|
||||||
|
} catch {
|
||||||
|
command = 'tsx';
|
||||||
|
cmdArgs = [first];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = argvArg.name || script;
|
||||||
|
const watch = argvArg.watch || false;
|
||||||
|
const autorestart = argvArg.autorestart !== false;
|
||||||
|
const watchPaths = argvArg.watchPaths
|
||||||
|
? typeof argvArg.watchPaths === 'string'
|
||||||
|
? (argvArg.watchPaths as string).split(',')
|
||||||
|
: argvArg.watchPaths
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
console.log('Adding process configuration:');
|
||||||
|
console.log(` Command: ${script}${parts.length === 1 && first.endsWith('.ts') ? ' (via tsx)' : ''}`);
|
||||||
|
console.log(` Directory: ${projectDir}`);
|
||||||
|
console.log(` Memory limit: ${formatMemory(memoryLimit)}`);
|
||||||
|
console.log(` Auto-restart: ${autorestart}`);
|
||||||
|
if (watch) {
|
||||||
|
console.log(` Watch: enabled`);
|
||||||
|
if (watchPaths) console.log(` Watch paths: ${watchPaths.join(',')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await tspmIpcClient.request('add', {
|
||||||
|
config: {
|
||||||
|
name,
|
||||||
|
command,
|
||||||
|
args: cmdArgs,
|
||||||
|
projectDir,
|
||||||
|
memoryLimitBytes: memoryLimit,
|
||||||
|
autorestart,
|
||||||
|
watch,
|
||||||
|
watchPaths,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✓ Added');
|
||||||
|
console.log(` Assigned ID: ${response.id}`);
|
||||||
|
},
|
||||||
|
{ actionLabel: 'add process config' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@@ -6,24 +6,27 @@ import { registerIpcCommand } from '../../registration/index.js';
|
|||||||
export function registerDeleteCommand(smartcli: plugins.smartcli.Smartcli) {
|
export function registerDeleteCommand(smartcli: plugins.smartcli.Smartcli) {
|
||||||
registerIpcCommand(
|
registerIpcCommand(
|
||||||
smartcli,
|
smartcli,
|
||||||
'delete',
|
['delete', 'remove'],
|
||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
const id = argvArg._[1];
|
const id = argvArg._[1];
|
||||||
if (!id) {
|
if (!id) {
|
||||||
console.error('Error: Please provide a process ID');
|
console.error('Error: Please provide a process ID');
|
||||||
console.log('Usage: tspm delete <id>');
|
console.log('Usage: tspm delete <id> | tspm remove <id>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Deleting process: ${id}`);
|
// Determine if command was 'remove' to use the new IPC route, otherwise 'delete'
|
||||||
const response = await tspmIpcClient.request('delete', { id });
|
const cmd = String(argvArg._[0]);
|
||||||
|
const useRemove = cmd === 'remove';
|
||||||
|
console.log(`${useRemove ? 'Removing' : 'Deleting'} process: ${id}`);
|
||||||
|
const response = await tspmIpcClient.request(useRemove ? 'remove' : 'delete', { id } as any);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
console.log(`✓ ${response.message}`);
|
console.log(`✓ ${response.message || (useRemove ? 'Removed successfully' : 'Deleted successfully')}`);
|
||||||
} else {
|
} else {
|
||||||
console.error(`✗ Failed to delete process: ${response.message}`);
|
console.error(`✗ Failed to ${useRemove ? 'remove' : 'delete'} process: ${response.message}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ actionLabel: 'delete process' },
|
{ actionLabel: 'delete/remove process' },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -8,13 +8,31 @@ export function registerRestartCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
smartcli,
|
smartcli,
|
||||||
'restart',
|
'restart',
|
||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
const id = argvArg._[1];
|
const arg = argvArg._[1];
|
||||||
if (!id) {
|
if (!arg) {
|
||||||
console.error('Error: Please provide a process ID');
|
console.error('Error: Please provide a process ID or "all"');
|
||||||
console.log('Usage: tspm restart <id>');
|
console.log('Usage:');
|
||||||
|
console.log(' tspm restart <id>');
|
||||||
|
console.log(' tspm restart all');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (String(arg).toLowerCase() === 'all') {
|
||||||
|
console.log('Restarting all processes...');
|
||||||
|
const res = await tspmIpcClient.request('restartAll', {});
|
||||||
|
if (res.restarted.length > 0) {
|
||||||
|
console.log(`✓ Restarted ${res.restarted.length} processes:`);
|
||||||
|
for (const id of res.restarted) console.log(` - ${id}`);
|
||||||
|
}
|
||||||
|
if (res.failed.length > 0) {
|
||||||
|
console.log(`✗ Failed to restart ${res.failed.length} processes:`);
|
||||||
|
for (const f of res.failed) console.log(` - ${f.id}: ${f.error}`);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = String(arg);
|
||||||
console.log(`Restarting process: ${id}`);
|
console.log(`Restarting process: ${id}`);
|
||||||
const response = await tspmIpcClient.request('restart', { id });
|
const response = await tspmIpcClient.request('restart', { id });
|
||||||
|
|
||||||
|
@@ -10,108 +10,22 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
smartcli,
|
smartcli,
|
||||||
'start',
|
'start',
|
||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
// Get all arguments after 'start' command
|
const id = argvArg._[1];
|
||||||
const commandArgs = argvArg._.slice(1);
|
if (!id) {
|
||||||
if (commandArgs.length === 0) {
|
console.error('Error: Please provide a process ID to start');
|
||||||
console.error('Error: Please provide a command to run');
|
console.log('Usage: tspm start <id>');
|
||||||
console.log('Usage: tspm start <command> [options]');
|
|
||||||
console.log('\nExamples:');
|
|
||||||
console.log(' tspm start "npm run dev"');
|
|
||||||
console.log(' tspm start pnpm start');
|
|
||||||
console.log(' tspm start node server.js');
|
|
||||||
console.log(' tspm start script.ts');
|
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join all command parts to form the full command
|
const desc = await tspmIpcClient.request('describe', { id }).catch(() => null);
|
||||||
const script = commandArgs.join(' ');
|
if (!desc) {
|
||||||
|
console.error(`Process with id '${id}' not found. Use 'tspm add' first.`);
|
||||||
const memoryLimit = argvArg.memory
|
return;
|
||||||
? parseMemoryString(argvArg.memory)
|
|
||||||
: 512 * 1024 * 1024;
|
|
||||||
const projectDir = argvArg.cwd || process.cwd();
|
|
||||||
|
|
||||||
// Parse the command to determine if we need to handle .ts files
|
|
||||||
let actualCommand: string;
|
|
||||||
let processArgs: string[] | undefined = undefined;
|
|
||||||
|
|
||||||
// Split the script to check if it's a single .ts file or a full command
|
|
||||||
const scriptParts = script.split(' ');
|
|
||||||
const firstPart = scriptParts[0];
|
|
||||||
|
|
||||||
// Check if this is a direct .ts file execution (single argument ending in .ts)
|
|
||||||
if (scriptParts.length === 1 && firstPart.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(firstPart)
|
|
||||||
? firstPart
|
|
||||||
: plugins.path.join(projectDir, firstPart);
|
|
||||||
actualCommand = tsxPath;
|
|
||||||
processArgs = [scriptPath];
|
|
||||||
} catch {
|
|
||||||
actualCommand = 'tsx';
|
|
||||||
processArgs = [firstPart];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For multi-word commands, use the entire script as the command
|
|
||||||
// This handles cases like "pnpm start", "npm run dev", etc.
|
|
||||||
actualCommand = script;
|
|
||||||
processArgs = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = argvArg.name || script;
|
console.log(`Starting process id ${id} (${desc.config.name || id})...`);
|
||||||
const watch = argvArg.watch || false;
|
const response = await tspmIpcClient.request('start', { config: desc.config });
|
||||||
const autorestart = argvArg.autorestart !== false; // default true
|
console.log('✓ Process started');
|
||||||
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: processArgs,
|
|
||||||
projectDir,
|
|
||||||
memoryLimitBytes: memoryLimit,
|
|
||||||
autorestart,
|
|
||||||
watch,
|
|
||||||
watchPaths,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(`Starting process: ${name}`);
|
|
||||||
console.log(
|
|
||||||
` Command: ${script}${scriptParts.length === 1 && firstPart.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(` ID: ${response.processId}`);
|
||||||
console.log(` PID: ${response.pid || 'N/A'}`);
|
console.log(` PID: ${response.pid || 'N/A'}`);
|
||||||
console.log(` Status: ${response.status}`);
|
console.log(` Status: ${response.status}`);
|
||||||
|
@@ -5,6 +5,7 @@ import { Logger, LogLevel } from '../shared/common/utils.errorhandler.js';
|
|||||||
// Import command registration functions
|
// Import command registration functions
|
||||||
import { registerDefaultCommand } from './commands/default.js';
|
import { registerDefaultCommand } from './commands/default.js';
|
||||||
import { registerStartCommand } from './commands/process/start.js';
|
import { registerStartCommand } from './commands/process/start.js';
|
||||||
|
import { registerAddCommand } from './commands/process/add.js';
|
||||||
import { registerStopCommand } from './commands/process/stop.js';
|
import { registerStopCommand } from './commands/process/stop.js';
|
||||||
import { registerRestartCommand } from './commands/process/restart.js';
|
import { registerRestartCommand } from './commands/process/restart.js';
|
||||||
import { registerDeleteCommand } from './commands/process/delete.js';
|
import { registerDeleteCommand } from './commands/process/delete.js';
|
||||||
@@ -43,6 +44,7 @@ export const run = async (): Promise<void> => {
|
|||||||
registerDefaultCommand(smartcliInstance);
|
registerDefaultCommand(smartcliInstance);
|
||||||
|
|
||||||
// Process commands
|
// Process commands
|
||||||
|
registerAddCommand(smartcliInstance);
|
||||||
registerStartCommand(smartcliInstance);
|
registerStartCommand(smartcliInstance);
|
||||||
registerStopCommand(smartcliInstance);
|
registerStopCommand(smartcliInstance);
|
||||||
registerRestartCommand(smartcliInstance);
|
registerRestartCommand(smartcliInstance);
|
||||||
|
@@ -17,13 +17,15 @@ import { ensureDaemonOrHint } from './daemon-check.js';
|
|||||||
*/
|
*/
|
||||||
export function registerIpcCommand(
|
export function registerIpcCommand(
|
||||||
smartcli: plugins.smartcli.Smartcli,
|
smartcli: plugins.smartcli.Smartcli,
|
||||||
name: string,
|
name: string | string[],
|
||||||
action: CommandAction,
|
action: CommandAction,
|
||||||
opts: IpcCommandOptions = {},
|
opts: IpcCommandOptions = {},
|
||||||
) {
|
) {
|
||||||
const { actionLabel = name, keepAlive = false, requireDaemon = true } = opts;
|
const names = Array.isArray(name) ? name : [name];
|
||||||
|
for (const singleName of names) {
|
||||||
|
const { actionLabel = singleName, keepAlive = false, requireDaemon = true } = opts;
|
||||||
|
|
||||||
smartcli.addCommand(name).subscribe({
|
smartcli.addCommand(singleName).subscribe({
|
||||||
next: async (argv: CliArguments) => {
|
next: async (argv: CliArguments) => {
|
||||||
// Early preflight for better UX
|
// Early preflight for better UX
|
||||||
const ok = await ensureDaemonOrHint(requireDaemon, actionLabel);
|
const ok = await ensureDaemonOrHint(requireDaemon, actionLabel);
|
||||||
@@ -57,7 +59,7 @@ export function registerIpcCommand(
|
|||||||
error: (err) => {
|
error: (err) => {
|
||||||
// Fallback error path (should be rare with try/catch in next)
|
// Fallback error path (should be rare with try/catch in next)
|
||||||
console.error(
|
console.error(
|
||||||
`Unexpected error in command "${name}":`,
|
`Unexpected error in command "${singleName}":`,
|
||||||
unknownError(err),
|
unknownError(err),
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -65,6 +67,7 @@ export function registerIpcCommand(
|
|||||||
complete: () => {},
|
complete: () => {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register local commands that don't require IPC/daemon connection
|
* Register local commands that don't require IPC/daemon connection
|
||||||
|
@@ -34,6 +34,42 @@ export class ProcessManager extends EventEmitter {
|
|||||||
this.loadProcessConfigs();
|
this.loadProcessConfigs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a process configuration without starting it.
|
||||||
|
* Returns the assigned numeric sequential id as string.
|
||||||
|
*/
|
||||||
|
public async add(configInput: Omit<IProcessConfig, 'id'> & { id?: string }): Promise<string> {
|
||||||
|
// Determine next numeric id
|
||||||
|
const nextId = this.getNextSequentialId();
|
||||||
|
|
||||||
|
const config: IProcessConfig = {
|
||||||
|
id: String(nextId),
|
||||||
|
name: configInput.name || `process-${nextId}`,
|
||||||
|
command: configInput.command,
|
||||||
|
args: configInput.args,
|
||||||
|
projectDir: configInput.projectDir,
|
||||||
|
memoryLimitBytes: configInput.memoryLimitBytes || 512 * 1024 * 1024,
|
||||||
|
monitorIntervalMs: configInput.monitorIntervalMs,
|
||||||
|
env: configInput.env,
|
||||||
|
logBufferSize: configInput.logBufferSize,
|
||||||
|
autorestart: configInput.autorestart ?? true,
|
||||||
|
watch: configInput.watch,
|
||||||
|
watchPaths: configInput.watchPaths,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store config and initial info
|
||||||
|
this.processConfigs.set(config.id, config);
|
||||||
|
this.processInfo.set(config.id, {
|
||||||
|
id: config.id,
|
||||||
|
status: 'stopped',
|
||||||
|
memory: 0,
|
||||||
|
restarts: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.saveProcessConfigs();
|
||||||
|
return config.id;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a new process with the given configuration
|
* Start a new process with the given configuration
|
||||||
*/
|
*/
|
||||||
@@ -342,6 +378,20 @@ export class ProcessManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute next sequential numeric id based on existing configs
|
||||||
|
*/
|
||||||
|
private getNextSequentialId(): number {
|
||||||
|
let maxId = 0;
|
||||||
|
for (const id of this.processConfigs.keys()) {
|
||||||
|
const n = parseInt(id, 10);
|
||||||
|
if (!isNaN(n)) {
|
||||||
|
maxId = Math.max(maxId, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxId + 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save all process configurations to config storage
|
* Save all process configurations to config storage
|
||||||
*/
|
*/
|
||||||
|
@@ -171,6 +171,31 @@ export class TspmDaemon {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Query handlers
|
// Query handlers
|
||||||
|
this.ipcServer.onMessage(
|
||||||
|
'add',
|
||||||
|
async (request: RequestForMethod<'add'>) => {
|
||||||
|
try {
|
||||||
|
const id = await this.tspmInstance.add(request.config as any);
|
||||||
|
const config = this.tspmInstance.processConfigs.get(id)!;
|
||||||
|
return { id, config };
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to add process: ${error.message}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.ipcServer.onMessage(
|
||||||
|
'remove',
|
||||||
|
async (request: RequestForMethod<'remove'>) => {
|
||||||
|
try {
|
||||||
|
await this.tspmInstance.delete(request.id);
|
||||||
|
return { success: true, message: `Process ${request.id} deleted successfully` };
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to remove process: ${error.message}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
this.ipcServer.onMessage(
|
this.ipcServer.onMessage(
|
||||||
'list',
|
'list',
|
||||||
async (request: RequestForMethod<'list'>) => {
|
async (request: RequestForMethod<'list'>) => {
|
||||||
|
@@ -200,12 +200,35 @@ export interface HeartbeatResponse {
|
|||||||
status: 'healthy' | 'degraded';
|
status: 'healthy' | 'degraded';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add (register config without starting)
|
||||||
|
export interface AddRequest {
|
||||||
|
// Optional id is ignored server-side if present; server assigns sequential id
|
||||||
|
config: Omit<IProcessConfig, 'id'> & { id?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddResponse {
|
||||||
|
id: string;
|
||||||
|
config: IProcessConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove (delete config and stop if running)
|
||||||
|
export interface RemoveRequest {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoveResponse {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Type mappings for methods
|
// Type mappings for methods
|
||||||
export type IpcMethodMap = {
|
export type IpcMethodMap = {
|
||||||
start: { request: StartRequest; response: StartResponse };
|
start: { request: StartRequest; response: StartResponse };
|
||||||
stop: { request: StopRequest; response: StopResponse };
|
stop: { request: StopRequest; response: StopResponse };
|
||||||
restart: { request: RestartRequest; response: RestartResponse };
|
restart: { request: RestartRequest; response: RestartResponse };
|
||||||
delete: { request: DeleteRequest; response: DeleteResponse };
|
delete: { request: DeleteRequest; response: DeleteResponse };
|
||||||
|
add: { request: AddRequest; response: AddResponse };
|
||||||
|
remove: { request: RemoveRequest; response: RemoveResponse };
|
||||||
list: { request: ListRequest; response: ListResponse };
|
list: { request: ListRequest; response: ListResponse };
|
||||||
describe: { request: DescribeRequest; response: DescribeResponse };
|
describe: { request: DescribeRequest; response: DescribeResponse };
|
||||||
getLogs: { request: GetLogsRequest; response: GetLogsResponse };
|
getLogs: { request: GetLogsRequest; response: GetLogsResponse };
|
||||||
|
Reference in New Issue
Block a user