Compare commits

...

6 Commits

Author SHA1 Message Date
3676bff04c 4.4.1
Some checks failed
Default (tags) / security (push) Successful in 50s
Default (tags) / test (push) Failing after 3m58s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-29 21:10:01 +00:00
dfe0677cab fix(cli): Use server-side start-by-id flow for starting processes 2025-08-29 21:10:01 +00:00
611b756670 4.4.0
Some checks failed
Default (tags) / security (push) Successful in 39s
Default (tags) / test (push) Failing after 3m59s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-29 17:27:32 +00:00
2291348774 feat(daemon): Persist desired process states and add daemon restart command 2025-08-29 17:27:32 +00:00
504725043d 4.3.1
Some checks failed
Default (tags) / security (push) Successful in 50s
Default (tags) / test (push) Failing after 3m59s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-29 17:16:40 +00:00
e16a3fb845 fix(daemon): Fix daemon describe handler to return correct process info and config; bump @push.rocks/smartipc to ^2.2.2 2025-08-29 17:16:40 +00:00
10 changed files with 234 additions and 176 deletions

View File

@@ -1,5 +1,27 @@
# Changelog
## 2025-08-29 - 4.4.1 - fix(cli)
Use server-side start-by-id flow for starting processes
- CLI: 'tspm start <id>' now calls a new 'startById' IPC method instead of fetching the full config via 'describe' and submitting it back to 'start'.
- Daemon: Added server-side handler for 'startById' which resolves the stored process config and starts the process on the daemon.
- Protocol: Added StartByIdRequest/StartByIdResponse types and registered 'startById' in the IPC method map.
## 2025-08-29 - 4.4.0 - feat(daemon)
Persist desired process states and add daemon restart command
- Persist desired process states: ProcessManager now stores desiredStates to user storage (desiredStates key) and reloads them on startup.
- Start/stop operations update desired state: IPC handlers in the daemon now set desired state when processes are started, stopped, restarted or when batch start/stop is invoked.
- Resume desired state on daemon start: Daemon loads desired states and calls startDesired() to bring processes to their desired 'online' state after startup.
- Remove desired state on deletion/reset: Deleting a process or resetting clears its desired state; reset clears all desired states as well.
- CLI: Added 'tspm daemon restart' — stops the daemon (gracefully) and restarts it in the foreground for the current session, with checks and informative output.
## 2025-08-29 - 4.3.1 - fix(daemon)
Fix daemon describe handler to return correct process info and config; bump @push.rocks/smartipc to ^2.2.2
- Corrected the 'describe' IPC handler in the daemon to use ProcessManager.describe(...) result and return { processInfo, config } — this fixes a mismatch between the handler and the ProcessManager.describe() return shape.
- Bumped dependency @push.rocks/smartipc to ^2.2.2 in package.json.
## 2025-08-29 - 4.3.0 - feat(cli)
Correct CLI plugin imports and add reset command/IPC to stop processes and clear persisted configs

View File

@@ -1,6 +1,6 @@
{
"name": "@git.zone/tspm",
"version": "4.3.0",
"version": "4.4.1",
"private": false,
"description": "a no fuzz process manager",
"main": "dist_ts/index.js",
@@ -35,9 +35,9 @@
"@push.rocks/npmextra": "^5.3.3",
"@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/smartcli": "^4.0.11",
"@push.rocks/smartinteract": "^2.0.16",
"@push.rocks/smartdaemon": "^2.0.9",
"@push.rocks/smartipc": "^2.2.1",
"@push.rocks/smartinteract": "^2.0.16",
"@push.rocks/smartipc": "^2.2.2",
"@push.rocks/smartpath": "^6.0.0",
"pidusage": "^4.0.1",
"ps-tree": "^1.2.0",

162
pnpm-lock.yaml generated
View File

@@ -24,8 +24,8 @@ importers:
specifier: ^2.0.16
version: 2.0.16
'@push.rocks/smartipc':
specifier: ^2.2.1
version: 2.2.1
specifier: ^2.2.2
version: 2.2.2
'@push.rocks/smartpath':
specifier: ^6.0.0
version: 6.0.0
@@ -865,8 +865,8 @@ packages:
'@push.rocks/smartinteract@2.0.16':
resolution: {integrity: sha512-eltvVRRUKBKd77DSFA4DPY2g4V4teZLNe8A93CDy/WglglYcUjxMoLY/b0DFTWCWKYT+yjk6Fe6p0FRrvX9Yvg==}
'@push.rocks/smartipc@2.2.1':
resolution: {integrity: sha512-yBFZwJsWRyVdN1YRSiHafRMfn0PYIi2IStcQqPkiU4Srr6XPDMZD3mmIeV2V1WL6bWvRWf+4WF9Y+rLhj4jGdA==}
'@push.rocks/smartipc@2.2.2':
resolution: {integrity: sha512-pkWqp2nQH7p5zD9Efh5KNX2O0+gFWL6bxbdd6SdDh4gP8Gb0b3Sn87Tpedghpc/d+LCVql+1pUf6OlvMQpD5Yw==}
'@push.rocks/smartjson@5.0.20':
resolution: {integrity: sha512-ogGBLyOTluphZVwBYNyjhm5sziPGuiAwWihW07OSRxD4HQUyqj9Ek6r1pqH07JUG5EbtRYivM1Yt1cCwnu3JVQ==}
@@ -1641,9 +1641,6 @@ packages:
'@types/node-forge@1.3.14':
resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==}
'@types/node-ipc@9.2.3':
resolution: {integrity: sha512-/MvSiF71fYf3+zwqkh/zkVkZj1hl1Uobre9EMFy08mqfJNAmpR0vmPgOUdEIDVgifxHj6G1vYMPLSBLLxoDACQ==}
'@types/node@22.13.10':
resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==}
@@ -2042,9 +2039,6 @@ packages:
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
engines: {node: '>= 12'}
cliui@7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -2141,10 +2135,6 @@ packages:
resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}
engines: {node: '>= 0.8'}
copyfiles@2.4.1:
resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==}
hasBin: true
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@@ -2317,10 +2307,6 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
easy-stack@1.0.1:
resolution: {integrity: sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==}
engines: {node: '>=6.0.0'}
ee-first@1.1.1:
resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
@@ -2441,10 +2427,6 @@ packages:
resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=}
engines: {node: '>= 0.6'}
event-pubsub@5.0.3:
resolution: {integrity: sha512-2QiHxshejKgJrYMzSI9MEHrvhmzxBL+eLyiM5IiyjDBySkgwS2+tdtnO3gbx8pEisu/yOFCIhfCb63gCEu0yBQ==}
engines: {node: '>=13.0.0'}
event-stream@3.3.4:
resolution: {integrity: sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=}
@@ -2975,9 +2957,6 @@ packages:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
engines: {node: '>=8'}
isarray@0.0.1:
resolution: {integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=}
isarray@1.0.0:
resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=}
@@ -3041,14 +3020,6 @@ packages:
js-base64@3.7.7:
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
js-message@1.0.7:
resolution: {integrity: sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==}
engines: {node: '>=0.6.0'}
js-queue@2.0.2:
resolution: {integrity: sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==}
engines: {node: '>=1.0.0'}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -3552,13 +3523,6 @@ packages:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'}
node-ipc@12.0.0:
resolution: {integrity: sha512-QHJ2gAJiqA3cM7cQiRjLsfCOBRB0TwQ6axYD4FSllQWipEbP6i7Se1dP8EzPKk5J1nCe27W69eqPmCoKyQ61Vg==}
engines: {node: '>=14'}
noms@0.0.0:
resolution: {integrity: sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==}
normalize-newline@4.1.0:
resolution: {integrity: sha512-ff4jKqMI8Xl50/4Mms/9jPobzAV/UK+kXG2XJ/7AqOmxIx8mqfqTIHYxuAnEgJ2AQeBbLnlbmZ5+38Y9A0w/YA==}
engines: {node: '>=12'}
@@ -3869,9 +3833,6 @@ packages:
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
readable-stream@1.0.34:
resolution: {integrity: sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=}
readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
@@ -4158,9 +4119,6 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
string_decoder@0.10.31:
resolution: {integrity: sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=}
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
@@ -4200,14 +4158,6 @@ packages:
strnum@2.1.1:
resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==}
strong-type@0.1.6:
resolution: {integrity: sha512-eJe5caH6Pi5oMMeQtIoBPpvNu/s4jiyb63u5tkHNnQXomK+puyQ5i+Z5iTLBr/xUz/pIcps0NSfzzFI34+gAXg==}
engines: {node: '>=12.0.0'}
strong-type@1.1.0:
resolution: {integrity: sha512-X5Z6riticuH5GnhUyzijfDi1SoXas8ODDyN7K8lJeQK+Jfi4dKdoJGL4CXTskY/ATBcN+rz5lROGn1tAUkOX7g==}
engines: {node: '>=12.21.0'}
strtok3@10.3.4:
resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==}
engines: {node: '>=18'}
@@ -4261,9 +4211,6 @@ packages:
threads@1.7.0:
resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==}
through2@2.0.5:
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
through2@4.0.2:
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
@@ -4422,10 +4369,6 @@ packages:
resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=}
engines: {node: '>= 0.8'}
untildify@4.0.0:
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
engines: {node: '>=8'}
upper-case@1.1.3:
resolution: {integrity: sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=}
@@ -4551,26 +4494,14 @@ packages:
resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
engines: {node: '>=0.4.0'}
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yargs-parser@20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
@@ -6427,12 +6358,10 @@ snapshots:
'@push.rocks/smartpromise': 4.2.3
inquirer: 11.1.0
'@push.rocks/smartipc@2.2.1':
'@push.rocks/smartipc@2.2.2':
dependencies:
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartrx': 3.0.10
'@types/node-ipc': 9.2.3
node-ipc: 12.0.0
'@push.rocks/smartjson@5.0.20':
dependencies:
@@ -7689,10 +7618,6 @@ snapshots:
dependencies:
'@types/node': 22.13.10
'@types/node-ipc@9.2.3':
dependencies:
'@types/node': 22.13.10
'@types/node@22.13.10':
dependencies:
undici-types: 6.20.0
@@ -8130,12 +8055,6 @@ snapshots:
cli-width@4.1.0: {}
cliui@7.0.4:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@@ -8227,16 +8146,6 @@ snapshots:
depd: 2.0.0
keygrip: 1.1.0
copyfiles@2.4.1:
dependencies:
glob: 7.2.3
minimatch: 3.1.2
mkdirp: 1.0.4
noms: 0.0.0
through2: 2.0.5
untildify: 4.0.0
yargs: 16.2.0
core-util-is@1.0.3: {}
cors@2.8.5:
@@ -8373,8 +8282,6 @@ snapshots:
eastasianwidth@0.2.0: {}
easy-stack@1.0.1: {}
ee-first@1.1.1: {}
emoji-regex@8.0.0: {}
@@ -8507,11 +8414,6 @@ snapshots:
etag@1.8.1: {}
event-pubsub@5.0.3:
dependencies:
copyfiles: 2.4.1
strong-type: 0.1.6
event-stream@3.3.4:
dependencies:
duplexer: 0.1.2
@@ -9162,8 +9064,6 @@ snapshots:
dependencies:
is-docker: 2.2.1
isarray@0.0.1: {}
isarray@1.0.0: {}
isbinaryfile@5.0.4: {}
@@ -9240,12 +9140,6 @@ snapshots:
js-base64@3.7.7: {}
js-message@1.0.7: {}
js-queue@2.0.2:
dependencies:
easy-stack: 1.0.1
js-tokens@4.0.0: {}
js-yaml@3.14.1:
@@ -9939,18 +9833,6 @@ snapshots:
node-forge@1.3.1: {}
node-ipc@12.0.0:
dependencies:
event-pubsub: 5.0.3
js-message: 1.0.7
js-queue: 2.0.2
strong-type: 1.1.0
noms@0.0.0:
dependencies:
inherits: 2.0.4
readable-stream: 1.0.34
normalize-newline@4.1.0:
dependencies:
replace-buffer: 1.2.1
@@ -10269,13 +10151,6 @@ snapshots:
react-is@18.3.1: {}
readable-stream@1.0.34:
dependencies:
core-util-is: 1.0.3
inherits: 2.0.4
isarray: 0.0.1
string_decoder: 0.10.31
readable-stream@2.3.8:
dependencies:
core-util-is: 1.0.3
@@ -10673,8 +10548,6 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.1.0
string_decoder@0.10.31: {}
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2
@@ -10710,10 +10583,6 @@ snapshots:
strnum@2.1.1: {}
strong-type@0.1.6: {}
strong-type@1.1.0: {}
strtok3@10.3.4:
dependencies:
'@tokenizer/token': 0.3.0
@@ -10778,11 +10647,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
through2@2.0.5:
dependencies:
readable-stream: 2.3.8
xtend: 4.0.2
through2@4.0.2:
dependencies:
readable-stream: 3.6.2
@@ -10923,8 +10787,6 @@ snapshots:
unpipe@1.0.0: {}
untildify@4.0.0: {}
upper-case@1.1.3: {}
url@0.11.4:
@@ -11031,24 +10893,10 @@ snapshots:
xmlhttprequest-ssl@2.1.2: {}
xtend@4.0.2: {}
y18n@5.0.8: {}
yargs-parser@20.2.9: {}
yargs-parser@21.1.1: {}
yargs@16.2.0:
dependencies:
cliui: 7.0.4
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 20.2.9
yargs@17.7.2:
dependencies:
cliui: 8.0.1

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@git.zone/tspm',
version: '4.3.0',
version: '4.4.1',
description: 'a no fuzz process manager'
}

View File

@@ -80,6 +80,48 @@ export function registerDaemonCommand(smartcli: plugins.smartcli.Smartcli) {
}
break;
case 'restart':
try {
console.log('Restarting TSPM daemon...');
await tspmIpcClient.stopDaemon(true);
// Reuse the manual start logic from 'start'
const statusAfterStop = await tspmIpcClient.getDaemonStatus();
if (statusAfterStop) {
console.warn('Daemon still appears to be running; proceeding to start anyway.');
}
console.log('Starting TSPM daemon manually...');
const { spawn } = await import('child_process');
const daemonScript = plugins.path.join(
paths.packageDir,
'dist_ts',
'daemon.js',
);
const daemonProcess = spawn(process.execPath, [daemonScript], {
detached: true,
stdio: process.env.TSPM_DEBUG === 'true' ? 'inherit' : 'ignore',
env: { ...process.env, TSPM_DAEMON_MODE: 'true' },
});
daemonProcess.unref();
console.log(`Started daemon with PID: ${daemonProcess.pid}`);
await new Promise((resolve) => setTimeout(resolve, 2000));
const newStatus = await tspmIpcClient.getDaemonStatus();
if (newStatus) {
console.log('✓ TSPM daemon restarted successfully');
console.log(` PID: ${newStatus.pid}`);
} else {
console.warn('\n⚠ Warning: Daemon restart attempted but status is unavailable.');
}
await tspmIpcClient.disconnect();
} catch (error) {
console.error('Error restarting daemon:', (error as any).message || String(error));
process.exit(1);
}
break;
case 'start-service':
// This is called by systemd - start the daemon directly
console.log('Starting TSPM daemon for systemd service...');
@@ -135,6 +177,7 @@ export function registerDaemonCommand(smartcli: plugins.smartcli.Smartcli) {
console.log('Usage: tspm daemon <command>');
console.log('\nCommands:');
console.log(' start Start the TSPM daemon');
console.log(' restart Restart the TSPM daemon');
console.log(' stop Stop the TSPM daemon');
console.log(' status Show daemon status');
break;

View File

@@ -17,14 +17,8 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
return;
}
const desc = await tspmIpcClient.request('describe', { id }).catch(() => null);
if (!desc) {
console.error(`Process with id '${id}' not found. Use 'tspm add' first.`);
return;
}
console.log(`Starting process id ${id} (${desc.config.name || id})...`);
const response = await tspmIpcClient.request('start', { config: desc.config });
console.log(`Starting process id ${id}...`);
const response = await tspmIpcClient.request('startById', { id });
console.log('✓ Process started');
console.log(` ID: ${response.processId}`);
console.log(` PID: ${response.pid || 'N/A'}`);

View File

@@ -1,4 +1,5 @@
import * as plugins from './plugins.js';
import { tspmIpcClient } from '../client/tspm.ipcclient.js';
import * as paths from '../paths.js';
import { Logger, LogLevel } from '../shared/common/utils.errorhandler.js';
@@ -38,6 +39,22 @@ export const run = async (): Promise<void> => {
}
const smartcliInstance = new plugins.smartcli.Smartcli();
// Intercept -v/--version to show CLI and daemon versions
const args = process.argv.slice(2);
if (args.includes('-v') || args.includes('--version')) {
const cliVersion = tspmProjectinfo.npm.version;
console.log(`tspm CLI: ${cliVersion}`);
const status = await tspmIpcClient.getDaemonStatus();
if (status) {
console.log(
`Daemon: running v${status.version || 'unknown'} (pid ${status.pid})`,
);
} else {
console.log('Daemon: not running');
}
return; // do not start parser
}
// Keep Smartcli version info for help output but not used for -v now
smartcliInstance.addVersion(tspmProjectinfo.npm.version);
// Register all commands

View File

@@ -25,6 +25,8 @@ export class ProcessManager extends EventEmitter {
public processInfo: Map<string, IProcessInfo> = new Map();
private config: TspmConfig;
private configStorageKey = 'processes';
private desiredStateStorageKey = 'desiredStates';
private desiredStates: Map<string, IProcessInfo['status']> = new Map();
private logger: Logger;
constructor() {
@@ -32,6 +34,7 @@ export class ProcessManager extends EventEmitter {
this.logger = new Logger('Tspm');
this.config = new TspmConfig();
this.loadProcessConfigs();
this.loadDesiredStates();
}
/**
@@ -67,6 +70,7 @@ export class ProcessManager extends EventEmitter {
});
await this.saveProcessConfigs();
await this.setDesiredState(config.id, 'stopped');
return config.id;
}
@@ -279,6 +283,7 @@ export class ProcessManager extends EventEmitter {
// Save updated configs
await this.saveProcessConfigs();
await this.removeDesiredState(id);
this.logger.info(`Successfully deleted process with id '${id}'`);
} catch (error: Error | unknown) {
@@ -288,6 +293,7 @@ export class ProcessManager extends EventEmitter {
this.processConfigs.delete(id);
this.processInfo.delete(id);
await this.saveProcessConfigs();
await this.removeDesiredState(id);
this.logger.info(
`Successfully deleted process with id '${id}' after stopping failure`,
@@ -415,6 +421,80 @@ export class ProcessManager extends EventEmitter {
}
}
// === Desired state persistence ===
private async saveDesiredStates(): Promise<void> {
try {
const obj: Record<string, IProcessInfo['status']> = {};
for (const [id, state] of this.desiredStates.entries()) {
obj[id] = state;
}
await this.config.writeKey(
this.desiredStateStorageKey,
JSON.stringify(obj),
);
} catch (error: any) {
this.logger.warn(
`Failed to save desired states: ${error?.message || String(error)}`,
);
}
}
public async loadDesiredStates(): Promise<void> {
try {
const raw = await this.config.readKey(this.desiredStateStorageKey);
if (raw) {
const obj = JSON.parse(raw) as Record<string, IProcessInfo['status']>;
this.desiredStates = new Map(Object.entries(obj));
this.logger.debug(
`Loaded desired states for ${this.desiredStates.size} processes`,
);
}
} catch (error: any) {
this.logger.warn(
`Failed to load desired states: ${error?.message || String(error)}`,
);
}
}
public async setDesiredState(
id: string,
state: IProcessInfo['status'],
): Promise<void> {
this.desiredStates.set(id, state);
await this.saveDesiredStates();
}
public async removeDesiredState(id: string): Promise<void> {
this.desiredStates.delete(id);
await this.saveDesiredStates();
}
public async setDesiredStateForAll(
state: IProcessInfo['status'],
): Promise<void> {
for (const id of this.processConfigs.keys()) {
this.desiredStates.set(id, state);
}
await this.saveDesiredStates();
}
public async startDesired(): Promise<void> {
for (const [id, config] of this.processConfigs.entries()) {
const desired = this.desiredStates.get(id);
if (desired === 'online' && !this.processes.has(id)) {
try {
await this.start(config);
} catch (e) {
this.logger.warn(
`Failed to start desired process ${id}: ${
(e as Error)?.message || String(e)
}`,
);
}
}
}
}
/**
* Load process configurations from config storage
*/
@@ -499,10 +579,12 @@ export class ProcessManager extends EventEmitter {
this.processes.clear();
this.processInfo.clear();
this.processConfigs.clear();
this.desiredStates.clear();
// Remove persisted configs
try {
await this.config.deleteKey(this.configStorageKey);
await this.config.deleteKey(this.desiredStateStorageKey).catch(() => {});
this.logger.debug('Cleared persisted process configurations');
} catch (error) {
// Fallback: write empty list if deleteKey fails for any reason

View File

@@ -20,12 +20,20 @@ export class TspmDaemon {
private socketPath: string;
private heartbeatInterval: NodeJS.Timeout | null = null;
private daemonPidFile: string;
private version: string;
constructor() {
this.tspmInstance = new ProcessManager();
this.socketPath = plugins.path.join(paths.tspmDir, 'tspm.sock');
this.daemonPidFile = plugins.path.join(paths.tspmDir, 'daemon.pid');
this.startTime = Date.now();
// Determine daemon version from package metadata
try {
const proj = new plugins.projectinfo.ProjectInfo(paths.packageDir);
this.version = proj.npm.version || 'unknown';
} catch {
this.version = 'unknown';
}
}
/**
@@ -81,6 +89,7 @@ export class TspmDaemon {
// Load existing process configurations
await this.tspmInstance.loadProcessConfigs();
await this.tspmInstance.loadDesiredStates();
// Set up log publishing
this.tspmInstance.on('process:log', ({ processId, log }) => {
@@ -95,6 +104,9 @@ export class TspmDaemon {
// Set up graceful shutdown handlers
this.setupShutdownHandlers();
// Start processes that should be online per desired state
await this.tspmInstance.startDesired();
console.log(`TSPM daemon started successfully on ${this.socketPath}`);
console.log(`PID: ${process.pid}`);
}
@@ -108,6 +120,7 @@ export class TspmDaemon {
'start',
async (request: RequestForMethod<'start'>) => {
try {
await this.tspmInstance.setDesiredState(request.config.id, 'online');
await this.tspmInstance.start(request.config);
const processInfo = this.tspmInstance.processInfo.get(
request.config.id,
@@ -123,10 +136,34 @@ export class TspmDaemon {
},
);
// Start by id (resolve config on server)
this.ipcServer.onMessage(
'startById',
async (request: RequestForMethod<'startById'>) => {
try {
const config = this.tspmInstance.processConfigs.get(request.id);
if (!config) {
throw new Error(`Process ${request.id} not found`);
}
await this.tspmInstance.setDesiredState(request.id, 'online');
await this.tspmInstance.start(config);
const processInfo = this.tspmInstance.processInfo.get(request.id);
return {
processId: request.id,
pid: processInfo?.pid,
status: processInfo?.status || 'stopped',
};
} catch (error) {
throw new Error(`Failed to start process: ${error.message}`);
}
},
);
this.ipcServer.onMessage(
'stop',
async (request: RequestForMethod<'stop'>) => {
try {
await this.tspmInstance.setDesiredState(request.id, 'stopped');
await this.tspmInstance.stop(request.id);
return {
success: true,
@@ -142,6 +179,7 @@ export class TspmDaemon {
'restart',
async (request: RequestForMethod<'restart'>) => {
try {
await this.tspmInstance.setDesiredState(request.id, 'online');
await this.tspmInstance.restart(request.id);
const processInfo = this.tspmInstance.processInfo.get(request.id);
return {
@@ -207,16 +245,14 @@ export class TspmDaemon {
this.ipcServer.onMessage(
'describe',
async (request: RequestForMethod<'describe'>) => {
const processInfo = await this.tspmInstance.describe(request.id);
const config = this.tspmInstance.processConfigs.get(request.id);
if (!processInfo || !config) {
const result = await this.tspmInstance.describe(request.id);
if (!result) {
throw new Error(`Process ${request.id} not found`);
}
// Return correctly shaped response
return {
processInfo,
config,
processInfo: result.info,
config: result.config,
};
},
);
@@ -236,6 +272,7 @@ export class TspmDaemon {
const started: string[] = [];
const failed: Array<{ id: string; error: string }> = [];
await this.tspmInstance.setDesiredStateForAll('online');
await this.tspmInstance.startAll();
// Get status of all processes
@@ -257,6 +294,7 @@ export class TspmDaemon {
const stopped: string[] = [];
const failed: Array<{ id: string; error: string }> = [];
await this.tspmInstance.setDesiredStateForAll('stopped');
await this.tspmInstance.stopAll();
// Get status of all processes
@@ -314,6 +352,7 @@ export class TspmDaemon {
processCount: this.tspmInstance.processes.size,
memoryUsage: memUsage.heapUsed,
cpuUsage: process.cpuUsage().user / 1000000, // Convert to seconds
version: this.version,
};
},
);

View File

@@ -66,6 +66,17 @@ export interface StartResponse {
status: 'online' | 'stopped' | 'errored';
}
// Start by id (server resolves config)
export interface StartByIdRequest {
id: string;
}
export interface StartByIdResponse {
processId: string;
pid?: number;
status: 'online' | 'stopped' | 'errored';
}
// Stop command
export interface StopRequest {
id: string;
@@ -191,6 +202,7 @@ export interface DaemonStatusResponse {
processCount: number;
memoryUsage?: number;
cpuUsage?: number;
version?: string;
}
// Daemon shutdown command
@@ -238,6 +250,7 @@ export interface RemoveResponse {
// Type mappings for methods
export type IpcMethodMap = {
start: { request: StartRequest; response: StartResponse };
startById: { request: StartByIdRequest; response: StartByIdResponse };
stop: { request: StopRequest; response: StopResponse };
restart: { request: RestartRequest; response: RestartResponse };
delete: { request: DeleteRequest; response: DeleteResponse };