10 Commits

Author SHA1 Message Date
5c4836fd68 1.5.0
Some checks failed
Default (tags) / security (push) Successful in 39s
Default (tags) / test (push) Failing after 44s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-04 11:44:55 +00:00
2dc766fa6e feat(cli): Enhance CLI with new process management commands 2025-03-04 11:44:55 +00:00
0232741b89 1.4.0
Some checks failed
Default (tags) / security (push) Successful in 38s
Default (tags) / test (push) Failing after 1h14m52s
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-03-03 05:21:52 +00:00
9c1327c9be feat(core): Introduced process management features using ProcessWrapper and enhanced configuration. 2025-03-03 05:21:52 +00:00
74bfcb273a 1.3.1
Some checks failed
Default (tags) / security (push) Successful in 36s
Default (tags) / test (push) Successful in 53s
Default (tags) / release (push) Failing after 47s
Default (tags) / metadata (push) Successful in 56s
2025-03-01 19:47:46 +00:00
cefbce1ba0 fix(test): Update test script to fix type references and remove private method call 2025-03-01 19:47:46 +00:00
51bb3a8967 1.3.0
Some checks failed
Default (tags) / security (push) Successful in 37s
Default (tags) / test (push) Successful in 51s
Default (tags) / release (push) Failing after 47s
Default (tags) / metadata (push) Successful in 1m0s
2025-03-01 19:19:28 +00:00
c4a082031e feat(cli): Add CLI support with command parsing and version display 2025-03-01 19:19:28 +00:00
761f9ca1b6 1.2.0
Some checks failed
Default (tags) / security (push) Successful in 36s
Default (tags) / test (push) Successful in 52s
Default (tags) / release (push) Failing after 46s
Default (tags) / metadata (push) Successful in 59s
2025-03-01 18:02:40 +00:00
ad2c180cfe feat(core): Introduce ProcessMonitor with memory management and spawning features 2025-03-01 18:02:40 +00:00
13 changed files with 1180 additions and 75 deletions

View File

@ -1,5 +1,42 @@
# Changelog # Changelog
## 2025-03-04 - 1.5.0 - feat(cli)
Enhance CLI with new process management commands
- Added comprehensive CLI commands for process management including start, stop, restart, list, describe and logs.
- Implemented memory string parsing for process memory limits.
- Enhanced CLI output with formatted table listings for active processes.
## 2025-03-03 - 1.4.0 - feat(core)
Introduced process management features using ProcessWrapper and enhanced configuration.
- Added ProcessWrapper for wrapping and managing child processes.
- Refactored process monitoring logic using ProcessWrapper.
- Introduced TspmConfig for configuration handling.
- Enhanced CLI to support new process management commands like 'startAsDaemon'.
## 2025-03-01 - 1.3.1 - fix(test)
Update test script to fix type references and remove private method call
- Corrected type references in test script for IMonitorConfig.
- Fixed test script to use console.log instead of private method monitor.log.
## 2025-03-01 - 1.3.0 - feat(cli)
Add CLI support with command parsing and version display
- Added a basic CLI interface using smartcli.
- Implemented command parsing with a 'restart' command.
- Integrated project version display in the CLI.
## 2025-03-01 - 1.2.0 - feat(core)
Introduce ProcessMonitor with memory management and spawning features
- Added ProcessMonitor class with functionality to manage process execution and memory usage.
- Implemented process spawning with ability to handle command arguments and directories.
- Added periodic memory monitoring and automatic restarts when memory thresholds are exceeded.
- ProcessMonitor now logs its actions with optional configuration name for better identification.
- Updated test file to include example usage of ProcessMonitor.
## 2025-03-01 - 1.1.1 - fix(package) ## 2025-03-01 - 1.1.1 - fix(package)
Update dependencies and pnpm configuration Update dependencies and pnpm configuration

View File

@ -1,6 +1,6 @@
{ {
"name": "@git.zone/tspm", "name": "@git.zone/tspm",
"version": "1.1.1", "version": "1.5.0",
"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",
@ -11,7 +11,11 @@
"scripts": { "scripts": {
"test": "(tstest test/ --web)", "test": "(tstest test/ --web)",
"build": "(tsbuild --web --allowimplicitany)", "build": "(tsbuild --web --allowimplicitany)",
"buildDocs": "(tsdoc)" "buildDocs": "(tsdoc)",
"start": "(tsrun ./cli.ts -v)"
},
"bin": {
"tspm": "./cli.js"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.1.25", "@git.zone/tsbuild": "^2.1.25",
@ -22,7 +26,13 @@
"@types/node": "^22.13.8" "@types/node": "^22.13.8"
}, },
"dependencies": { "dependencies": {
"@push.rocks/smartpath": "^5.0.18" "@push.rocks/npmextra": "^5.1.2",
"@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/smartcli": "^4.0.11",
"@push.rocks/smartdaemon": "^2.0.6",
"@push.rocks/smartpath": "^5.0.18",
"pidusage": "^4.0.0",
"ps-tree": "^1.2.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

200
pnpm-lock.yaml generated
View File

@ -8,9 +8,27 @@ importers:
.: .:
dependencies: dependencies:
'@push.rocks/npmextra':
specifier: ^5.1.2
version: 5.1.2
'@push.rocks/projectinfo':
specifier: ^5.0.2
version: 5.0.2
'@push.rocks/smartcli':
specifier: ^4.0.11
version: 4.0.11
'@push.rocks/smartdaemon':
specifier: ^2.0.6
version: 2.0.6
'@push.rocks/smartpath': '@push.rocks/smartpath':
specifier: ^5.0.18 specifier: ^5.0.18
version: 5.0.18 version: 5.0.18
pidusage:
specifier: ^4.0.0
version: 4.0.0
ps-tree:
specifier: ^1.2.0
version: 1.2.0
devDependencies: devDependencies:
'@git.zone/tsbuild': '@git.zone/tsbuild':
specifier: ^2.1.25 specifier: ^2.1.25
@ -701,6 +719,12 @@ packages:
'@push.rocks/mongodump@1.0.8': '@push.rocks/mongodump@1.0.8':
resolution: {integrity: sha512-oDufyjNBg8I50OaJvbHhc0RnRpJQ544dr9her0G6sA8JmI3hD2/amTdcPLVIX1kzYf5GsTUKeWuRaZgdNqz3ew==} resolution: {integrity: sha512-oDufyjNBg8I50OaJvbHhc0RnRpJQ544dr9her0G6sA8JmI3hD2/amTdcPLVIX1kzYf5GsTUKeWuRaZgdNqz3ew==}
'@push.rocks/npmextra@5.1.2':
resolution: {integrity: sha512-0utZEsQSUDgFG6nGcm66Dh4DgPwqpUQcEAOtJKvubXIFRaOzQ3Yp6M8GKeL5VwxgFxWWtqp9xP8NxLEtHN9UcA==}
'@push.rocks/projectinfo@5.0.2':
resolution: {integrity: sha512-zzieCal6jwR++o+fDl8gMpWkNV2cGEsbT96vCNZu/H9kr0iqRmapOiA4DFadkhOnhlDqvRr6TPaXESu2YUbI8Q==}
'@push.rocks/qenv@6.1.0': '@push.rocks/qenv@6.1.0':
resolution: {integrity: sha512-1FUFMlSVwFSFg8LbqfkzJ2LLP4lMGApUtgOpsvrde6+AxBmB4gjoNgCUH7z3xXfDAtYqcrtSELXBNE0xVL1MqQ==} resolution: {integrity: sha512-1FUFMlSVwFSFg8LbqfkzJ2LLP4lMGApUtgOpsvrde6+AxBmB4gjoNgCUH7z3xXfDAtYqcrtSELXBNE0xVL1MqQ==}
@ -728,6 +752,9 @@ packages:
'@push.rocks/smartcrypto@2.0.4': '@push.rocks/smartcrypto@2.0.4':
resolution: {integrity: sha512-1+/5bsjyataf5uUkUNnnVXGRAt+gHVk1KDzozjTqgqJxHvQk1d9fVDohL6CxUhUucTPtu5VR5xNBiV8YCDuGyw==} resolution: {integrity: sha512-1+/5bsjyataf5uUkUNnnVXGRAt+gHVk1KDzozjTqgqJxHvQk1d9fVDohL6CxUhUucTPtu5VR5xNBiV8YCDuGyw==}
'@push.rocks/smartdaemon@2.0.6':
resolution: {integrity: sha512-yFbZIT8Vb8fCdihM+kUnSluYmTzfARuZEs7Mbh+GjNJ9H+gbfyCt30BKocsPBUDkuliSAv/RlbYLILACNZlX8w==}
'@push.rocks/smartdata@5.2.12': '@push.rocks/smartdata@5.2.12':
resolution: {integrity: sha512-vp0nz1P/SJcoFhyfZoewPbFSameWnuMuCkySvnb41TcCi1PFHA//KOYImdti/qURSOYwVoTKboDrnx1/ffHp7g==} resolution: {integrity: sha512-vp0nz1P/SJcoFhyfZoewPbFSameWnuMuCkySvnb41TcCi1PFHA//KOYImdti/qURSOYwVoTKboDrnx1/ffHp7g==}
@ -755,6 +782,9 @@ packages:
'@push.rocks/smartfile@11.2.0': '@push.rocks/smartfile@11.2.0':
resolution: {integrity: sha512-0Gw6DvCQ2D/BXNN6airSC7hoSBut0p/uNWf2+rqO+D6VLhIJ/QUBvF6xm/LnpPI/zcF8YlDn/GEriInB5DUtEw==} resolution: {integrity: sha512-0Gw6DvCQ2D/BXNN6airSC7hoSBut0p/uNWf2+rqO+D6VLhIJ/QUBvF6xm/LnpPI/zcF8YlDn/GEriInB5DUtEw==}
'@push.rocks/smartfm@2.2.2':
resolution: {integrity: sha512-kLrBv/vWXJmB558LI5C79fWXLKOnno998vnp3opfB+uyznT2E6LkcpKsxdjwe1V/r+Z5GlhXPOWmGgHPCzUR6w==}
'@push.rocks/smartguard@3.1.0': '@push.rocks/smartguard@3.1.0':
resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==} resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==}
@ -857,6 +887,9 @@ packages:
'@push.rocks/smartstring@4.0.15': '@push.rocks/smartstring@4.0.15':
resolution: {integrity: sha512-NTNeOjWyg+aHtBTiQEyXamr7oTvYZ3wS1fudHo9ua7CLrykpK+i+RxFyJaLg1zB5x9xQF3NLEQecB14HPFX8Cg==} resolution: {integrity: sha512-NTNeOjWyg+aHtBTiQEyXamr7oTvYZ3wS1fudHo9ua7CLrykpK+i+RxFyJaLg1zB5x9xQF3NLEQecB14HPFX8Cg==}
'@push.rocks/smartsystem@3.0.1':
resolution: {integrity: sha512-+W9AiSJWcRAjthqDFT8rDli2+5k3bk8c9Psndy3uKN2YbaQkMZwWptZRI3WgpXMG9NhsjF8XrkyiH/xHv9AxzQ==}
'@push.rocks/smarttime@4.1.1': '@push.rocks/smarttime@4.1.1':
resolution: {integrity: sha512-Ha/3J/G+zfTl4ahpZgF6oUOZnUjpLhrBja0OQ2cloFxF9sKT8I1COaSqIfBGDtoK2Nly4UD4aTJ3JcJNOg/kgA==} resolution: {integrity: sha512-Ha/3J/G+zfTl4ahpZgF6oUOZnUjpLhrBja0OQ2cloFxF9sKT8I1COaSqIfBGDtoK2Nly4UD4aTJ3JcJNOg/kgA==}
@ -957,6 +990,10 @@ packages:
resolution: {integrity: sha512-FCRg5p5NFTyZnPsvy2sbheVGz67Zeno7VoZARrcP0O+hFtVPnQKnJ73ze11G+MKZ3dVCmYCh1Li+73R6Lx8XJA==} resolution: {integrity: sha512-FCRg5p5NFTyZnPsvy2sbheVGz67Zeno7VoZARrcP0O+hFtVPnQKnJ73ze11G+MKZ3dVCmYCh1Li+73R6Lx8XJA==}
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartmime deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartmime
'@pushrocks/smartnetwork@3.0.2':
resolution: {integrity: sha512-XKVeTzf22IRgAvY9m8naFlsjh5yYVCU4/Dqi7XnxQUVfrnrcNIJVo+9JIYjQetLbHiUOHAnthlZVP5yXppOxyw==}
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartnetwork
'@pushrocks/smartpath@4.0.3': '@pushrocks/smartpath@4.0.3':
resolution: {integrity: sha512-KWz4DWOrB0sPfk6L4i+CPOo+UK5HXNaLI7ZAaqJe1nEWoDrpyeds1dNDaqVAmSgX4riLGxVpslKH5MnABCPsPg==} resolution: {integrity: sha512-KWz4DWOrB0sPfk6L4i+CPOo+UK5HXNaLI7ZAaqJe1nEWoDrpyeds1dNDaqVAmSgX4riLGxVpslKH5MnABCPsPg==}
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartpath deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartpath
@ -2071,6 +2108,9 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
duplexify@3.7.1: duplexify@3.7.1:
resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==}
@ -2206,6 +2246,9 @@ packages:
resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=} resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
event-stream@3.3.4:
resolution: {integrity: sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=}
eventemitter3@4.0.7: eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
@ -2225,6 +2268,10 @@ packages:
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}
extend-shallow@2.0.1:
resolution: {integrity: sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=}
engines: {node: '>=0.10.0'}
extend@3.0.2: extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
@ -2349,6 +2396,9 @@ packages:
from2@2.3.0: from2@2.3.0:
resolution: {integrity: sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=} resolution: {integrity: sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=}
from@0.1.7:
resolution: {integrity: sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=}
fs-constants@1.0.0: fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
@ -2448,6 +2498,10 @@ packages:
graceful-fs@4.2.11: graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
gray-matter@4.0.3:
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
engines: {node: '>=6.0'}
gunzip-maybe@1.4.2: gunzip-maybe@1.4.2:
resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==}
hasBin: true hasBin: true
@ -2617,6 +2671,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
hasBin: true hasBin: true
is-extendable@0.1.1:
resolution: {integrity: sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=}
engines: {node: '>=0.10.0'}
is-extglob@2.1.1: is-extglob@2.1.1:
resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2930,6 +2988,9 @@ packages:
make-error@1.3.6: make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
map-stream@0.1.0:
resolution: {integrity: sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=}
markdown-table@3.0.4: markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
@ -3431,6 +3492,9 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'} engines: {node: '>=8'}
pause-stream@0.0.11:
resolution: {integrity: sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=}
pdf-lib@1.17.1: pdf-lib@1.17.1:
resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==} resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==}
@ -3458,6 +3522,10 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
pidusage@4.0.0:
resolution: {integrity: sha512-89hVJc5gq157puLYZaO3CH0qfGyDfbDG1KFCE4lCSwK0l1EuEbNa4pIJJXL93ltU5SsYia/DHJUgMY2qE4XRQg==}
engines: {node: '>=18'}
ping@0.4.4: ping@0.4.4:
resolution: {integrity: sha512-56ZMC0j7SCsMMLdOoUg12VZCfj/+ZO+yfOSjaNCRrmZZr6GLbN2X/Ui56T15dI8NhiHckaw5X2pvyfAomanwqQ==} resolution: {integrity: sha512-56ZMC0j7SCsMMLdOoUg12VZCfj/+ZO+yfOSjaNCRrmZZr6GLbN2X/Ui56T15dI8NhiHckaw5X2pvyfAomanwqQ==}
engines: {node: '>=4.0.0'} engines: {node: '>=4.0.0'}
@ -3506,6 +3574,11 @@ packages:
proxy-from-env@1.1.0: proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
ps-tree@1.2.0:
resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==}
engines: {node: '>= 0.10'}
hasBin: true
public-ip@6.0.2: public-ip@6.0.2:
resolution: {integrity: sha512-+6bkjnf0yQ4+tZV0zJv1017DiIF7y6R4yg17Mrhhkc25L7dtQtXWHgSCrz9BbLL4OeTFbPK4EALXqJUrwCIWXw==} resolution: {integrity: sha512-+6bkjnf0yQ4+tZV0zJv1017DiIF7y6R4yg17Mrhhkc25L7dtQtXWHgSCrz9BbLL4OeTFbPK4EALXqJUrwCIWXw==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
@ -3682,6 +3755,10 @@ packages:
sax@1.4.1: sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
section-matter@1.0.0:
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
engines: {node: '>=4'}
semver@6.3.1: semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true hasBin: true
@ -3796,6 +3873,9 @@ packages:
resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==}
engines: {node: '>=8'} engines: {node: '>=8'}
split@0.3.3:
resolution: {integrity: sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=}
sprintf-js@1.0.3: sprintf-js@1.0.3:
resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=}
@ -3817,6 +3897,9 @@ packages:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
stream-combiner@0.0.4:
resolution: {integrity: sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=}
stream-shift@1.0.3: stream-shift@1.0.3:
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
@ -3852,6 +3935,10 @@ packages:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
strip-bom-string@1.0.0:
resolution: {integrity: sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=}
engines: {node: '>=0.10.0'}
strip-final-newline@2.0.0: strip-final-newline@2.0.0:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -3923,6 +4010,9 @@ packages:
through2@4.0.2: through2@4.0.2:
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
through@2.3.8:
resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=}
tiny-worker@2.3.0: tiny-worker@2.3.0:
resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==} resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==}
@ -5370,6 +5460,25 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- aws-crt - aws-crt
'@push.rocks/npmextra@5.1.2':
dependencies:
'@push.rocks/qenv': 6.1.0
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smartjson': 5.0.20
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartpath': 5.0.18
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrx': 3.0.7
'@push.rocks/taskbuffer': 3.1.7
'@tsclass/tsclass': 4.4.0
'@push.rocks/projectinfo@5.0.2':
dependencies:
'@push.rocks/smartfile': 10.0.41
'@push.rocks/smartpath': 5.0.18
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartstring': 4.0.15
'@push.rocks/qenv@6.1.0': '@push.rocks/qenv@6.1.0':
dependencies: dependencies:
'@api.global/typedrequest': 3.1.10 '@api.global/typedrequest': 3.1.10
@ -5454,6 +5563,17 @@ snapshots:
'@types/node-forge': 1.3.11 '@types/node-forge': 1.3.11
node-forge: 1.3.1 node-forge: 1.3.1
'@push.rocks/smartdaemon@2.0.6':
dependencies:
'@push.rocks/lik': 6.1.0
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smartfm': 2.2.2
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartlog-destination-local': 9.0.2
'@push.rocks/smartpath': 5.0.18
'@push.rocks/smartshell': 3.2.3
'@push.rocks/smartsystem': 3.0.1
'@push.rocks/smartdata@5.2.12(@aws-sdk/credential-providers@3.758.0)(socks@2.8.4)': '@push.rocks/smartdata@5.2.12(@aws-sdk/credential-providers@3.758.0)(socks@2.8.4)':
dependencies: dependencies:
'@push.rocks/lik': 6.1.0 '@push.rocks/lik': 6.1.0
@ -5546,6 +5666,10 @@ snapshots:
glob: 11.0.1 glob: 11.0.1
js-yaml: 4.1.0 js-yaml: 4.1.0
'@push.rocks/smartfm@2.2.2':
dependencies:
gray-matter: 4.0.3
'@push.rocks/smartguard@3.1.0': '@push.rocks/smartguard@3.1.0':
dependencies: dependencies:
'@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartpromise': 4.2.3
@ -5838,6 +5962,14 @@ snapshots:
strip-indent: 4.0.0 strip-indent: 4.0.0
url: 0.11.4 url: 0.11.4
'@push.rocks/smartsystem@3.0.1':
dependencies:
'@pushrocks/lik': 6.0.2
'@pushrocks/smartenv': 5.0.5
'@pushrocks/smartnetwork': 3.0.2
'@pushrocks/smartpromise': 3.1.10
systeminformation: 5.25.11
'@push.rocks/smarttime@4.1.1': '@push.rocks/smarttime@4.1.1':
dependencies: dependencies:
'@push.rocks/lik': 6.1.0 '@push.rocks/lik': 6.1.0
@ -6046,6 +6178,16 @@ snapshots:
'@types/mime-types': 2.1.4 '@types/mime-types': 2.1.4
mime-types: 2.1.35 mime-types: 2.1.35
'@pushrocks/smartnetwork@3.0.2':
dependencies:
'@pushrocks/smartping': 1.0.8
'@pushrocks/smartpromise': 3.1.10
'@pushrocks/smartstring': 4.0.7
'@types/default-gateway': 3.0.1
isopen: 1.3.0
public-ip: 6.0.2
systeminformation: 5.25.11
'@pushrocks/smartpath@4.0.3': {} '@pushrocks/smartpath@4.0.3': {}
'@pushrocks/smartpath@5.0.5': {} '@pushrocks/smartpath@5.0.5': {}
@ -7345,6 +7487,8 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
gopd: 1.2.0 gopd: 1.2.0
duplexer@0.1.2: {}
duplexify@3.7.1: duplexify@3.7.1:
dependencies: dependencies:
end-of-stream: 1.4.4 end-of-stream: 1.4.4
@ -7516,6 +7660,16 @@ snapshots:
etag@1.8.1: {} etag@1.8.1: {}
event-stream@3.3.4:
dependencies:
duplexer: 0.1.2
from: 0.1.7
map-stream: 0.1.0
pause-stream: 0.0.11
split: 0.3.3
stream-combiner: 0.0.4
through: 2.3.8
eventemitter3@4.0.7: {} eventemitter3@4.0.7: {}
execa@5.1.1: execa@5.1.1:
@ -7578,6 +7732,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
extend-shallow@2.0.1:
dependencies:
is-extendable: 0.1.1
extend@3.0.2: {} extend@3.0.2: {}
extract-zip@2.0.1: extract-zip@2.0.1:
@ -7712,6 +7870,8 @@ snapshots:
inherits: 2.0.4 inherits: 2.0.4
readable-stream: 2.3.8 readable-stream: 2.3.8
from@0.1.7: {}
fs-constants@1.0.0: {} fs-constants@1.0.0: {}
fs-extra@10.1.0: fs-extra@10.1.0:
@ -7848,6 +8008,13 @@ snapshots:
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
gray-matter@4.0.3:
dependencies:
js-yaml: 3.14.1
kind-of: 6.0.3
section-matter: 1.0.0
strip-bom-string: 1.0.0
gunzip-maybe@1.4.2: gunzip-maybe@1.4.2:
dependencies: dependencies:
browserify-zlib: 0.1.4 browserify-zlib: 0.1.4
@ -8039,6 +8206,8 @@ snapshots:
is-docker@2.2.1: {} is-docker@2.2.1: {}
is-extendable@0.1.1: {}
is-extglob@2.1.1: {} is-extglob@2.1.1: {}
is-fullwidth-code-point@3.0.0: {} is-fullwidth-code-point@3.0.0: {}
@ -8377,6 +8546,8 @@ snapshots:
make-error@1.3.6: {} make-error@1.3.6: {}
map-stream@0.1.0: {}
markdown-table@3.0.4: {} markdown-table@3.0.4: {}
matcher@3.0.0: matcher@3.0.0:
@ -9020,6 +9191,10 @@ snapshots:
path-type@4.0.0: {} path-type@4.0.0: {}
pause-stream@0.0.11:
dependencies:
through: 2.3.8
pdf-lib@1.17.1: pdf-lib@1.17.1:
dependencies: dependencies:
'@pdf-lib/standard-fonts': 1.0.0 '@pdf-lib/standard-fonts': 1.0.0
@ -9043,6 +9218,10 @@ snapshots:
picomatch@2.3.1: {} picomatch@2.3.1: {}
pidusage@4.0.0:
dependencies:
safe-buffer: 5.2.1
ping@0.4.4: {} ping@0.4.4: {}
pkg-dir@4.2.0: pkg-dir@4.2.0:
@ -9095,6 +9274,10 @@ snapshots:
proxy-from-env@1.1.0: {} proxy-from-env@1.1.0: {}
ps-tree@1.2.0:
dependencies:
event-stream: 3.3.4
public-ip@6.0.2: public-ip@6.0.2:
dependencies: dependencies:
aggregate-error: 4.0.1 aggregate-error: 4.0.1
@ -9335,6 +9518,11 @@ snapshots:
sax@1.4.1: {} sax@1.4.1: {}
section-matter@1.0.0:
dependencies:
extend-shallow: 2.0.1
kind-of: 6.0.3
semver@6.3.1: {} semver@6.3.1: {}
semver@7.7.1: {} semver@7.7.1: {}
@ -9504,6 +9692,10 @@ snapshots:
signal-exit: 3.0.7 signal-exit: 3.0.7
which: 2.0.2 which: 2.0.2
split@0.3.3:
dependencies:
through: 2.3.8
sprintf-js@1.0.3: {} sprintf-js@1.0.3: {}
sprintf-js@1.1.3: {} sprintf-js@1.1.3: {}
@ -9518,6 +9710,10 @@ snapshots:
statuses@2.0.1: {} statuses@2.0.1: {}
stream-combiner@0.0.4:
dependencies:
duplexer: 0.1.2
stream-shift@1.0.3: {} stream-shift@1.0.3: {}
streamsearch@0.1.2: {} streamsearch@0.1.2: {}
@ -9562,6 +9758,8 @@ snapshots:
dependencies: dependencies:
ansi-regex: 6.1.0 ansi-regex: 6.1.0
strip-bom-string@1.0.0: {}
strip-final-newline@2.0.0: {} strip-final-newline@2.0.0: {}
strip-indent@4.0.0: strip-indent@4.0.0:
@ -9652,6 +9850,8 @@ snapshots:
dependencies: dependencies:
readable-stream: 3.6.2 readable-stream: 3.6.2
through@2.3.8: {}
tiny-worker@2.3.0: tiny-worker@2.3.0:
dependencies: dependencies:
esm: 3.2.25 esm: 3.2.25

View File

@ -6,3 +6,22 @@ tap.test('first test', async () => {
}); });
tap.start(); tap.start();
// Example usage:
const config: tspm.IMonitorConfig = {
name: 'Project XYZ Monitor', // Identifier for the instance
projectDir: '/path/to/your/project', // Set the project directory here
command: 'npm run xyz', // Full command string (no need for args)
memoryLimitBytes: 500 * 1024 * 1024, // 500 MB memory limit
monitorIntervalMs: 5000, // Check memory usage every 5 seconds
};
const monitor = new tspm.ProcessMonitor(config);
monitor.start();
// Ensure that on process exit (e.g. Ctrl+C) we clean up the child process and prevent respawns.
process.on('SIGINT', () => {
console.log('Received SIGINT, stopping monitor...');
monitor.stop();
process.exit();
});

View File

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

20
ts/classes.config.ts Normal file
View File

@ -0,0 +1,20 @@
import * as plugins from './plugins.js';
export class TspmConfig {
public npmextraInstance = new plugins.npmextra.KeyValueStore({
identityArg: '@git.zone__tspm',
typeArg: 'userHomeDir',
})
public async readKey(keyArg: string): Promise<string> {
return await this.npmextraInstance.readKey(keyArg);
}
public async writeKey(keyArg: string, value: string): Promise<void> {
return await this.npmextraInstance.writeKey(keyArg, value);
}
public async deleteKey(keyArg: string): Promise<void> {
return await this.npmextraInstance.deleteKey(keyArg);
}
}

View File

@ -1,21 +1,23 @@
import { spawn, ChildProcess } from 'child_process'; import * as plugins from './plugins.js';
import psTree from 'ps-tree'; import { ProcessWrapper, type IProcessLog } from './classes.processwrapper.js';
import pidusage from 'pidusage';
interface IMonitorConfig { export interface IMonitorConfig {
name?: string; // Optional name to identify the instance name?: string; // Optional name to identify the instance
projectDir: string; // Directory where the command will run projectDir: string; // Directory where the command will run
command: string; // Full command to run (e.g., "npm run xyz") command: string; // Full command to run (e.g., "npm run xyz")
args?: string[]; // Optional: arguments for the command args?: string[]; // Optional: arguments for the command
memoryLimitBytes: number; // Maximum allowed memory (in bytes) for the process group memoryLimitBytes: number; // Maximum allowed memory (in bytes) for the process group
monitorIntervalMs?: number; // Interval (in ms) at which memory is checked (default: 5000) monitorIntervalMs?: number; // Interval (in ms) at which memory is checked (default: 5000)
env?: NodeJS.ProcessEnv; // Optional: custom environment variables
logBufferSize?: number; // Optional: number of log lines to keep (default: 100)
} }
class ProcessMonitor { export class ProcessMonitor {
private child: ChildProcess | null = null; private processWrapper: ProcessWrapper | null = null;
private config: IMonitorConfig; private config: IMonitorConfig;
private intervalId: NodeJS.Timeout | null = null; private intervalId: NodeJS.Timeout | null = null;
private stopped: boolean = true; // Initially stopped until start() is called private stopped: boolean = true; // Initially stopped until start() is called
private restartCount: number = 0;
constructor(config: IMonitorConfig) { constructor(config: IMonitorConfig) {
this.config = config; this.config = config;
@ -25,59 +27,64 @@ class ProcessMonitor {
// Reset the stopped flag so that new processes can spawn. // Reset the stopped flag so that new processes can spawn.
this.stopped = false; this.stopped = false;
this.log(`Starting process monitor.`); this.log(`Starting process monitor.`);
this.spawnChild(); this.spawnProcess();
// Set the monitoring interval. // Set the monitoring interval.
const interval = this.config.monitorIntervalMs || 5000; const interval = this.config.monitorIntervalMs || 5000;
this.intervalId = setInterval(() => { this.intervalId = setInterval(() => {
if (this.child && this.child.pid) { if (this.processWrapper && this.processWrapper.getPid()) {
this.monitorProcessGroup(this.child.pid, this.config.memoryLimitBytes); this.monitorProcessGroup(this.processWrapper.getPid()!, this.config.memoryLimitBytes);
} }
}, interval); }, interval);
} }
private spawnChild(): void { private spawnProcess(): void {
// Don't spawn if the monitor has been stopped. // Don't spawn if the monitor has been stopped.
if (this.stopped) return; if (this.stopped) return;
if (this.config.args && this.config.args.length > 0) { // Create a new process wrapper
this.log( this.processWrapper = new ProcessWrapper({
`Spawning command "${this.config.command}" with args [${this.config.args.join( name: this.config.name || 'unnamed-process',
', ' command: this.config.command,
)}] in directory: ${this.config.projectDir}` args: this.config.args,
);
this.child = spawn(this.config.command, this.config.args, {
cwd: this.config.projectDir, cwd: this.config.projectDir,
detached: true, env: this.config.env,
stdio: 'inherit', logBuffer: this.config.logBufferSize,
});
} else {
this.log(
`Spawning command "${this.config.command}" in directory: ${this.config.projectDir}`
);
// Use shell mode to allow a full command string.
this.child = spawn(this.config.command, {
cwd: this.config.projectDir,
detached: true,
stdio: 'inherit',
shell: true,
}); });
// Set up event handlers
this.processWrapper.on('log', (log) => {
// Here we could add handlers to send logs somewhere
// For now, we just log system messages to the console
if (log.type === 'system') {
this.log(log.message);
} }
});
this.log(`Spawned process with PID ${this.child.pid}`); this.processWrapper.on('exit', (code, signal) => {
this.log(`Process exited with code ${code}, signal ${signal}.`);
// When the child process exits, restart it if the monitor isn't stopped.
this.child.on('exit', (code, signal) => {
this.log(`Child process exited with code ${code}, signal ${signal}.`);
if (!this.stopped) { if (!this.stopped) {
this.log('Restarting process...'); this.log('Restarting process...');
this.spawnChild(); this.restartCount++;
this.spawnProcess();
} }
}); });
this.processWrapper.on('error', (error) => {
this.log(`Process error: ${error.message}`);
if (!this.stopped) {
this.log('Restarting process due to error...');
this.restartCount++;
this.spawnProcess();
}
});
// Start the process
this.processWrapper.start();
} }
/** /**
* Monitor the process groups 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.
*/ */
private async monitorProcessGroup(pid: number, memoryLimit: number): Promise<void> { private async monitorProcessGroup(pid: number, memoryLimit: number): Promise<void> {
@ -94,8 +101,10 @@ class ProcessMonitor {
memoryUsage memoryUsage
)} exceeds limit of ${this.humanReadableBytes(memoryLimit)}. Restarting process.` )} exceeds limit of ${this.humanReadableBytes(memoryLimit)}. Restarting process.`
); );
// Kill the entire process group by sending a signal to -PID. // Stop the process wrapper, which will trigger the exit handler and restart
process.kill(-pid, 'SIGKILL'); if (this.processWrapper) {
this.processWrapper.stop();
}
} }
} catch (error) { } catch (error) {
this.log('Error monitoring process group: ' + error); this.log('Error monitoring process group: ' + error);
@ -107,11 +116,11 @@ class ProcessMonitor {
*/ */
private getProcessGroupMemory(pid: number): Promise<number> { private getProcessGroupMemory(pid: number): Promise<number> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
psTree(pid, (err, children) => { plugins.psTree(pid, (err, children) => {
if (err) return reject(err); if (err) return reject(err);
// Include the main process and its children. // Include the main process and its children.
const pids: number[] = [pid, ...children.map(child => Number(child.PID))]; const pids: number[] = [pid, ...children.map(child => Number(child.PID))];
pidusage(pids, (err, stats) => { plugins.pidusage(pids, (err, stats) => {
if (err) return reject(err); if (err) return reject(err);
let totalMemory = 0; let totalMemory = 0;
for (const key in stats) { for (const key in stats) {
@ -144,11 +153,49 @@ class ProcessMonitor {
if (this.intervalId) { if (this.intervalId) {
clearInterval(this.intervalId); clearInterval(this.intervalId);
} }
if (this.child && this.child.pid) { if (this.processWrapper) {
process.kill(-this.child.pid, 'SIGKILL'); this.processWrapper.stop();
} }
} }
/**
* Get the current logs from the process
*/
public getLogs(limit?: number): IProcessLog[] {
if (!this.processWrapper) {
return [];
}
return this.processWrapper.getLogs(limit);
}
/**
* Get the number of times the process has been restarted
*/
public getRestartCount(): number {
return this.restartCount;
}
/**
* Get the process ID if running
*/
public getPid(): number | null {
return this.processWrapper?.getPid() || null;
}
/**
* Get process uptime in milliseconds
*/
public getUptime(): number {
return this.processWrapper?.getUptime() || 0;
}
/**
* Check if the process is currently running
*/
public isRunning(): boolean {
return this.processWrapper?.isRunning() || false;
}
/** /**
* Helper method for logging messages with the instance name. * Helper method for logging messages with the instance name.
*/ */
@ -157,22 +204,3 @@ class ProcessMonitor {
console.log(prefix + message); console.log(prefix + message);
} }
} }
// Example usage:
const config: IMonitorConfig = {
name: 'Project XYZ Monitor', // Identifier for the instance
projectDir: '/path/to/your/project', // Set the project directory here
command: 'npm run xyz', // Full command string (no need for args)
memoryLimitBytes: 500 * 1024 * 1024, // 500 MB memory limit
monitorIntervalMs: 5000, // Check memory usage every 5 seconds
};
const monitor = new ProcessMonitor(config);
monitor.start();
// Ensure that on process exit (e.g. Ctrl+C) we clean up the child process and prevent respawns.
process.on('SIGINT', () => {
monitor.log('Received SIGINT, stopping monitor...');
monitor.stop();
process.exit();
});

View File

@ -0,0 +1,207 @@
import * as plugins from './plugins.js';
import { EventEmitter } from 'events';
export interface IProcessWrapperOptions {
command: string;
args?: string[];
cwd: string;
env?: NodeJS.ProcessEnv;
name: string;
logBuffer?: number; // Number of log lines to keep in memory (default: 100)
}
export interface IProcessLog {
timestamp: Date;
type: 'stdout' | 'stderr' | 'system';
message: string;
}
export class ProcessWrapper extends EventEmitter {
private process: plugins.childProcess.ChildProcess | null = null;
private options: IProcessWrapperOptions;
private logs: IProcessLog[] = [];
private logBufferSize: number;
private startTime: Date | null = null;
constructor(options: IProcessWrapperOptions) {
super();
this.options = options;
this.logBufferSize = options.logBuffer || 100;
}
/**
* Start the wrapped process
*/
public start(): void {
this.addSystemLog('Starting process...');
try {
if (this.options.args && this.options.args.length > 0) {
this.process = plugins.childProcess.spawn(this.options.command, this.options.args, {
cwd: this.options.cwd,
env: this.options.env || process.env,
stdio: ['ignore', 'pipe', 'pipe'], // We need to pipe stdout and stderr
});
} else {
// Use shell mode to allow a full command string
this.process = plugins.childProcess.spawn(this.options.command, {
cwd: this.options.cwd,
env: this.options.env || process.env,
stdio: ['ignore', 'pipe', 'pipe'], // We need to pipe stdout and stderr
shell: true,
});
}
this.startTime = new Date();
// Handle process exit
this.process.on('exit', (code, signal) => {
this.addSystemLog(`Process exited with code ${code}, signal ${signal}`);
this.emit('exit', code, signal);
});
// Handle errors
this.process.on('error', (error) => {
this.addSystemLog(`Process error: ${error.message}`);
this.emit('error', error);
});
// Capture stdout
if (this.process.stdout) {
this.process.stdout.on('data', (data) => {
const lines = data.toString().split('\n');
for (const line of lines) {
if (line.trim()) {
this.addLog('stdout', line);
}
}
});
}
// Capture stderr
if (this.process.stderr) {
this.process.stderr.on('data', (data) => {
const lines = data.toString().split('\n');
for (const line of lines) {
if (line.trim()) {
this.addLog('stderr', line);
}
}
});
}
this.addSystemLog(`Process started with PID ${this.process.pid}`);
this.emit('start', this.process.pid);
} catch (error) {
this.addSystemLog(`Failed to start process: ${error.message}`);
this.emit('error', error);
throw error;
}
}
/**
* Stop the wrapped process
*/
public stop(): void {
if (!this.process) {
this.addSystemLog('No process running');
return;
}
this.addSystemLog('Stopping process...');
// First try SIGTERM for graceful shutdown
if (this.process.pid) {
try {
process.kill(this.process.pid, 'SIGTERM');
// Give it 5 seconds to shut down gracefully
setTimeout(() => {
if (this.process && this.process.pid) {
this.addSystemLog('Process did not exit gracefully, force killing...');
try {
process.kill(this.process.pid, 'SIGKILL');
} catch (error) {
// Process might have exited between checks
}
}
}, 5000);
} catch (error) {
this.addSystemLog(`Error stopping process: ${error.message}`);
}
}
}
/**
* Get the process ID if running
*/
public getPid(): number | null {
return this.process?.pid || null;
}
/**
* Get the current logs
*/
public getLogs(limit: number = this.logBufferSize): IProcessLog[] {
// Return the most recent logs up to the limit
return this.logs.slice(-limit);
}
/**
* Get uptime in milliseconds
*/
public getUptime(): number {
if (!this.startTime) return 0;
return Date.now() - this.startTime.getTime();
}
/**
* Check if the process is currently running
*/
public isRunning(): boolean {
return this.process !== null && typeof this.process.exitCode !== 'number';
}
/**
* Add a log entry from stdout or stderr
*/
private addLog(type: 'stdout' | 'stderr', message: string): void {
const log: IProcessLog = {
timestamp: new Date(),
type,
message,
};
this.logs.push(log);
// Trim logs if they exceed buffer size
if (this.logs.length > this.logBufferSize) {
this.logs = this.logs.slice(-this.logBufferSize);
}
// Emit log event for potential handlers
this.emit('log', log);
}
/**
* Add a system log entry (not from the process itself)
*/
private addSystemLog(message: string): void {
const log: IProcessLog = {
timestamp: new Date(),
type: 'system',
message,
};
this.logs.push(log);
// Trim logs if they exceed buffer size
if (this.logs.length > this.logBufferSize) {
this.logs = this.logs.slice(-this.logBufferSize);
}
// Emit log event for potential handlers
this.emit('log', log);
}
}

View File

@ -1,6 +1,259 @@
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
import * as paths from './paths.js'; import * as paths from './paths.js';
import { ProcessMonitor, type IMonitorConfig } from './classes.processmonitor.js';
import { TspmConfig } from './classes.config.js';
export interface IProcessConfig extends IMonitorConfig {
id: string; // Unique identifier for the process
autorestart: boolean; // Whether to restart the process automatically on crash
watch?: boolean; // Whether to watch for file changes and restart
watchPaths?: string[]; // Paths to watch for changes
}
export interface IProcessInfo {
id: string;
pid?: number;
status: 'online' | 'stopped' | 'errored';
memory: number;
cpu?: number;
uptime?: number;
restarts: number;
}
export interface IProcessLog {
timestamp: Date;
type: 'stdout' | 'stderr' | 'system';
message: string;
}
export class Tspm { export class Tspm {
private processes: Map<string, ProcessMonitor> = new Map();
private processConfigs: Map<string, IProcessConfig> = new Map();
private processInfo: Map<string, IProcessInfo> = new Map();
private config: TspmConfig;
private configStorageKey = 'processes';
constructor() {
this.config = new TspmConfig();
this.loadProcessConfigs();
}
/**
* Start a new process with the given configuration
*/
public async start(config: IProcessConfig): Promise<void> {
// Check if process with this id already exists
if (this.processes.has(config.id)) {
throw new Error(`Process with id '${config.id}' already exists`);
}
// Create and store process config
this.processConfigs.set(config.id, config);
// Initialize process info
this.processInfo.set(config.id, {
id: config.id,
status: 'stopped',
memory: 0,
restarts: 0
});
// Create and start process monitor
const monitor = new ProcessMonitor({
name: config.name || config.id,
projectDir: config.projectDir,
command: config.command,
args: config.args,
memoryLimitBytes: config.memoryLimitBytes,
monitorIntervalMs: config.monitorIntervalMs
});
this.processes.set(config.id, monitor);
monitor.start();
// Update process info
this.updateProcessInfo(config.id, { status: 'online' });
// Save updated configs
await this.saveProcessConfigs();
}
/**
* Stop a process by id
*/
public async stop(id: string): Promise<void> {
const monitor = this.processes.get(id);
if (!monitor) {
throw new Error(`Process with id '${id}' not found`);
}
monitor.stop();
this.updateProcessInfo(id, { status: 'stopped' });
// Don't remove from the maps, just mark as stopped
// This allows it to be restarted later
}
/**
* Restart a process by id
*/
public async restart(id: string): Promise<void> {
const monitor = this.processes.get(id);
const config = this.processConfigs.get(id);
if (!monitor || !config) {
throw new Error(`Process with id '${id}' not found`);
}
// Stop and then start the process
monitor.stop();
// Create a new monitor instance
const newMonitor = new ProcessMonitor({
name: config.name || config.id,
projectDir: config.projectDir,
command: config.command,
args: config.args,
memoryLimitBytes: config.memoryLimitBytes,
monitorIntervalMs: config.monitorIntervalMs
});
this.processes.set(id, newMonitor);
newMonitor.start();
// Update restart count
const info = this.processInfo.get(id);
if (info) {
this.updateProcessInfo(id, {
status: 'online',
restarts: info.restarts + 1
});
}
}
/**
* Delete a process by id
*/
public async delete(id: string): Promise<void> {
// Stop the process if it's running
try {
await this.stop(id);
} catch (error) {
// Ignore errors if the process is not running
}
// Remove from all maps
this.processes.delete(id);
this.processConfigs.delete(id);
this.processInfo.delete(id);
// Save updated configs
await this.saveProcessConfigs();
}
/**
* Get a list of all process infos
*/
public list(): IProcessInfo[] {
return Array.from(this.processInfo.values());
}
/**
* Get detailed info for a specific process
*/
public describe(id: string): { config: IProcessConfig; info: IProcessInfo } | null {
const config = this.processConfigs.get(id);
const info = this.processInfo.get(id);
if (!config || !info) {
return null;
}
return { config, info };
}
/**
* Get process logs
*/
public getLogs(id: string, limit?: number): IProcessLog[] {
const monitor = this.processes.get(id);
if (!monitor) {
return [];
}
return monitor.getLogs(limit);
}
/**
* Start all saved processes
*/
public async startAll(): Promise<void> {
for (const [id, config] of this.processConfigs.entries()) {
if (!this.processes.has(id)) {
await this.start(config);
}
}
}
/**
* Stop all running processes
*/
public async stopAll(): Promise<void> {
for (const id of this.processes.keys()) {
await this.stop(id);
}
}
/**
* Restart all processes
*/
public async restartAll(): Promise<void> {
for (const id of this.processes.keys()) {
await this.restart(id);
}
}
/**
* Update the info for a process
*/
private updateProcessInfo(id: string, update: Partial<IProcessInfo>): void {
const info = this.processInfo.get(id);
if (info) {
this.processInfo.set(id, { ...info, ...update });
}
}
/**
* Save all process configurations to config storage
*/
private async saveProcessConfigs(): Promise<void> {
const configs = Array.from(this.processConfigs.values());
await this.config.writeKey(this.configStorageKey, JSON.stringify(configs));
}
/**
* Load process configurations from config storage
*/
private async loadProcessConfigs(): Promise<void> {
try {
const configsJson = await this.config.readKey(this.configStorageKey);
if (configsJson) {
const configs = JSON.parse(configsJson) as IProcessConfig[];
for (const config of configs) {
this.processConfigs.set(config.id, config);
// Initialize process info
this.processInfo.set(config.id, {
id: config.id,
status: 'stopped',
memory: 0,
restarts: 0
});
}
}
} catch (error) {
// If no configs found or error reading, just continue with empty configs
console.log('No saved process configurations found');
}
}
} }

300
ts/cli.ts Normal file
View File

@ -0,0 +1,300 @@
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import { Tspm, IProcessConfig } from './classes.tspm.js';
export const run = async () => {
const tspmProjectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
const tspm = new Tspm();
const smartcliInstance = new plugins.smartcli.Smartcli();
smartcliInstance.addVersion(tspmProjectinfo.npm.version);
// Default command - show help and list processes
smartcliInstance.standardCommand().subscribe({
next: async (argvArg) => {
console.log(`TSPM - TypeScript Process Manager v${tspmProjectinfo.npm.version}`);
console.log('Usage: tspm [command] [options]');
console.log('\nCommands:');
console.log(' start <script> Start a process');
console.log(' startAsDaemon <script> Start a process in daemon mode');
console.log(' list List all processes');
console.log(' stop <id> Stop a process');
console.log(' restart <id> Restart a process');
console.log(' delete <id> Delete a process');
console.log(' describe <id> Show details for a process');
console.log('\nUse tspm [command] --help for more information about a command.');
// Show current process list
console.log('\nProcess List:');
const processes = tspm.list();
if (processes.length === 0) {
console.log(' No processes running. Use "tspm start" to start a process.');
} else {
console.log('┌─────────┬─────────────┬───────────┬───────────┬──────────┐');
console.log('│ ID │ Name │ Status │ Memory │ Restarts │');
console.log('├─────────┼─────────────┼───────────┼───────────┼──────────┤');
for (const proc of processes) {
console.log(`${pad(proc.id, 8)}${pad(proc.id, 12)}${pad(proc.status, 10)}${pad(formatMemory(proc.memory), 10)}${pad(String(proc.restarts), 9)}`);
}
console.log('└─────────┴─────────────┴───────────┴───────────┴──────────┘');
}
},
});
// Start command - start a new process
smartcliInstance.addCommand('start').subscribe({
next: async (argvArg) => {
const script = argvArg._.length > 1 ? String(argvArg._[1]) : '';
if (!script) {
console.error('Error: Missing script argument. Usage: tspm start <script>');
return;
}
// Parse additional options
const name = argvArg.name || script;
const cwd = argvArg.cwd || process.cwd();
const memLimit = parseMemoryString(argvArg.memory || '500MB');
try {
const processConfig: IProcessConfig = {
id: argvArg.id || name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase(),
name: name,
projectDir: cwd,
command: script,
args: argvArg.args ? String(argvArg.args).split(' ') : undefined,
memoryLimitBytes: memLimit,
monitorIntervalMs: Number(argvArg.interval) || 5000,
autorestart: argvArg.autorestart !== 'false',
watch: Boolean(argvArg.watch)
};
await tspm.start(processConfig);
console.log(`Process ${processConfig.id} started successfully.`);
} catch (error) {
console.error(`Error starting process: ${error.message}`);
}
},
});
// Start as daemon command
smartcliInstance.addCommand('startAsDaemon').subscribe({
next: async (argvArg) => {
const script = argvArg._.length > 1 ? String(argvArg._[1]) : '';
if (!script) {
console.error('Error: Missing script argument. Usage: tspm startAsDaemon <script>');
return;
}
// For daemon mode, we'll detach from the console
const daemonProcess = plugins.childProcess.spawn(
process.execPath,
[
...process.execArgv,
process.argv[1], // The tspm script path
'start',
script,
...process.argv.slice(3) // Pass other arguments
],
{
detached: true,
stdio: 'ignore',
cwd: process.cwd()
}
);
// Unref to allow parent to exit
daemonProcess.unref();
console.log(`Started process ${script} as daemon.`);
}
});
// Stop command
smartcliInstance.addCommand('stop').subscribe({
next: async (argvArg) => {
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
if (!id) {
console.error('Error: Missing process ID. Usage: tspm stop <id>');
return;
}
try {
await tspm.stop(id);
console.log(`Process ${id} stopped.`);
} catch (error) {
console.error(`Error stopping process: ${error.message}`);
}
}
});
// Restart command
smartcliInstance.addCommand('restart').subscribe({
next: async (argvArg) => {
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
if (!id) {
console.error('Error: Missing process ID. Usage: tspm restart <id>');
return;
}
try {
await tspm.restart(id);
console.log(`Process ${id} restarted.`);
} catch (error) {
console.error(`Error restarting process: ${error.message}`);
}
}
});
// Delete command
smartcliInstance.addCommand('delete').subscribe({
next: async (argvArg) => {
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
if (!id) {
console.error('Error: Missing process ID. Usage: tspm delete <id>');
return;
}
try {
await tspm.delete(id);
console.log(`Process ${id} deleted.`);
} catch (error) {
console.error(`Error deleting process: ${error.message}`);
}
}
});
// List command
smartcliInstance.addCommand('list').subscribe({
next: async (argvArg) => {
const processes = tspm.list();
if (processes.length === 0) {
console.log('No processes running.');
return;
}
console.log('┌─────────┬─────────────┬───────────┬───────────┬──────────┐');
console.log('│ ID │ Name │ Status │ Memory │ Restarts │');
console.log('├─────────┼─────────────┼───────────┼───────────┼──────────┤');
for (const proc of processes) {
console.log(`${pad(proc.id, 8)}${pad(proc.id, 12)}${pad(proc.status, 10)}${pad(formatMemory(proc.memory), 10)}${pad(String(proc.restarts), 9)}`);
}
console.log('└─────────┴─────────────┴───────────┴───────────┴──────────┘');
}
});
// Describe command
smartcliInstance.addCommand('describe').subscribe({
next: async (argvArg) => {
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
if (!id) {
console.error('Error: Missing process ID. Usage: tspm describe <id>');
return;
}
const details = tspm.describe(id);
if (!details) {
console.error(`Process with ID '${id}' not found.`);
return;
}
console.log(`Details for process '${id}':`);
console.log(` Status: ${details.info.status}`);
console.log(` Memory: ${formatMemory(details.info.memory)}`);
console.log(` Restarts: ${details.info.restarts}`);
console.log(` Command: ${details.config.command}`);
console.log(` Directory: ${details.config.projectDir}`);
console.log(` Memory limit: ${formatMemory(details.config.memoryLimitBytes)}`);
if (details.config.args && details.config.args.length > 0) {
console.log(` Arguments: ${details.config.args.join(' ')}`);
}
}
});
// Logs command
smartcliInstance.addCommand('logs').subscribe({
next: async (argvArg) => {
const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
if (!id) {
console.error('Error: Missing process ID. Usage: tspm logs <id>');
return;
}
const lines = Number(argvArg.lines || argvArg.n) || 20;
const logs = tspm.getLogs(id, lines);
if (logs.length === 0) {
console.log(`No logs found for process '${id}'.`);
return;
}
// Display logs with colors for different log types
for (const log of logs) {
const timestamp = log.timestamp.toISOString();
const prefix = `[${timestamp}] `;
switch (log.type) {
case 'stdout':
console.log(`${prefix}${log.message}`);
break;
case 'stderr':
console.error(`${prefix}${log.message}`);
break;
case 'system':
console.log(`${prefix}[SYSTEM] ${log.message}`);
break;
}
}
}
});
// Start parsing
smartcliInstance.startParse();
};
// Helper function to format memory usage
function formatMemory(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Helper function to parse memory strings like "500MB"
function parseMemoryString(memString: string): number {
const units = {
'B': 1,
'KB': 1024,
'MB': 1024 * 1024,
'GB': 1024 * 1024 * 1024,
'TB': 1024 * 1024 * 1024 * 1024
};
const match = memString.match(/^(\d+(?:\.\d+)?)\s*([KMGT]?B)$/i);
if (!match) {
throw new Error(`Invalid memory format: ${memString}. Use format like 500MB`);
}
const value = parseFloat(match[1]);
const unit = match[2].toUpperCase();
return value * units[unit];
}
// Helper function to pad strings for table display
function pad(str: string, length: number): string {
return str.padEnd(length);
}

View File

@ -1,3 +1,11 @@
import * as plugins from './plugins.js'; export * from './classes.tspm.js';
export * from './classes.processmonitor.js';
export let demoExport = 'Hi there! :) This is an exported string'; import * as cli from './cli.js';
/**
* called to run as cli
*/
export const runCli = async () => {
await cli.run();
}

4
ts/paths.ts Normal file
View File

@ -0,0 +1,4 @@
import * as plugins from './plugins.js';
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '..');
export const cwd = process.cwd();

View File

@ -1,13 +1,32 @@
// native scope // native scope
import * as childProcess from 'child_process';
import * as path from 'node:path'; import * as path from 'node:path';
export { export {
childProcess,
path, path,
} }
// @push.rocks scope // @push.rocks scope
import * as npmextra from '@push.rocks/npmextra';
import * as projectinfo from '@push.rocks/projectinfo';
import * as smartcli from '@push.rocks/smartcli';
import * as smartdaemon from '@push.rocks/smartdaemon';
import * as smartpath from '@push.rocks/smartpath'; import * as smartpath from '@push.rocks/smartpath';
export { export {
npmextra,
projectinfo,
smartcli,
smartdaemon,
smartpath, smartpath,
} }
// third-party scope
import psTree from 'ps-tree';
import pidusage from 'pidusage';
export {
psTree,
pidusage,
}