Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
f4cbdd51e1 | |||
1340c1c248 | |||
92a6ecac71 | |||
5e26b0ab5f | |||
e09cf38f30 | |||
c694672438 | |||
3b21a338fb | |||
28680309ad | |||
833573eb10 | |||
ebc20a9232 |
45
changelog.md
45
changelog.md
@@ -1,5 +1,50 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-08-30 - 5.4.1 - fix(processmonitor)
|
||||||
|
Bump tsbuild devDependency and relax ps-tree callback typing in ProcessMonitor
|
||||||
|
|
||||||
|
- Update devDependency @git.zone/tsbuild from ^2.6.7 to ^2.6.8
|
||||||
|
- Change psTree callback types in ts/daemon/processmonitor.ts to accept any error and ReadonlyArray for children to improve type compatibility
|
||||||
|
|
||||||
|
## 2025-08-30 - 5.4.0 - feat(daemon)
|
||||||
|
Add CLI systemd service refresh on version mismatch and fix daemon memory leak; update dependencies
|
||||||
|
|
||||||
|
- CLI: when client and daemon versions differ, prompt to refresh the systemd service and optionally disable/enable the service automatically
|
||||||
|
- Daemon: clear pidusage state for PIDs on process exit/stop to prevent memory leaks in long-running monitors
|
||||||
|
- Client: expose smartdaemon in client plugin exports and fix import path for tspm.servicemanager
|
||||||
|
- Package: tighten dependency ranges (set specific versions) and add @types for pidusage and ps-tree
|
||||||
|
- Misc: ensure IPC disconnects and PID/socket handling improvements were integrated alongside the above changes
|
||||||
|
|
||||||
|
## 2025-08-30 - 5.3.2 - fix(daemon)
|
||||||
|
Improve daemon log delivery and process monitor memory accounting; gate debug output and update tests to numeric ProcessId
|
||||||
|
|
||||||
|
- Deliver process logs only to subscribed clients instead of broadcasting to all connections (reduce unnecessary IPC traffic and noise)
|
||||||
|
- Implement incremental log memory accounting in ProcessMonitor using an estimateLogSize helper and WeakMap to avoid repeated JSON.stringify and reduce CPU/memory overhead
|
||||||
|
- Seed the incremental size map when loading persisted logs so memory accounting is accurate after restart
|
||||||
|
- Trim logs incrementally by subtracting estimated sizes of removed entries (avoids O(n) recalculation)
|
||||||
|
- Gate verbose console/debug output behind TSPM_DEBUG to prevent spamming in normal runs (applies to ProcessWrapper and ProcessMonitor)
|
||||||
|
- Improve process wrapper stdout/stderr debug logging to be conditional on debug mode
|
||||||
|
- Update tests to use numeric ProcessId via toProcessId(...) for consistency with typed IDs
|
||||||
|
|
||||||
|
## 2025-08-30 - 5.3.1 - fix(client(tspmIpcClient))
|
||||||
|
Use bare topic names for IPC client subscribe/unsubscribe to fix log subscription issues
|
||||||
|
|
||||||
|
- Updated ts/client/tspm.ipcclient.ts to call ipcClient.subscribe/unsubscribe with the bare topic (e.g. 'logs.<id>') instead of prefixed 'topic:<...>'.
|
||||||
|
- Added comments clarifying that the IpcClient registers the 'topic:' prefix internally.
|
||||||
|
- Fixes incorrect topic registration that could prevent log streaming handlers from receiving messages.
|
||||||
|
|
||||||
|
## 2025-08-30 - 5.3.0 - feat(cli/daemon/processmonitor)
|
||||||
|
Add flexible target resolution and search command; improve restart/backoff and error handling
|
||||||
|
|
||||||
|
- Add new cli command `search` to find processes by id or name fragment.
|
||||||
|
- Allow flexible process targets in CLI commands (accepts numeric id, id:<n>, or name:<label>) for start/stop/restart/delete/describe/logs/edit commands.
|
||||||
|
- Introduce a new daemon IPC method `resolveTarget` to normalize user-provided targets to ProcessId (supports id:<n>, name:<label>, or bare numeric id).
|
||||||
|
- Keep `remove` as a CLI alias but daemon exposes `delete` only; CLI resolves targets and always calls daemon `delete`.
|
||||||
|
- Implement scheduled restart/backoff in ProcessMonitor with incremental debounce, max retries, and a 1-hour reset window.
|
||||||
|
- Emit a `failed` event from ProcessMonitor when max restart attempts are exceeded; ProcessManager listens and marks processes as `errored` and clears pid.
|
||||||
|
- Ensure desired state is set to `stopped` before deleting a process to avoid race conditions.
|
||||||
|
- Improve cli output messages to include resolved names alongside numeric ids where available.
|
||||||
|
|
||||||
## 2025-08-30 - 5.2.0 - feat(cli)
|
## 2025-08-30 - 5.2.0 - feat(cli)
|
||||||
Preserve CLI environment when adding processes, simplify edit flow, and refresh README docs
|
Preserve CLI environment when adding processes, simplify edit flow, and refresh README docs
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/tspm",
|
"name": "@git.zone/tspm",
|
||||||
"version": "5.2.0",
|
"version": "5.4.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",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"tspm": "./cli.js"
|
"tspm": "./cli.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.6.7",
|
"@git.zone/tsbuild": "^2.6.8",
|
||||||
"@git.zone/tsbundle": "^2.5.1",
|
"@git.zone/tsbundle": "^2.5.1",
|
||||||
"@git.zone/tsrun": "^1.2.46",
|
"@git.zone/tsrun": "^1.2.46",
|
||||||
"@git.zone/tstest": "^2.3.5",
|
"@git.zone/tstest": "^2.3.5",
|
||||||
@@ -40,6 +40,8 @@
|
|||||||
"@push.rocks/smartinteract": "^2.0.16",
|
"@push.rocks/smartinteract": "^2.0.16",
|
||||||
"@push.rocks/smartipc": "^2.2.2",
|
"@push.rocks/smartipc": "^2.2.2",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
|
"@types/pidusage": "^2.0.5",
|
||||||
|
"@types/ps-tree": "^1.1.6",
|
||||||
"pidusage": "^4.0.1",
|
"pidusage": "^4.0.1",
|
||||||
"ps-tree": "^1.2.0",
|
"ps-tree": "^1.2.0",
|
||||||
"tsx": "^4.20.5"
|
"tsx": "^4.20.5"
|
||||||
|
74
pnpm-lock.yaml
generated
74
pnpm-lock.yaml
generated
@@ -32,6 +32,12 @@ importers:
|
|||||||
'@push.rocks/smartpath':
|
'@push.rocks/smartpath':
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
|
'@types/pidusage':
|
||||||
|
specifier: ^2.0.5
|
||||||
|
version: 2.0.5
|
||||||
|
'@types/ps-tree':
|
||||||
|
specifier: ^1.1.6
|
||||||
|
version: 1.1.6
|
||||||
pidusage:
|
pidusage:
|
||||||
specifier: ^4.0.1
|
specifier: ^4.0.1
|
||||||
version: 4.0.1
|
version: 4.0.1
|
||||||
@@ -43,8 +49,8 @@ importers:
|
|||||||
version: 4.20.5
|
version: 4.20.5
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@git.zone/tsbuild':
|
'@git.zone/tsbuild':
|
||||||
specifier: ^2.6.7
|
specifier: ^2.6.8
|
||||||
version: 2.6.7
|
version: 2.6.8
|
||||||
'@git.zone/tsbundle':
|
'@git.zone/tsbundle':
|
||||||
specifier: ^2.5.1
|
specifier: ^2.5.1
|
||||||
version: 2.5.1
|
version: 2.5.1
|
||||||
@@ -530,8 +536,8 @@ packages:
|
|||||||
'@esm-bundle/chai@4.3.4-fix.0':
|
'@esm-bundle/chai@4.3.4-fix.0':
|
||||||
resolution: {integrity: sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==}
|
resolution: {integrity: sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==}
|
||||||
|
|
||||||
'@git.zone/tsbuild@2.6.7':
|
'@git.zone/tsbuild@2.6.8':
|
||||||
resolution: {integrity: sha512-nLRYk1V4gxdEAp5mbLYNdr/in9mFA26L4MPKBKqzASID4lXSYya5sDbLRdDTv+mD0ZRBgdn6e+WMylA0SU4hSw==}
|
resolution: {integrity: sha512-g1z7+MxiYD0xMfuqn8NSWitbfK1OaF0Qolmw7WOmUsHmNF60T1AR02Lo4DtNmnjSpchA+xzDFAQzL1xTcQA39w==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@git.zone/tsbundle@2.5.1':
|
'@git.zone/tsbundle@2.5.1':
|
||||||
@@ -769,8 +775,8 @@ packages:
|
|||||||
'@push.rocks/isounique@1.0.5':
|
'@push.rocks/isounique@1.0.5':
|
||||||
resolution: {integrity: sha512-Z0BVqZZOCif1THTbIKWMgg0wxCzt9CyBtBBqQJiZ+jJ0KlQFrQHNHrPt81/LXe/L4x0cxWsn0bpL6W5DNSvNLw==}
|
resolution: {integrity: sha512-Z0BVqZZOCif1THTbIKWMgg0wxCzt9CyBtBBqQJiZ+jJ0KlQFrQHNHrPt81/LXe/L4x0cxWsn0bpL6W5DNSvNLw==}
|
||||||
|
|
||||||
'@push.rocks/levelcache@3.1.1':
|
'@push.rocks/levelcache@3.2.0':
|
||||||
resolution: {integrity: sha512-+JpDNEt+EuvmbtADGH9SkODxBy+slHDDzs43mAbuMbwpVvi6uNuMK0Mkhrfz9UFpxUSp+cJE/jl/OxdpD0xL1A==}
|
resolution: {integrity: sha512-Ch0Oguta2I0SVi704kHghhBcgfyfS92ua1elRu9d8X1/9LMRYuqvvBAnyXyFxQzI3S8q8QC6EkRdd8CAAYSzRg==}
|
||||||
|
|
||||||
'@push.rocks/lik@6.1.0':
|
'@push.rocks/lik@6.1.0':
|
||||||
resolution: {integrity: sha512-BoSAIRFNryQ8Sd5EP+35ZBj6vAQ1C60/XjZIO2O65XDyLG8xz7xJ+u5Wm8/fjIJ0WX3h8GkkaCz2tJM34nFT3A==}
|
resolution: {integrity: sha512-BoSAIRFNryQ8Sd5EP+35ZBj6vAQ1C60/XjZIO2O65XDyLG8xz7xJ+u5Wm8/fjIJ0WX3h8GkkaCz2tJM34nFT3A==}
|
||||||
@@ -805,6 +811,9 @@ packages:
|
|||||||
'@push.rocks/smartcache@1.0.16':
|
'@push.rocks/smartcache@1.0.16':
|
||||||
resolution: {integrity: sha512-UAXf74eDuH4/RebJhydIbHlYVR3ACYJjniEY/9ZePblu7bIPgwFZqLBE9g1lcKVogbH9yY62dk3rSpgBzenyfQ==}
|
resolution: {integrity: sha512-UAXf74eDuH4/RebJhydIbHlYVR3ACYJjniEY/9ZePblu7bIPgwFZqLBE9g1lcKVogbH9yY62dk3rSpgBzenyfQ==}
|
||||||
|
|
||||||
|
'@push.rocks/smartcache@1.0.18':
|
||||||
|
resolution: {integrity: sha512-3+cmLu9chbnmi4yD4kjlFP/Tn4NReaZIoicEcGTtwbcokTrSDMs3YPdJzIpDZkAs83PW7OcVSHa3Ak5KU5OWzA==}
|
||||||
|
|
||||||
'@push.rocks/smartchok@1.1.1':
|
'@push.rocks/smartchok@1.1.1':
|
||||||
resolution: {integrity: sha512-WmNigGmn1muBJMANVuJb4F8x3TzgYrnn6YZm6ixTsG+0WFbYevivEwp+J4S7npobLHsR7ynf+Ky8LxRYmsL50A==}
|
resolution: {integrity: sha512-WmNigGmn1muBJMANVuJb4F8x3TzgYrnn6YZm6ixTsG+0WFbYevivEwp+J4S7npobLHsR7ynf+Ky8LxRYmsL50A==}
|
||||||
|
|
||||||
@@ -832,6 +841,9 @@ packages:
|
|||||||
'@push.rocks/smartenv@5.0.13':
|
'@push.rocks/smartenv@5.0.13':
|
||||||
resolution: {integrity: sha512-ACXmUcHZHl2CF2jnVuRw9saRRrZvJblCRs2d+K5aLR1DfkYFX3eA21kcMlKeLisI3aGNbIj9vz/rowN5qkRkfA==}
|
resolution: {integrity: sha512-ACXmUcHZHl2CF2jnVuRw9saRRrZvJblCRs2d+K5aLR1DfkYFX3eA21kcMlKeLisI3aGNbIj9vz/rowN5qkRkfA==}
|
||||||
|
|
||||||
|
'@push.rocks/smarterror@2.0.1':
|
||||||
|
resolution: {integrity: sha512-iCcH1D8tlDJgMFsaJ6lhdOTKhbU0KoprNv9MRP9o7691QOx4JEDXiHtr/lNtxVo8BUtdb9CF6kazaknO9KuORA==}
|
||||||
|
|
||||||
'@push.rocks/smartexit@1.0.23':
|
'@push.rocks/smartexit@1.0.23':
|
||||||
resolution: {integrity: sha512-WmwKYcwbHBByoABhHHB+PAjr5475AtD/xBh1mDcqPrFsOOUOZq3BBUdpq25wI3ccu/SZB5IwaimiVzadls6HkA==}
|
resolution: {integrity: sha512-WmwKYcwbHBByoABhHHB+PAjr5475AtD/xBh1mDcqPrFsOOUOZq3BBUdpq25wI3ccu/SZB5IwaimiVzadls6HkA==}
|
||||||
|
|
||||||
@@ -1012,6 +1024,9 @@ packages:
|
|||||||
'@push.rocks/tapbundle@6.0.3':
|
'@push.rocks/tapbundle@6.0.3':
|
||||||
resolution: {integrity: sha512-SuP14V6TPdtd1y1CYTvwTKJdpHa7EzY55NfaaEMxW4oRKvHgJiOiPEiR/IrtL9tSiDMSfrx12waTMgZheYaBug==}
|
resolution: {integrity: sha512-SuP14V6TPdtd1y1CYTvwTKJdpHa7EzY55NfaaEMxW4oRKvHgJiOiPEiR/IrtL9tSiDMSfrx12waTMgZheYaBug==}
|
||||||
|
|
||||||
|
'@push.rocks/taskbuffer@3.1.10':
|
||||||
|
resolution: {integrity: sha512-jT+FxRSk0+IP17q9LD1/Ks8GJBn5TZWgLtfnKRHW/LAZ1bHX/2ARZvAV8fm1T4WMU5s7PyId+y4fkoohG/5Nkg==}
|
||||||
|
|
||||||
'@push.rocks/taskbuffer@3.1.7':
|
'@push.rocks/taskbuffer@3.1.7':
|
||||||
resolution: {integrity: sha512-QktGVJPucqQmW/QNGnscf4FAigT1H7JWKFGFdRuDEaOHKFh9qN+PXG3QY7DtZ4jfXdGLxPN4yAufDuPSAJYFnw==}
|
resolution: {integrity: sha512-QktGVJPucqQmW/QNGnscf4FAigT1H7JWKFGFdRuDEaOHKFh9qN+PXG3QY7DtZ4jfXdGLxPN4yAufDuPSAJYFnw==}
|
||||||
|
|
||||||
@@ -1647,9 +1662,15 @@ packages:
|
|||||||
'@types/parse5@6.0.3':
|
'@types/parse5@6.0.3':
|
||||||
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
||||||
|
|
||||||
|
'@types/pidusage@2.0.5':
|
||||||
|
resolution: {integrity: sha512-MIiyZI4/MK9UGUXWt0jJcCZhVw7YdhBuTOuqP/BjuLDLZ2PmmViMIQgZiWxtaMicQfAz/kMrZ5T7PKxFSkTeUA==}
|
||||||
|
|
||||||
'@types/ping@0.4.4':
|
'@types/ping@0.4.4':
|
||||||
resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==}
|
resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==}
|
||||||
|
|
||||||
|
'@types/ps-tree@1.1.6':
|
||||||
|
resolution: {integrity: sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==}
|
||||||
|
|
||||||
'@types/qs@6.14.0':
|
'@types/qs@6.14.0':
|
||||||
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
|
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
|
||||||
|
|
||||||
@@ -5599,7 +5620,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 4.3.20
|
'@types/chai': 4.3.20
|
||||||
|
|
||||||
'@git.zone/tsbuild@2.6.7':
|
'@git.zone/tsbuild@2.6.8':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@git.zone/tspublish': 1.10.3
|
'@git.zone/tspublish': 1.10.3
|
||||||
'@push.rocks/early': 4.0.4
|
'@push.rocks/early': 4.0.4
|
||||||
@@ -6015,21 +6036,21 @@ snapshots:
|
|||||||
|
|
||||||
'@push.rocks/isounique@1.0.5': {}
|
'@push.rocks/isounique@1.0.5': {}
|
||||||
|
|
||||||
'@push.rocks/levelcache@3.1.1':
|
'@push.rocks/levelcache@3.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartbucket': 3.3.10
|
'@push.rocks/smartbucket': 3.3.10
|
||||||
'@push.rocks/smartcache': 1.0.16
|
'@push.rocks/smartcache': 1.0.18
|
||||||
'@push.rocks/smartenv': 5.0.13
|
'@push.rocks/smartenv': 5.0.13
|
||||||
'@push.rocks/smartexit': 1.0.23
|
'@push.rocks/smartexit': 1.0.23
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smartjson': 5.0.20
|
'@push.rocks/smartjson': 5.0.20
|
||||||
'@push.rocks/smartpath': 5.1.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartstring': 4.0.15
|
'@push.rocks/smartstring': 4.0.15
|
||||||
'@push.rocks/smartunique': 3.0.9
|
'@push.rocks/smartunique': 3.0.9
|
||||||
'@push.rocks/taskbuffer': 3.1.7
|
'@push.rocks/taskbuffer': 3.1.10
|
||||||
'@tsclass/tsclass': 4.4.4
|
'@tsclass/tsclass': 9.2.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
@@ -6158,6 +6179,14 @@ snapshots:
|
|||||||
'@pushrocks/smartpromise': 3.1.10
|
'@pushrocks/smartpromise': 3.1.10
|
||||||
'@pushrocks/smarttime': 4.0.1
|
'@pushrocks/smarttime': 4.0.1
|
||||||
|
|
||||||
|
'@push.rocks/smartcache@1.0.18':
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
|
'@push.rocks/smarterror': 2.0.1
|
||||||
|
'@push.rocks/smarthash': 3.2.3
|
||||||
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
'@push.rocks/smarttime': 4.1.1
|
||||||
|
|
||||||
'@push.rocks/smartchok@1.1.1':
|
'@push.rocks/smartchok@1.1.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
@@ -6237,6 +6266,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
|
||||||
|
'@push.rocks/smarterror@2.0.1':
|
||||||
|
dependencies:
|
||||||
|
clean-stack: 1.3.0
|
||||||
|
make-error-cause: 2.3.0
|
||||||
|
|
||||||
'@push.rocks/smartexit@1.0.23':
|
'@push.rocks/smartexit@1.0.23':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.1.0
|
'@push.rocks/lik': 6.1.0
|
||||||
@@ -6443,7 +6477,7 @@ snapshots:
|
|||||||
'@push.rocks/smartnpm@2.0.6':
|
'@push.rocks/smartnpm@2.0.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/consolecolor': 2.0.3
|
'@push.rocks/consolecolor': 2.0.3
|
||||||
'@push.rocks/levelcache': 3.1.1
|
'@push.rocks/levelcache': 3.2.0
|
||||||
'@push.rocks/smartarchive': 4.2.2
|
'@push.rocks/smartarchive': 4.2.2
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
@@ -6745,6 +6779,16 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
|
'@push.rocks/taskbuffer@3.1.10':
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/lik': 6.2.2
|
||||||
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
|
'@push.rocks/smartlog': 3.1.8
|
||||||
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
'@push.rocks/smartrx': 3.0.10
|
||||||
|
'@push.rocks/smarttime': 4.1.1
|
||||||
|
'@push.rocks/smartunique': 3.0.9
|
||||||
|
|
||||||
'@push.rocks/taskbuffer@3.1.7':
|
'@push.rocks/taskbuffer@3.1.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
@@ -7592,8 +7636,12 @@ snapshots:
|
|||||||
|
|
||||||
'@types/parse5@6.0.3': {}
|
'@types/parse5@6.0.3': {}
|
||||||
|
|
||||||
|
'@types/pidusage@2.0.5': {}
|
||||||
|
|
||||||
'@types/ping@0.4.4': {}
|
'@types/ping@0.4.4': {}
|
||||||
|
|
||||||
|
'@types/ps-tree@1.1.6': {}
|
||||||
|
|
||||||
'@types/qs@6.14.0': {}
|
'@types/qs@6.14.0': {}
|
||||||
|
|
||||||
'@types/randomatic@3.1.5': {}
|
'@types/randomatic@3.1.5': {}
|
||||||
|
84
readme.md
84
readme.md
@@ -38,21 +38,23 @@ npm install --save-dev @git.zone/tspm
|
|||||||
# Add a process (creates config without starting)
|
# Add a process (creates config without starting)
|
||||||
tspm add "node server.js" --name my-server --memory 1GB
|
tspm add "node server.js" --name my-server --memory 1GB
|
||||||
|
|
||||||
# Start the process
|
# Start the process (by name or id)
|
||||||
tspm start my-server
|
tspm start name:my-server
|
||||||
|
# or
|
||||||
|
tspm start id:1
|
||||||
|
|
||||||
# Or add and start in one go
|
# Or add and start in one go
|
||||||
tspm add "node app.js" --name my-app
|
tspm add "node app.js" --name my-app
|
||||||
tspm start my-app
|
tspm start name:my-app
|
||||||
|
|
||||||
# List all processes
|
# List all processes
|
||||||
tspm list
|
tspm list
|
||||||
|
|
||||||
# View logs
|
# View logs
|
||||||
tspm logs my-app
|
tspm logs name:my-app
|
||||||
|
|
||||||
# Stop a process
|
# Stop a process
|
||||||
tspm stop my-app
|
tspm stop name:my-app
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📋 Commands
|
## 📋 Commands
|
||||||
@@ -86,38 +88,38 @@ tspm add "tsx watch src/index.ts" --name dev-server --watch --watch-paths "src,c
|
|||||||
tspm add "node worker.js" --name one-time-job --autorestart false
|
tspm add "node worker.js" --name one-time-job --autorestart false
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `tspm start <id>`
|
#### `tspm start <id|id:N|name:LABEL>`
|
||||||
|
|
||||||
Start a previously added process by its ID or name.
|
Start a previously added process by its ID or name.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tspm start my-server
|
tspm start name:my-server
|
||||||
tspm start 1 # Can also use numeric ID
|
tspm start id:1 # Or a bare numeric id: tspm start 1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `tspm stop <id>`
|
#### `tspm stop <id|id:N|name:LABEL>`
|
||||||
|
|
||||||
Gracefully stop a running process (SIGTERM → SIGKILL after timeout).
|
Gracefully stop a running process (SIGTERM → SIGKILL after timeout).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tspm stop my-server
|
tspm stop name:my-server
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `tspm restart <id>`
|
#### `tspm restart <id|id:N|name:LABEL>`
|
||||||
|
|
||||||
Stop and restart a process with the same configuration.
|
Stop and restart a process with the same configuration.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tspm restart my-server
|
tspm restart name:my-server
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `tspm delete <id>` / `tspm remove <id>`
|
#### `tspm delete <id|id:N|name:LABEL>` / `tspm remove <id|id:N|name:LABEL>`
|
||||||
|
|
||||||
Stop and remove a process from TSPM management. Also deletes persisted logs.
|
Stop and remove a process from TSPM management. Also deletes persisted logs.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tspm delete old-server
|
tspm delete name:old-server
|
||||||
tspm remove old-server # Alias for delete
|
tspm remove name:old-server # Alias for delete (daemon handles delete)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `tspm edit <id>`
|
#### `tspm edit <id>`
|
||||||
@@ -148,12 +150,12 @@ tspm list
|
|||||||
└─────────┴─────────────┴───────────┴───────────┴──────────┴──────────┘
|
└─────────┴─────────────┴───────────┴───────────┴──────────┴──────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `tspm describe <id>`
|
#### `tspm describe <id|id:N|name:LABEL>`
|
||||||
|
|
||||||
Get detailed information about a specific process.
|
Get detailed information about a specific process.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tspm describe my-server
|
tspm describe name:my-server
|
||||||
|
|
||||||
# Output:
|
# Output:
|
||||||
Process Details: my-server
|
Process Details: my-server
|
||||||
@@ -173,7 +175,7 @@ Auto-restart: true
|
|||||||
Watch: disabled
|
Watch: disabled
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `tspm logs <id> [options]`
|
#### `tspm logs <id|id:N|name:LABEL> [options]`
|
||||||
|
|
||||||
View process logs (stdout and stderr combined).
|
View process logs (stdout and stderr combined).
|
||||||
|
|
||||||
@@ -183,13 +185,13 @@ View process logs (stdout and stderr combined).
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View last 50 lines
|
# View last 50 lines
|
||||||
tspm logs my-server
|
tspm logs name:my-server
|
||||||
|
|
||||||
# View last 100 lines
|
# View last 100 lines
|
||||||
tspm logs my-server --lines 100
|
tspm logs name:my-server --lines 100
|
||||||
|
|
||||||
# Follow logs in real-time
|
# Follow logs in real-time
|
||||||
tspm logs my-server --follow
|
tspm logs name:my-server --follow
|
||||||
```
|
```
|
||||||
|
|
||||||
### Batch Operations
|
### Batch Operations
|
||||||
@@ -496,7 +498,31 @@ Common issues:
|
|||||||
|
|
||||||
- **"Daemon not running"**: Run `tspm daemon start` or `tspm enable`
|
- **"Daemon not running"**: Run `tspm daemon start` or `tspm enable`
|
||||||
- **"Permission denied"**: Check socket permissions in `~/.tspm/`
|
- **"Permission denied"**: Check socket permissions in `~/.tspm/`
|
||||||
- **"Process won't start"**: Check logs with `tspm logs <id>`
|
- **"Process won't start"**: Check logs with `tspm logs <id|id:N|name:LABEL>`
|
||||||
|
|
||||||
|
## 🎯 Targeting Processes (IDs and Names)
|
||||||
|
|
||||||
|
Most process commands accept the following target formats:
|
||||||
|
|
||||||
|
- Numeric ID: `tspm start 1`
|
||||||
|
- Explicit ID: `tspm start id:1`
|
||||||
|
- Explicit name: `tspm start name:api-server`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Names must be used with the `name:` prefix.
|
||||||
|
- If multiple processes share the same name, the CLI will report the ambiguous matches. Use `id:N` to disambiguate.
|
||||||
|
- Use `tspm search <query>` to discover IDs by name or ID fragments.
|
||||||
|
|
||||||
|
### `tspm search <query>`
|
||||||
|
|
||||||
|
Search processes by name or ID substring and print matching IDs (and names when available):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tspm search api
|
||||||
|
# Matches for "api":
|
||||||
|
# - id:3 name:api-server
|
||||||
|
```
|
||||||
|
|
||||||
- **"Memory limit exceeded"**: Increase limit with `tspm edit <id>`
|
- **"Memory limit exceeded"**: Increase limit with `tspm edit <id>`
|
||||||
|
|
||||||
## 🤝 Why Choose TSPM?
|
## 🤝 Why Choose TSPM?
|
||||||
@@ -515,6 +541,20 @@ Common issues:
|
|||||||
|
|
||||||
### Perfect For
|
### Perfect For
|
||||||
|
|
||||||
|
### Restart Backoff and Failure Handling
|
||||||
|
|
||||||
|
TSPM automatically restarts crashed processes with an incremental backoff:
|
||||||
|
|
||||||
|
- Debounce delay grows linearly from 1s up to 10s for consecutive retries.
|
||||||
|
- After the 10th retry, the process is marked as failed (status: "errored") and auto-restarts stop.
|
||||||
|
- The retry counter resets if no retry happens for 1 hour since the last attempt.
|
||||||
|
|
||||||
|
You can manually restart a failed process at any time:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tspm restart id:1
|
||||||
|
```
|
||||||
|
|
||||||
- 🚀 **Production Node.js apps** - Reliable process management
|
- 🚀 **Production Node.js apps** - Reliable process management
|
||||||
- 🔧 **Microservices** - Manage multiple services easily
|
- 🔧 **Microservices** - Manage multiple services easily
|
||||||
- 👨💻 **Development** - File watching and auto-restart
|
- 👨💻 **Development** - File watching and auto-restart
|
||||||
|
@@ -5,6 +5,7 @@ import * as fs from 'fs/promises';
|
|||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { tspmIpcClient, TspmIpcClient } from '../ts/client/tspm.ipcclient.js';
|
import { tspmIpcClient, TspmIpcClient } from '../ts/client/tspm.ipcclient.js';
|
||||||
|
import { toProcessId } from '../ts/shared/protocol/id.js';
|
||||||
|
|
||||||
// Helper to ensure daemon is stopped before tests
|
// Helper to ensure daemon is stopped before tests
|
||||||
async function ensureDaemonStopped() {
|
async function ensureDaemonStopped() {
|
||||||
@@ -160,7 +161,7 @@ tap.test('Process management through daemon', async (tools) => {
|
|||||||
|
|
||||||
// Test 2: Start a test process
|
// Test 2: Start a test process
|
||||||
const testConfig: tspm.IProcessConfig = {
|
const testConfig: tspm.IProcessConfig = {
|
||||||
id: 'test-echo',
|
id: toProcessId(1001),
|
||||||
name: 'Test Echo Process',
|
name: 'Test Echo Process',
|
||||||
command: 'echo "Test process"',
|
command: 'echo "Test process"',
|
||||||
projectDir: process.cwd(),
|
projectDir: process.cwd(),
|
||||||
@@ -172,7 +173,7 @@ tap.test('Process management through daemon', async (tools) => {
|
|||||||
config: testConfig,
|
config: testConfig,
|
||||||
});
|
});
|
||||||
console.log('Start response:', startResponse);
|
console.log('Start response:', startResponse);
|
||||||
expect(startResponse.processId).toEqual('test-echo');
|
expect(startResponse.processId).toEqual(1001);
|
||||||
expect(startResponse.status).toBeDefined();
|
expect(startResponse.status).toBeDefined();
|
||||||
|
|
||||||
// Test 3: List processes (should have one process)
|
// Test 3: List processes (should have one process)
|
||||||
@@ -180,27 +181,27 @@ tap.test('Process management through daemon', async (tools) => {
|
|||||||
console.log('List after start:', listResponse);
|
console.log('List after start:', listResponse);
|
||||||
expect(listResponse.processes.length).toBeGreaterThanOrEqual(1);
|
expect(listResponse.processes.length).toBeGreaterThanOrEqual(1);
|
||||||
|
|
||||||
const procInfo = listResponse.processes.find((p) => p.id === 'test-echo');
|
const procInfo = listResponse.processes.find((p) => p.id === toProcessId(1001));
|
||||||
expect(procInfo).toBeDefined();
|
expect(procInfo).toBeDefined();
|
||||||
expect(procInfo?.id).toEqual('test-echo');
|
expect(procInfo?.id).toEqual(1001);
|
||||||
|
|
||||||
// Test 4: Describe the process
|
// Test 4: Describe the process
|
||||||
const describeResponse = await tspmIpcClient.request('describe', {
|
const describeResponse = await tspmIpcClient.request('describe', {
|
||||||
id: 'test-echo',
|
id: toProcessId(1001),
|
||||||
});
|
});
|
||||||
console.log('Describe:', describeResponse);
|
console.log('Describe:', describeResponse);
|
||||||
expect(describeResponse.processInfo).toBeDefined();
|
expect(describeResponse.processInfo).toBeDefined();
|
||||||
expect(describeResponse.config).toBeDefined();
|
expect(describeResponse.config).toBeDefined();
|
||||||
expect(describeResponse.config.id).toEqual('test-echo');
|
expect(describeResponse.config.id).toEqual(1001);
|
||||||
|
|
||||||
// Test 5: Stop the process
|
// Test 5: Stop the process
|
||||||
const stopResponse = await tspmIpcClient.request('stop', { id: 'test-echo' });
|
const stopResponse = await tspmIpcClient.request('stop', { id: toProcessId(1001) });
|
||||||
console.log('Stop response:', stopResponse);
|
console.log('Stop response:', stopResponse);
|
||||||
expect(stopResponse.success).toEqual(true);
|
expect(stopResponse.success).toEqual(true);
|
||||||
|
|
||||||
// Test 6: Delete the process
|
// Test 6: Delete the process
|
||||||
const deleteResponse = await tspmIpcClient.request('delete', {
|
const deleteResponse = await tspmIpcClient.request('delete', {
|
||||||
id: 'test-echo',
|
id: toProcessId(1001),
|
||||||
});
|
});
|
||||||
console.log('Delete response:', deleteResponse);
|
console.log('Delete response:', deleteResponse);
|
||||||
expect(deleteResponse.success).toEqual(true);
|
expect(deleteResponse.success).toEqual(true);
|
||||||
@@ -208,9 +209,7 @@ tap.test('Process management through daemon', async (tools) => {
|
|||||||
// Test 7: Verify process is gone
|
// Test 7: Verify process is gone
|
||||||
listResponse = await tspmIpcClient.request('list', {});
|
listResponse = await tspmIpcClient.request('list', {});
|
||||||
console.log('List after delete:', listResponse);
|
console.log('List after delete:', listResponse);
|
||||||
const deletedProcess = listResponse.processes.find(
|
const deletedProcess = listResponse.processes.find((p) => p.id === toProcessId(1001));
|
||||||
(p) => p.id === 'test-echo',
|
|
||||||
);
|
|
||||||
expect(deletedProcess).toBeUndefined();
|
expect(deletedProcess).toBeUndefined();
|
||||||
|
|
||||||
// Cleanup: stop daemon
|
// Cleanup: stop daemon
|
||||||
@@ -241,7 +240,7 @@ tap.test('Batch operations through daemon', async (tools) => {
|
|||||||
// Add multiple test processes
|
// Add multiple test processes
|
||||||
const testConfigs: tspm.IProcessConfig[] = [
|
const testConfigs: tspm.IProcessConfig[] = [
|
||||||
{
|
{
|
||||||
id: 'batch-test-1',
|
id: toProcessId(1101),
|
||||||
name: 'Batch Test 1',
|
name: 'Batch Test 1',
|
||||||
command: 'echo "Process 1"',
|
command: 'echo "Process 1"',
|
||||||
projectDir: process.cwd(),
|
projectDir: process.cwd(),
|
||||||
@@ -249,7 +248,7 @@ tap.test('Batch operations through daemon', async (tools) => {
|
|||||||
autorestart: false,
|
autorestart: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'batch-test-2',
|
id: toProcessId(1102),
|
||||||
name: 'Batch Test 2',
|
name: 'Batch Test 2',
|
||||||
command: 'echo "Process 2"',
|
command: 'echo "Process 2"',
|
||||||
projectDir: process.cwd(),
|
projectDir: process.cwd(),
|
||||||
@@ -308,7 +307,7 @@ tap.test('Daemon error handling', async (tools) => {
|
|||||||
|
|
||||||
// Test 1: Try to stop non-existent process
|
// Test 1: Try to stop non-existent process
|
||||||
try {
|
try {
|
||||||
await tspmIpcClient.request('stop', { id: 'non-existent-process' });
|
await tspmIpcClient.request('stop', { id: toProcessId(99999) });
|
||||||
expect(false).toEqual(true); // Should not reach here
|
expect(false).toEqual(true); // Should not reach here
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error.message).toInclude('Failed to stop process');
|
expect(error.message).toInclude('Failed to stop process');
|
||||||
@@ -316,7 +315,7 @@ tap.test('Daemon error handling', async (tools) => {
|
|||||||
|
|
||||||
// Test 2: Try to describe non-existent process
|
// Test 2: Try to describe non-existent process
|
||||||
try {
|
try {
|
||||||
await tspmIpcClient.request('describe', { id: 'non-existent-process' });
|
await tspmIpcClient.request('describe', { id: toProcessId(99999) });
|
||||||
expect(false).toEqual(true); // Should not reach here
|
expect(false).toEqual(true); // Should not reach here
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error.message).toInclude('not found');
|
expect(error.message).toInclude('not found');
|
||||||
@@ -324,7 +323,7 @@ tap.test('Daemon error handling', async (tools) => {
|
|||||||
|
|
||||||
// Test 3: Try to restart non-existent process
|
// Test 3: Try to restart non-existent process
|
||||||
try {
|
try {
|
||||||
await tspmIpcClient.request('restart', { id: 'non-existent-process' });
|
await tspmIpcClient.request('restart', { id: toProcessId(99999) });
|
||||||
expect(false).toEqual(true); // Should not reach here
|
expect(false).toEqual(true); // Should not reach here
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error.message).toInclude('Failed to restart process');
|
expect(error.message).toInclude('Failed to restart process');
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import * as tspm from '../ts/index.js';
|
import * as tspm from '../ts/index.js';
|
||||||
|
import { toProcessId } from '../ts/shared/protocol/id.js';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
// Basic module import test
|
// Basic module import test
|
||||||
@@ -51,7 +52,7 @@ async function exampleUsingIpcClient() {
|
|||||||
// Start a process using the request method
|
// Start a process using the request method
|
||||||
await client.request('start', {
|
await client.request('start', {
|
||||||
config: {
|
config: {
|
||||||
id: 'web-server',
|
id: toProcessId(2001),
|
||||||
name: 'Web Server',
|
name: 'Web Server',
|
||||||
projectDir: '/path/to/web/project',
|
projectDir: '/path/to/web/project',
|
||||||
command: 'npm run serve',
|
command: 'npm run serve',
|
||||||
@@ -65,7 +66,7 @@ async function exampleUsingIpcClient() {
|
|||||||
// Start another process
|
// Start another process
|
||||||
await client.request('start', {
|
await client.request('start', {
|
||||||
config: {
|
config: {
|
||||||
id: 'api-server',
|
id: toProcessId(2002),
|
||||||
name: 'API Server',
|
name: 'API Server',
|
||||||
projectDir: '/path/to/api/project',
|
projectDir: '/path/to/api/project',
|
||||||
command: 'npm run api',
|
command: 'npm run api',
|
||||||
@@ -80,13 +81,13 @@ async function exampleUsingIpcClient() {
|
|||||||
|
|
||||||
// Get logs from a process
|
// Get logs from a process
|
||||||
const logs = await client.request('getLogs', {
|
const logs = await client.request('getLogs', {
|
||||||
id: 'web-server',
|
id: toProcessId(2001),
|
||||||
lines: 20,
|
lines: 20,
|
||||||
});
|
});
|
||||||
console.log('Web server logs:', logs.logs);
|
console.log('Web server logs:', logs.logs);
|
||||||
|
|
||||||
// Stop a process
|
// Stop a process
|
||||||
await client.request('stop', { id: 'api-server' });
|
await client.request('stop', { id: toProcessId(2002) });
|
||||||
|
|
||||||
// Handle graceful shutdown
|
// Handle graceful shutdown
|
||||||
process.on('SIGINT', async () => {
|
process.on('SIGINT', async () => {
|
||||||
|
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/tspm',
|
name: '@git.zone/tspm',
|
||||||
version: '5.2.0',
|
version: '5.4.1',
|
||||||
description: 'a no fuzz process manager'
|
description: 'a no fuzz process manager'
|
||||||
}
|
}
|
||||||
|
@@ -22,13 +22,14 @@ export function registerDefaultCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
);
|
);
|
||||||
console.log(' disable Disable TSPM system service');
|
console.log(' disable Disable TSPM system service');
|
||||||
console.log('\nProcess Commands:');
|
console.log('\nProcess Commands:');
|
||||||
console.log(' start <script> Start a process');
|
console.log(' start <id|id:N|name:LBL> Start a process');
|
||||||
console.log(' list List all processes');
|
console.log(' list List all processes');
|
||||||
console.log(' stop <id> Stop a process');
|
console.log(' stop <id|id:N|name:LBL> Stop a process');
|
||||||
console.log(' restart <id> Restart a process');
|
console.log(' restart <id|id:N|name:LBL> Restart a process');
|
||||||
console.log(' delete <id> Delete a process');
|
console.log(' delete <id|id:N|name:LBL> Delete a process');
|
||||||
console.log(' describe <id> Show details for a process');
|
console.log(' describe <id|id:N|name:LBL> Show details for a process');
|
||||||
console.log(' logs <id> Show logs for a process');
|
console.log(' logs <id|id:N|name:LBL> Show logs for a process');
|
||||||
|
console.log(' search <query> Find processes by id/name');
|
||||||
console.log(' start-all Start all saved processes');
|
console.log(' start-all Start all saved processes');
|
||||||
console.log(' stop-all Stop all processes');
|
console.log(' stop-all Stop all processes');
|
||||||
console.log(' restart-all Restart all processes');
|
console.log(' restart-all Restart all processes');
|
||||||
|
@@ -8,23 +8,25 @@ export function registerDeleteCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
smartcli,
|
smartcli,
|
||||||
['delete', 'remove'],
|
['delete', 'remove'],
|
||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
const id = argvArg._[1];
|
const target = argvArg._[1];
|
||||||
if (!id) {
|
if (!target) {
|
||||||
console.error('Error: Please provide a process ID');
|
console.error('Error: Please provide a process target');
|
||||||
console.log('Usage: tspm delete <id> | tspm remove <id>');
|
console.log('Usage: tspm delete <id|id:N|name:LABEL> | tspm remove <id|id:N|name:LABEL>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if command was 'remove' to use the new IPC route, otherwise 'delete'
|
// Determine if command was 'remove' to use the new IPC route, otherwise 'delete'
|
||||||
const cmd = String(argvArg._[0]);
|
const cmd = String(argvArg._[0]);
|
||||||
const useRemove = cmd === 'remove';
|
const isRemoveAlias = cmd === 'remove';
|
||||||
console.log(`${useRemove ? 'Removing' : 'Deleting'} process: ${id}`);
|
console.log(`${isRemoveAlias ? 'Removing' : 'Deleting'} process: ${target}`);
|
||||||
const response = await tspmIpcClient.request(useRemove ? 'remove' : 'delete', { id } as any);
|
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
|
||||||
|
// Always call daemon 'delete'; 'remove' is CLI alias only
|
||||||
|
const response = await tspmIpcClient.request('delete', { id: resolved.id } as any);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
console.log(`✓ ${response.message || (useRemove ? 'Removed successfully' : 'Deleted successfully')}`);
|
console.log(`✓ ${response.message || (isRemoveAlias ? 'Removed successfully' : 'Deleted successfully')}`);
|
||||||
} else {
|
} else {
|
||||||
console.error(`✗ Failed to ${useRemove ? 'remove' : 'delete'} process: ${response.message}`);
|
console.error(`✗ Failed to ${isRemoveAlias ? 'remove' : 'delete'} process: ${response.message}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ actionLabel: 'delete/remove process' },
|
{ actionLabel: 'delete/remove process' },
|
||||||
|
@@ -9,16 +9,17 @@ export function registerDescribeCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
smartcli,
|
smartcli,
|
||||||
'describe',
|
'describe',
|
||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
const id = argvArg._[1];
|
const target = argvArg._[1];
|
||||||
if (!id) {
|
if (!target) {
|
||||||
console.error('Error: Please provide a process ID');
|
console.error('Error: Please provide a process target');
|
||||||
console.log('Usage: tspm describe <id>');
|
console.log('Usage: tspm describe <id | id:N | name:LABEL>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await tspmIpcClient.request('describe', { id });
|
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
|
||||||
|
const response = await tspmIpcClient.request('describe', { id: resolved.id });
|
||||||
|
|
||||||
console.log(`Process Details: ${id}`);
|
console.log(`Process Details: ${response.config.name || resolved.id}`);
|
||||||
console.log('─'.repeat(40));
|
console.log('─'.repeat(40));
|
||||||
console.log(`Status: ${response.processInfo.status}`);
|
console.log(`Status: ${response.processInfo.status}`);
|
||||||
console.log(`PID: ${response.processInfo.pid || 'N/A'}`);
|
console.log(`PID: ${response.processInfo.pid || 'N/A'}`);
|
||||||
|
@@ -9,17 +9,16 @@ export function registerEditCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
smartcli,
|
smartcli,
|
||||||
'edit',
|
'edit',
|
||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
const idRaw = argvArg._[1];
|
const target = argvArg._[1];
|
||||||
if (!idRaw) {
|
if (!target) {
|
||||||
console.error('Error: Please provide a process ID to edit');
|
console.error('Error: Please provide a process target to edit');
|
||||||
console.log('Usage: tspm edit <id>');
|
console.log('Usage: tspm edit <id | id:N | name:LABEL>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = idRaw;
|
// Resolve and load current config
|
||||||
|
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
|
||||||
// Load current config
|
const { config } = await tspmIpcClient.request('describe', { id: resolved.id });
|
||||||
const { config } = await tspmIpcClient.request('describe', { id });
|
|
||||||
|
|
||||||
// Interactive editing is temporarily disabled - needs smartinteract API update
|
// Interactive editing is temporarily disabled - needs smartinteract API update
|
||||||
console.log('Interactive editing is temporarily disabled.');
|
console.log('Interactive editing is temporarily disabled.');
|
||||||
@@ -63,7 +62,7 @@ export function registerEditCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateResponse = await tspmIpcClient.request('update', {
|
const updateResponse = await tspmIpcClient.request('update', {
|
||||||
id,
|
id: resolved.id,
|
||||||
updates,
|
updates,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -73,4 +72,3 @@ export function registerEditCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
{ actionLabel: 'edit process config' },
|
{ actionLabel: 'edit process config' },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,10 +11,10 @@ export function registerLogsCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
smartcli,
|
smartcli,
|
||||||
'logs',
|
'logs',
|
||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
const id = argvArg._[1];
|
const target = argvArg._[1];
|
||||||
if (!id) {
|
if (!target) {
|
||||||
console.error('Error: Please provide a process ID');
|
console.error('Error: Please provide a process target');
|
||||||
console.log('Usage: tspm logs <id> [options]');
|
console.log('Usage: tspm logs <id | id:N | name:LABEL> [options]');
|
||||||
console.log('\nOptions:');
|
console.log('\nOptions:');
|
||||||
console.log(' --lines <n> Number of lines to show (default: 50)');
|
console.log(' --lines <n> Number of lines to show (default: 50)');
|
||||||
console.log(' --follow Stream logs in real-time (like tail -f)');
|
console.log(' --follow Stream logs in real-time (like tail -f)');
|
||||||
@@ -24,6 +24,8 @@ export function registerLogsCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
const lines = getNumber(argvArg, 'lines', 50);
|
const lines = getNumber(argvArg, 'lines', 50);
|
||||||
const follow = getBool(argvArg, 'follow', 'f');
|
const follow = getBool(argvArg, 'follow', 'f');
|
||||||
|
|
||||||
|
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
|
||||||
|
const id = resolved.id;
|
||||||
const response = await tspmIpcClient.request('getLogs', { id, lines });
|
const response = await tspmIpcClient.request('getLogs', { id, lines });
|
||||||
|
|
||||||
if (!follow) {
|
if (!follow) {
|
||||||
@@ -44,7 +46,7 @@ export function registerLogsCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Streaming mode
|
// Streaming mode
|
||||||
console.log(`Logs for process: ${id} (streaming...)`);
|
console.log(`Logs for process: ${resolved.name || id} (streaming...)`);
|
||||||
console.log('─'.repeat(60));
|
console.log('─'.repeat(60));
|
||||||
|
|
||||||
let lastSeq = 0;
|
let lastSeq = 0;
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import * as plugins from '../../plugins.js';
|
import * as plugins from '../../plugins.js';
|
||||||
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
|
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
|
||||||
import { toProcessId } from '../../../shared/protocol/id.js';
|
|
||||||
import type { CliArguments } from '../../types.js';
|
import type { CliArguments } from '../../types.js';
|
||||||
import { registerIpcCommand } from '../../registration/index.js';
|
import { registerIpcCommand } from '../../registration/index.js';
|
||||||
|
|
||||||
@@ -11,9 +10,9 @@ export function registerRestartCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
const arg = argvArg._[1];
|
const arg = argvArg._[1];
|
||||||
if (!arg) {
|
if (!arg) {
|
||||||
console.error('Error: Please provide a process ID or "all"');
|
console.error('Error: Please provide a process target or "all"');
|
||||||
console.log('Usage:');
|
console.log('Usage:');
|
||||||
console.log(' tspm restart <id>');
|
console.log(' tspm restart <id | id:N | name:LABEL>');
|
||||||
console.log(' tspm restart all');
|
console.log(' tspm restart all');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -33,12 +32,13 @@ export function registerRestartCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = String(arg);
|
const target = String(arg);
|
||||||
console.log(`Restarting process: ${id}`);
|
console.log(`Restarting process: ${target}`);
|
||||||
const response = await tspmIpcClient.request('restart', { id: toProcessId(id) });
|
const resolved = await tspmIpcClient.request('resolveTarget', { target });
|
||||||
|
const response = await tspmIpcClient.request('restart', { id: resolved.id });
|
||||||
|
|
||||||
console.log(`✓ Process restarted successfully`);
|
console.log(`✓ Process restarted successfully`);
|
||||||
console.log(` ID: ${response.processId}`);
|
console.log(` ID: ${response.processId}${resolved.name ? ` (name: ${resolved.name})` : ''}`);
|
||||||
console.log(` PID: ${response.pid || 'N/A'}`);
|
console.log(` PID: ${response.pid || 'N/A'}`);
|
||||||
console.log(` Status: ${response.status}`);
|
console.log(` Status: ${response.status}`);
|
||||||
},
|
},
|
||||||
|
62
ts/cli/commands/process/search.ts
Normal file
62
ts/cli/commands/process/search.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import * as plugins from '../../plugins.js';
|
||||||
|
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
|
||||||
|
import type { CliArguments } from '../../types.js';
|
||||||
|
import { registerIpcCommand } from '../../registration/index.js';
|
||||||
|
|
||||||
|
export function registerSearchCommand(smartcli: plugins.smartcli.Smartcli) {
|
||||||
|
registerIpcCommand(
|
||||||
|
smartcli,
|
||||||
|
'search',
|
||||||
|
async (argvArg: CliArguments) => {
|
||||||
|
const query = String(argvArg._[1] || '').trim();
|
||||||
|
if (!query) {
|
||||||
|
console.error('Error: Please provide a search query');
|
||||||
|
console.log('Usage: tspm search <name-fragment | id-fragment>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch list of processes, then enrich with names via describe
|
||||||
|
const listRes = await tspmIpcClient.request('list', {});
|
||||||
|
const processes = listRes.processes;
|
||||||
|
|
||||||
|
// If there are no processes, short-circuit
|
||||||
|
if (processes.length === 0) {
|
||||||
|
console.log('No processes found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowerQ = query.toLowerCase();
|
||||||
|
const matches: Array<{ id: number; name?: string }> = [];
|
||||||
|
|
||||||
|
// Collect describe calls to obtain names
|
||||||
|
for (const proc of processes) {
|
||||||
|
try {
|
||||||
|
const desc = await tspmIpcClient.request('describe', { id: proc.id });
|
||||||
|
const name = desc.config.name || '';
|
||||||
|
const idStr = String(proc.id);
|
||||||
|
if (name.toLowerCase().includes(lowerQ) || idStr.includes(query)) {
|
||||||
|
matches.push({ id: proc.id, name });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore describe errors for individual processes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches.length === 0) {
|
||||||
|
console.log(`No matches for "${query}"`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Matches for "${query}":`);
|
||||||
|
for (const m of matches) {
|
||||||
|
if (m.name) {
|
||||||
|
console.log(`- id:${m.id}\tname:${m.name}`);
|
||||||
|
} else {
|
||||||
|
console.log(`- id:${m.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ actionLabel: 'search processes' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@@ -10,17 +10,18 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
smartcli,
|
smartcli,
|
||||||
'start',
|
'start',
|
||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
const id = argvArg._[1];
|
const target = argvArg._[1];
|
||||||
if (!id) {
|
if (!target) {
|
||||||
console.error('Error: Please provide a process ID to start');
|
console.error('Error: Please provide a process target to start');
|
||||||
console.log('Usage: tspm start <id>');
|
console.log('Usage: tspm start <id | id:N | name:LABEL>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Starting process id ${id}...`);
|
console.log(`Starting process: ${target}...`);
|
||||||
const response = await tspmIpcClient.request('startById', { id });
|
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
|
||||||
|
const response = await tspmIpcClient.request('startById', { id: resolved.id });
|
||||||
console.log('✓ Process started');
|
console.log('✓ Process started');
|
||||||
console.log(` ID: ${response.processId}`);
|
console.log(` ID: ${response.processId}${resolved.name ? ` (name: ${resolved.name})` : ''}`);
|
||||||
console.log(` PID: ${response.pid || 'N/A'}`);
|
console.log(` PID: ${response.pid || 'N/A'}`);
|
||||||
console.log(` Status: ${response.status}`);
|
console.log(` Status: ${response.status}`);
|
||||||
},
|
},
|
||||||
|
@@ -8,15 +8,16 @@ export function registerStopCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
smartcli,
|
smartcli,
|
||||||
'stop',
|
'stop',
|
||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
const id = argvArg._[1];
|
const target = argvArg._[1];
|
||||||
if (!id) {
|
if (!target) {
|
||||||
console.error('Error: Please provide a process ID');
|
console.error('Error: Please provide a process target');
|
||||||
console.log('Usage: tspm stop <id>');
|
console.log('Usage: tspm stop <id | id:N | name:LABEL>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Stopping process: ${id}`);
|
console.log(`Stopping process: ${target}`);
|
||||||
const response = await tspmIpcClient.request('stop', { id });
|
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
|
||||||
|
const response = await tspmIpcClient.request('stop', { id: resolved.id });
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
console.log(`✓ ${response.message}`);
|
console.log(`✓ ${response.message}`);
|
||||||
|
@@ -2,6 +2,7 @@ import * as plugins from './plugins.js';
|
|||||||
import { tspmIpcClient } from '../client/tspm.ipcclient.js';
|
import { tspmIpcClient } from '../client/tspm.ipcclient.js';
|
||||||
import * as paths from '../paths.js';
|
import * as paths from '../paths.js';
|
||||||
import { Logger, LogLevel } from '../shared/common/utils.errorhandler.js';
|
import { Logger, LogLevel } from '../shared/common/utils.errorhandler.js';
|
||||||
|
import { TspmServiceManager } from '../client/tspm.servicemanager.js';
|
||||||
|
|
||||||
// Import command registration functions
|
// Import command registration functions
|
||||||
import { registerDefaultCommand } from './commands/default.js';
|
import { registerDefaultCommand } from './commands/default.js';
|
||||||
@@ -10,6 +11,7 @@ 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';
|
||||||
|
import { registerSearchCommand } from './commands/process/search.js';
|
||||||
import { registerListCommand } from './commands/process/list.js';
|
import { registerListCommand } from './commands/process/list.js';
|
||||||
import { registerDescribeCommand } from './commands/process/describe.js';
|
import { registerDescribeCommand } from './commands/process/describe.js';
|
||||||
import { registerLogsCommand } from './commands/process/logs.js';
|
import { registerLogsCommand } from './commands/process/logs.js';
|
||||||
@@ -50,6 +52,38 @@ export const run = async (): Promise<void> => {
|
|||||||
console.log(
|
console.log(
|
||||||
`Daemon: running v${status.version || 'unknown'} (pid ${status.pid})`,
|
`Daemon: running v${status.version || 'unknown'} (pid ${status.pid})`,
|
||||||
);
|
);
|
||||||
|
// If versions mismatch, offer to refresh the systemd service
|
||||||
|
if (status.version && status.version !== cliVersion) {
|
||||||
|
console.log('\nVersion mismatch detected:');
|
||||||
|
console.log(` CLI: v${cliVersion}`);
|
||||||
|
console.log(` Daemon: v${status.version}`);
|
||||||
|
console.log(
|
||||||
|
'\nThis can happen after upgrading tspm. The systemd service may still point to an older version.\n' +
|
||||||
|
'You can refresh the service (equivalent to "tspm disable" then "tspm enable").',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ask the user for confirmation
|
||||||
|
const confirm = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
||||||
|
'Refresh the systemd service now?',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
if (confirm) {
|
||||||
|
try {
|
||||||
|
const sm = new TspmServiceManager();
|
||||||
|
console.log('Refreshing TSPM system service...');
|
||||||
|
await sm.disableService();
|
||||||
|
await sm.enableService();
|
||||||
|
console.log('✓ Service refreshed. Daemon restarted via systemd.');
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(
|
||||||
|
'Failed to refresh service automatically. You can try manually:\n tspm disable && tspm enable',
|
||||||
|
);
|
||||||
|
console.error(err?.message || String(err));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Skipped service refresh.');
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Daemon: not running');
|
console.log('Daemon: not running');
|
||||||
}
|
}
|
||||||
@@ -74,6 +108,7 @@ export const run = async (): Promise<void> => {
|
|||||||
registerDescribeCommand(smartcliInstance);
|
registerDescribeCommand(smartcliInstance);
|
||||||
registerLogsCommand(smartcliInstance);
|
registerLogsCommand(smartcliInstance);
|
||||||
registerEditCommand(smartcliInstance);
|
registerEditCommand(smartcliInstance);
|
||||||
|
registerSearchCommand(smartcliInstance);
|
||||||
|
|
||||||
// Batch commands
|
// Batch commands
|
||||||
registerStartAllCommand(smartcliInstance);
|
registerStartAllCommand(smartcliInstance);
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
// Minimal plugin set for lightweight client startup
|
// Minimal plugin set for lightweight client startup
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
|
import * as smartdaemon from '@push.rocks/smartdaemon';
|
||||||
import * as smartipc from '@push.rocks/smartipc';
|
import * as smartipc from '@push.rocks/smartipc';
|
||||||
|
|
||||||
export { path, smartipc };
|
export { path, smartdaemon, smartipc };
|
||||||
|
|
||||||
|
@@ -155,7 +155,9 @@ export class TspmIpcClient {
|
|||||||
|
|
||||||
const id = toProcessId(processId);
|
const id = toProcessId(processId);
|
||||||
const topic = `logs.${id}`;
|
const topic = `logs.${id}`;
|
||||||
await this.ipcClient.subscribe(`topic:${topic}`, handler);
|
// Note: IpcClient.subscribe expects the bare topic (without the 'topic:' prefix)
|
||||||
|
// and will register a handler for 'topic:<topic>' internally.
|
||||||
|
await this.ipcClient.subscribe(topic, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,7 +170,8 @@ export class TspmIpcClient {
|
|||||||
|
|
||||||
const id = toProcessId(processId);
|
const id = toProcessId(processId);
|
||||||
const topic = `logs.${id}`;
|
const topic = `logs.${id}`;
|
||||||
await this.ipcClient.unsubscribe(`topic:${topic}`);
|
// Pass bare topic; client handles 'topic:' prefix internally
|
||||||
|
await this.ipcClient.unsubscribe(topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from '../paths.js';
|
import * as paths from '../paths.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -156,6 +156,11 @@ export class ProcessManager extends EventEmitter {
|
|||||||
this.updateProcessInfo(config.id, { pid: undefined });
|
this.updateProcessInfo(config.id, { pid: undefined });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set up failure handler to mark process as errored
|
||||||
|
monitor.on('failed', () => {
|
||||||
|
this.updateProcessInfo(config.id, { status: 'errored', pid: undefined });
|
||||||
|
});
|
||||||
|
|
||||||
await monitor.start();
|
await monitor.start();
|
||||||
|
|
||||||
// Wait a moment for the process to spawn and get its PID
|
// Wait a moment for the process to spawn and get its PID
|
||||||
@@ -327,6 +332,11 @@ export class ProcessManager extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark errored on failure events
|
||||||
|
newMonitor.on('failed', () => {
|
||||||
|
this.updateProcessInfo(id, { status: 'errored', pid: undefined });
|
||||||
|
});
|
||||||
|
|
||||||
this.logger.info(`Successfully restarted process with id '${id}'`);
|
this.logger.info(`Successfully restarted process with id '${id}'`);
|
||||||
} catch (error: Error | unknown) {
|
} catch (error: Error | unknown) {
|
||||||
const processError = new ProcessError(
|
const processError = new ProcessError(
|
||||||
|
@@ -18,6 +18,12 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
private processId?: ProcessId;
|
private processId?: ProcessId;
|
||||||
private currentLogMemorySize: number = 0;
|
private currentLogMemorySize: number = 0;
|
||||||
private readonly MAX_LOG_MEMORY_SIZE = 10 * 1024 * 1024; // 10MB
|
private readonly MAX_LOG_MEMORY_SIZE = 10 * 1024 * 1024; // 10MB
|
||||||
|
// Track approximate size per log to avoid O(n) JSON stringify on every update
|
||||||
|
private logSizeMap: WeakMap<IProcessLog, number> = new WeakMap();
|
||||||
|
private restartTimer: NodeJS.Timeout | null = null;
|
||||||
|
private lastRetryAt: number | null = null;
|
||||||
|
private readonly MAX_RETRIES = 10;
|
||||||
|
private readonly RESET_WINDOW_MS = 60 * 60 * 1000; // 1 hour
|
||||||
|
|
||||||
constructor(config: IMonitorConfig & { id?: ProcessId }) {
|
constructor(config: IMonitorConfig & { id?: ProcessId }) {
|
||||||
super();
|
super();
|
||||||
@@ -35,7 +41,13 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
const persistedLogs = await this.logPersistence.loadLogs(this.processId);
|
const persistedLogs = await this.logPersistence.loadLogs(this.processId);
|
||||||
if (persistedLogs.length > 0) {
|
if (persistedLogs.length > 0) {
|
||||||
this.logs = persistedLogs;
|
this.logs = persistedLogs;
|
||||||
this.currentLogMemorySize = LogPersistence.calculateLogMemorySize(this.logs);
|
// Recalculate size once from scratch and seed the size map
|
||||||
|
this.currentLogMemorySize = 0;
|
||||||
|
for (const log of this.logs) {
|
||||||
|
const size = this.estimateLogSize(log);
|
||||||
|
this.logSizeMap.set(log, size);
|
||||||
|
this.currentLogMemorySize += size;
|
||||||
|
}
|
||||||
this.logger.info(`Loaded ${persistedLogs.length} persisted logs from disk`);
|
this.logger.info(`Loaded ${persistedLogs.length} persisted logs from disk`);
|
||||||
|
|
||||||
// Delete the persisted file after loading
|
// Delete the persisted file after loading
|
||||||
@@ -83,18 +95,27 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
this.processWrapper.on('log', (log: IProcessLog): void => {
|
this.processWrapper.on('log', (log: IProcessLog): void => {
|
||||||
// Store the log in our buffer
|
// Store the log in our buffer
|
||||||
this.logs.push(log);
|
this.logs.push(log);
|
||||||
console.error(`[ProcessMonitor:${this.config.name}] Received log (type=${log.type}): ${log.message}`);
|
if (process.env.TSPM_DEBUG) {
|
||||||
console.error(`[ProcessMonitor:${this.config.name}] Logs array now has ${this.logs.length} items`);
|
console.error(
|
||||||
|
`[ProcessMonitor:${this.config.name}] Received log (type=${log.type}): ${log.message}`,
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
`[ProcessMonitor:${this.config.name}] Logs array now has ${this.logs.length} items`,
|
||||||
|
);
|
||||||
|
}
|
||||||
this.logger.debug(`ProcessMonitor received log: ${log.message}`);
|
this.logger.debug(`ProcessMonitor received log: ${log.message}`);
|
||||||
|
|
||||||
// Update memory size tracking
|
// Update memory size tracking incrementally
|
||||||
this.currentLogMemorySize = LogPersistence.calculateLogMemorySize(this.logs);
|
const approxSize = this.estimateLogSize(log);
|
||||||
|
this.logSizeMap.set(log, approxSize);
|
||||||
|
this.currentLogMemorySize += approxSize;
|
||||||
|
|
||||||
// Trim logs if they exceed memory limit (10MB)
|
// Trim logs if they exceed memory limit (10MB)
|
||||||
while (this.currentLogMemorySize > this.MAX_LOG_MEMORY_SIZE && this.logs.length > 1) {
|
while (this.currentLogMemorySize > this.MAX_LOG_MEMORY_SIZE && this.logs.length > 1) {
|
||||||
// Remove oldest logs until we're under the memory limit
|
// Remove oldest logs until we're under the memory limit
|
||||||
this.logs.shift();
|
const removed = this.logs.shift()!;
|
||||||
this.currentLogMemorySize = LogPersistence.calculateLogMemorySize(this.logs);
|
const removedSize = this.logSizeMap.get(removed) ?? this.estimateLogSize(removed);
|
||||||
|
this.currentLogMemorySize -= removedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-emit the log event for upstream handlers
|
// Re-emit the log event for upstream handlers
|
||||||
@@ -118,6 +139,14 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
this.logger.info(exitMsg);
|
this.logger.info(exitMsg);
|
||||||
this.log(exitMsg);
|
this.log(exitMsg);
|
||||||
|
|
||||||
|
// Clear pidusage internal state for this PID to prevent memory leaks
|
||||||
|
try {
|
||||||
|
const pidToClear = this.processWrapper?.getPid();
|
||||||
|
if (pidToClear) {
|
||||||
|
(plugins.pidusage as any)?.clear?.(pidToClear);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
// Flush logs to disk on exit
|
// Flush logs to disk on exit
|
||||||
if (this.processId && this.logs.length > 0) {
|
if (this.processId && this.logs.length > 0) {
|
||||||
try {
|
try {
|
||||||
@@ -132,10 +161,7 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
this.emit('exit', code, signal);
|
this.emit('exit', code, signal);
|
||||||
|
|
||||||
if (!this.stopped) {
|
if (!this.stopped) {
|
||||||
this.logger.info('Restarting process...');
|
this.scheduleRestart('exit');
|
||||||
this.log('Restarting process...');
|
|
||||||
this.restartCount++;
|
|
||||||
this.spawnProcess();
|
|
||||||
} else {
|
} else {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'Not restarting process because monitor is stopped',
|
'Not restarting process because monitor is stopped',
|
||||||
@@ -164,10 +190,7 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.stopped) {
|
if (!this.stopped) {
|
||||||
this.logger.info('Restarting process due to error...');
|
this.scheduleRestart('error');
|
||||||
this.log('Restarting process due to error...');
|
|
||||||
this.restartCount++;
|
|
||||||
this.spawnProcess();
|
|
||||||
} else {
|
} else {
|
||||||
this.logger.debug('Not restarting process because monitor is stopped');
|
this.logger.debug('Not restarting process because monitor is stopped');
|
||||||
}
|
}
|
||||||
@@ -185,6 +208,49 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a restart with incremental debounce and failure cutoff.
|
||||||
|
*/
|
||||||
|
private scheduleRestart(reason: 'exit' | 'error'): void {
|
||||||
|
const now = Date.now();
|
||||||
|
// Reset window: if last retry was more than 1 hour ago, reset counter
|
||||||
|
if (this.lastRetryAt && now - this.lastRetryAt >= this.RESET_WINDOW_MS) {
|
||||||
|
this.logger.info('Resetting retry counter after 1 hour window');
|
||||||
|
this.restartCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already at or above max retries?
|
||||||
|
if (this.restartCount >= this.MAX_RETRIES) {
|
||||||
|
const msg = 'Maximum restart attempts reached. Marking process as failed.';
|
||||||
|
this.logger.warn(msg);
|
||||||
|
this.log(msg);
|
||||||
|
this.stopped = true;
|
||||||
|
// Emit a specific event so manager can set status to errored
|
||||||
|
this.emit('failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment and compute delay (1..10 seconds)
|
||||||
|
this.restartCount++;
|
||||||
|
const delaySec = Math.min(this.restartCount, 10);
|
||||||
|
const msg = `Restarting process in ${delaySec}s (attempt ${this.restartCount}/${this.MAX_RETRIES}) due to ${reason}...`;
|
||||||
|
this.logger.info(msg);
|
||||||
|
this.log(msg);
|
||||||
|
|
||||||
|
// Clear existing timer if any, then schedule
|
||||||
|
if (this.restartTimer) {
|
||||||
|
clearTimeout(this.restartTimer);
|
||||||
|
}
|
||||||
|
this.lastRetryAt = now;
|
||||||
|
this.restartTimer = setTimeout(() => {
|
||||||
|
// If stopped in the meantime, do not spawn
|
||||||
|
if (this.stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.spawnProcess();
|
||||||
|
}, delaySec * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitor the process group's memory usage. If the total memory exceeds the limit,
|
* Monitor the process group's memory usage. If the total memory exceeds the limit,
|
||||||
* kill the process group so that the 'exit' handler can restart it.
|
* kill the process group so that the 'exit' handler can restart it.
|
||||||
@@ -200,12 +266,14 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
`Memory usage for PID ${pid}: ${this.humanReadableBytes(memoryUsage)} (${memoryUsage} bytes)`,
|
`Memory usage for PID ${pid}: ${this.humanReadableBytes(memoryUsage)} (${memoryUsage} bytes)`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Only log to the process log at longer intervals to avoid spamming
|
// Only log memory usage in debug mode to avoid spamming
|
||||||
|
if (process.env.TSPM_DEBUG) {
|
||||||
this.log(
|
this.log(
|
||||||
`Current memory usage for process group (PID ${pid}): ${this.humanReadableBytes(
|
`Current memory usage for process group (PID ${pid}): ${this.humanReadableBytes(
|
||||||
memoryUsage,
|
memoryUsage,
|
||||||
)} (${memoryUsage} bytes)`,
|
)} (${memoryUsage} bytes)`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (memoryUsage > memoryLimit) {
|
if (memoryUsage > memoryLimit) {
|
||||||
const memoryLimitMsg = `Memory usage ${this.humanReadableBytes(
|
const memoryLimitMsg = `Memory usage ${this.humanReadableBytes(
|
||||||
@@ -243,7 +311,7 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
|
|
||||||
plugins.psTree(
|
plugins.psTree(
|
||||||
pid,
|
pid,
|
||||||
(err: Error | null, children: Array<{ PID: string }>) => {
|
(err: any, children: ReadonlyArray<{ PID: string }>) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
const processError = new ProcessError(
|
const processError = new ProcessError(
|
||||||
`Failed to get process tree: ${err.message}`,
|
`Failed to get process tree: ${err.message}`,
|
||||||
@@ -325,6 +393,13 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
clearInterval(this.intervalId);
|
clearInterval(this.intervalId);
|
||||||
}
|
}
|
||||||
if (this.processWrapper) {
|
if (this.processWrapper) {
|
||||||
|
// Clear pidusage state for current PID before stopping to avoid leaks
|
||||||
|
try {
|
||||||
|
const pidToClear = this.processWrapper.getPid();
|
||||||
|
if (pidToClear) {
|
||||||
|
(plugins.pidusage as any)?.clear?.(pidToClear);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
this.processWrapper.stop();
|
this.processWrapper.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,7 +408,11 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
* Get the current logs from the process
|
* Get the current logs from the process
|
||||||
*/
|
*/
|
||||||
public getLogs(limit?: number): IProcessLog[] {
|
public getLogs(limit?: number): IProcessLog[] {
|
||||||
console.error(`[ProcessMonitor:${this.config.name}] getLogs called, logs.length=${this.logs.length}, limit=${limit}`);
|
if (process.env.TSPM_DEBUG) {
|
||||||
|
console.error(
|
||||||
|
`[ProcessMonitor:${this.config.name}] getLogs called, logs.length=${this.logs.length}, limit=${limit}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
this.logger.debug(`Getting logs, total stored: ${this.logs.length}`);
|
this.logger.debug(`Getting logs, total stored: ${this.logs.length}`);
|
||||||
if (limit && limit > 0) {
|
if (limit && limit > 0) {
|
||||||
return this.logs.slice(-limit);
|
return this.logs.slice(-limit);
|
||||||
@@ -376,4 +455,17 @@ export class ProcessMonitor extends EventEmitter {
|
|||||||
const prefix = this.config.name ? `[${this.config.name}] ` : '';
|
const prefix = this.config.name ? `[${this.config.name}] ` : '';
|
||||||
console.log(prefix + message);
|
console.log(prefix + message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate approximate memory size in bytes for a log entry.
|
||||||
|
* Keeps CPU low by avoiding JSON.stringify on the full array.
|
||||||
|
*/
|
||||||
|
private estimateLogSize(log: IProcessLog): number {
|
||||||
|
const messageBytes = Buffer.byteLength(log.message || '', 'utf8');
|
||||||
|
const typeBytes = Buffer.byteLength(log.type || '', 'utf8');
|
||||||
|
const runIdBytes = Buffer.byteLength((log as any).runId || '', 'utf8');
|
||||||
|
// Rough overhead for object structure, keys, timestamp/seq values
|
||||||
|
const overhead = 64;
|
||||||
|
return messageBytes + typeBytes + runIdBytes + overhead;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -90,9 +90,19 @@ export class ProcessWrapper extends EventEmitter {
|
|||||||
|
|
||||||
// Capture stdout
|
// Capture stdout
|
||||||
if (this.process.stdout) {
|
if (this.process.stdout) {
|
||||||
console.error(`[ProcessWrapper] Setting up stdout listener for process ${this.process.pid}`);
|
if (process.env.TSPM_DEBUG) {
|
||||||
|
console.error(
|
||||||
|
`[ProcessWrapper] Setting up stdout listener for process ${this.process.pid}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
this.process.stdout.on('data', (data) => {
|
this.process.stdout.on('data', (data) => {
|
||||||
console.error(`[ProcessWrapper] Received stdout data from PID ${this.process?.pid}: ${data.toString().substring(0, 100)}`);
|
if (process.env.TSPM_DEBUG) {
|
||||||
|
console.error(
|
||||||
|
`[ProcessWrapper] Received stdout data from PID ${this.process?.pid}: ${data
|
||||||
|
.toString()
|
||||||
|
.substring(0, 100)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
// Add data to remainder buffer and split by newlines
|
// Add data to remainder buffer and split by newlines
|
||||||
const text = this.stdoutRemainder + data.toString();
|
const text = this.stdoutRemainder + data.toString();
|
||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
@@ -102,7 +112,9 @@ export class ProcessWrapper extends EventEmitter {
|
|||||||
|
|
||||||
// Process complete lines
|
// Process complete lines
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
|
if (process.env.TSPM_DEBUG) {
|
||||||
console.error(`[ProcessWrapper] Processing stdout line: ${line}`);
|
console.error(`[ProcessWrapper] Processing stdout line: ${line}`);
|
||||||
|
}
|
||||||
this.logger.debug(`Captured stdout: ${line}`);
|
this.logger.debug(`Captured stdout: ${line}`);
|
||||||
this.addLog('stdout', line);
|
this.addLog('stdout', line);
|
||||||
}
|
}
|
||||||
|
@@ -97,9 +97,25 @@ export class TspmDaemon {
|
|||||||
this.tspmInstance.on('process:log', ({ processId, log }) => {
|
this.tspmInstance.on('process:log', ({ processId, log }) => {
|
||||||
// Publish to topic for this process
|
// Publish to topic for this process
|
||||||
const topic = `logs.${processId}`;
|
const topic = `logs.${processId}`;
|
||||||
// Broadcast to all connected clients subscribed to this topic
|
// Deliver only to subscribed clients
|
||||||
if (this.ipcServer) {
|
if (this.ipcServer) {
|
||||||
this.ipcServer.broadcast(`topic:${topic}`, log);
|
try {
|
||||||
|
const topicIndex = (this.ipcServer as any).topicIndex as Map<string, Set<string>> | undefined;
|
||||||
|
const subscribers = topicIndex?.get(topic);
|
||||||
|
if (subscribers && subscribers.size > 0) {
|
||||||
|
// Send directly to subscribers for this topic
|
||||||
|
for (const clientId of subscribers) {
|
||||||
|
this.ipcServer
|
||||||
|
.sendToClient(clientId, `topic:${topic}`, log)
|
||||||
|
.catch((err: any) => {
|
||||||
|
// Surface but don't fail the loop
|
||||||
|
console.error('[IPC] sendToClient error:', err?.message || err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('[IPC] Topic delivery error:', err?.message || err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -208,6 +224,8 @@ export class TspmDaemon {
|
|||||||
async (request: RequestForMethod<'delete'>) => {
|
async (request: RequestForMethod<'delete'>) => {
|
||||||
try {
|
try {
|
||||||
const id = toProcessId(request.id);
|
const id = toProcessId(request.id);
|
||||||
|
// Ensure desired state reflects stopped before deletion
|
||||||
|
await this.tspmInstance.setDesiredState(id, 'stopped');
|
||||||
await this.tspmInstance.delete(id);
|
await this.tspmInstance.delete(id);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -246,18 +264,7 @@ export class TspmDaemon {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
this.ipcServer.onMessage(
|
// Note: 'remove' is only a CLI alias. Daemon exposes 'delete' only.
|
||||||
'remove',
|
|
||||||
async (request: RequestForMethod<'remove'>) => {
|
|
||||||
try {
|
|
||||||
const id = toProcessId(request.id);
|
|
||||||
await this.tspmInstance.delete(id);
|
|
||||||
return { success: true, message: `Process ${id} deleted successfully` };
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to remove process: ${error.message}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
this.ipcServer.onMessage(
|
this.ipcServer.onMessage(
|
||||||
'list',
|
'list',
|
||||||
@@ -291,6 +298,58 @@ export class TspmDaemon {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Resolve target (id:n | name:foo | numeric string) to ProcessId
|
||||||
|
this.ipcServer.onMessage(
|
||||||
|
'resolveTarget',
|
||||||
|
async (request: RequestForMethod<'resolveTarget'>) => {
|
||||||
|
const raw = String(request.target || '').trim();
|
||||||
|
if (!raw) {
|
||||||
|
throw new Error('Empty target');
|
||||||
|
}
|
||||||
|
|
||||||
|
// id:<n>
|
||||||
|
if (/^id:\s*\d+$/i.test(raw)) {
|
||||||
|
const idNum = raw.split(':')[1].trim();
|
||||||
|
const id = toProcessId(idNum);
|
||||||
|
const config = this.tspmInstance.processConfigs.get(id);
|
||||||
|
if (!config) throw new Error(`Process ${id} not found`);
|
||||||
|
return { id, name: config.name } as ResponseForMethod<'resolveTarget'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// name:<label>
|
||||||
|
if (/^name:/i.test(raw)) {
|
||||||
|
const name = raw.slice(raw.indexOf(':') + 1).trim();
|
||||||
|
if (!name) throw new Error('Missing name after name:');
|
||||||
|
const matches = Array.from(this.tspmInstance.processConfigs.values()).filter(
|
||||||
|
(c) => (c.name || '').trim() === name,
|
||||||
|
);
|
||||||
|
if (matches.length === 0) {
|
||||||
|
throw new Error(`No process found with name "${name}"`);
|
||||||
|
}
|
||||||
|
if (matches.length > 1) {
|
||||||
|
const ids = matches.map((c) => String(c.id)).join(', ');
|
||||||
|
throw new Error(
|
||||||
|
`Multiple processes found with name "${name}": ids [${ids}]. Please use id:<n>.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { id: matches[0].id, name } as ResponseForMethod<'resolveTarget'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bare numeric id
|
||||||
|
if (/^\d+$/.test(raw)) {
|
||||||
|
const id = toProcessId(raw);
|
||||||
|
const config = this.tspmInstance.processConfigs.get(id);
|
||||||
|
if (!config) throw new Error(`Process ${id} not found`);
|
||||||
|
return { id, name: config.name } as ResponseForMethod<'resolveTarget'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown format
|
||||||
|
throw new Error(
|
||||||
|
'Unsupported target format. Use numeric id (e.g. 1), id:<n> (e.g. id:1), or name:<label> (e.g. name:api).',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Batch operations handlers
|
// Batch operations handlers
|
||||||
this.ipcServer.onMessage(
|
this.ipcServer.onMessage(
|
||||||
'startAll',
|
'startAll',
|
||||||
|
@@ -240,14 +240,6 @@ export interface AddResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove (delete config and stop if running)
|
// Remove (delete config and stop if running)
|
||||||
export interface RemoveRequest {
|
|
||||||
id: ProcessId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RemoveResponse {
|
|
||||||
success: boolean;
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update (modify existing config)
|
// Update (modify existing config)
|
||||||
export interface UpdateRequest {
|
export interface UpdateRequest {
|
||||||
@@ -260,6 +252,16 @@ export interface UpdateResponse {
|
|||||||
config: IProcessConfig;
|
config: IProcessConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve a user-provided target (id:n or name:foo or numeric string) to a ProcessId
|
||||||
|
export interface ResolveTargetRequest {
|
||||||
|
target: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResolveTargetResponse {
|
||||||
|
id: ProcessId;
|
||||||
|
name?: 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 };
|
||||||
@@ -269,7 +271,6 @@ export type IpcMethodMap = {
|
|||||||
delete: { request: DeleteRequest; response: DeleteResponse };
|
delete: { request: DeleteRequest; response: DeleteResponse };
|
||||||
add: { request: AddRequest; response: AddResponse };
|
add: { request: AddRequest; response: AddResponse };
|
||||||
update: { request: UpdateRequest; response: UpdateResponse };
|
update: { request: UpdateRequest; response: UpdateResponse };
|
||||||
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 };
|
||||||
@@ -286,6 +287,7 @@ export type IpcMethodMap = {
|
|||||||
response: DaemonShutdownResponse;
|
response: DaemonShutdownResponse;
|
||||||
};
|
};
|
||||||
heartbeat: { request: HeartbeatRequest; response: HeartbeatResponse };
|
heartbeat: { request: HeartbeatRequest; response: HeartbeatResponse };
|
||||||
|
resolveTarget: { request: ResolveTargetRequest; response: ResolveTargetResponse };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper type to extract request type for a method
|
// Helper type to extract request type for a method
|
||||||
|
Reference in New Issue
Block a user