Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
0232741b89 | |||
9c1327c9be | |||
74bfcb273a | |||
cefbce1ba0 | |||
51bb3a8967 | |||
c4a082031e | |||
761f9ca1b6 | |||
ad2c180cfe |
30
changelog.md
30
changelog.md
@ -1,5 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
## 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)
|
||||
Update dependencies and pnpm configuration
|
||||
|
||||
|
15
package.json
15
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@git.zone/tspm",
|
||||
"version": "1.1.1",
|
||||
"version": "1.4.0",
|
||||
"private": false,
|
||||
"description": "a no fuzz process manager",
|
||||
"main": "dist_ts/index.js",
|
||||
@ -11,7 +11,11 @@
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --web)",
|
||||
"build": "(tsbuild --web --allowimplicitany)",
|
||||
"buildDocs": "(tsdoc)"
|
||||
"buildDocs": "(tsdoc)",
|
||||
"start": "(tsrun ./cli.ts -v)"
|
||||
},
|
||||
"bin": {
|
||||
"tspm": "./cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.1.25",
|
||||
@ -22,7 +26,12 @@
|
||||
"@types/node": "^22.13.8"
|
||||
},
|
||||
"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/smartpath": "^5.0.18",
|
||||
"pidusage": "^4.0.0",
|
||||
"ps-tree": "^1.2.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
111
pnpm-lock.yaml
generated
111
pnpm-lock.yaml
generated
@ -8,9 +8,24 @@ importers:
|
||||
|
||||
.:
|
||||
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/smartpath':
|
||||
specifier: ^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:
|
||||
'@git.zone/tsbuild':
|
||||
specifier: ^2.1.25
|
||||
@ -701,6 +716,12 @@ packages:
|
||||
'@push.rocks/mongodump@1.0.8':
|
||||
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':
|
||||
resolution: {integrity: sha512-1FUFMlSVwFSFg8LbqfkzJ2LLP4lMGApUtgOpsvrde6+AxBmB4gjoNgCUH7z3xXfDAtYqcrtSELXBNE0xVL1MqQ==}
|
||||
|
||||
@ -2071,6 +2092,9 @@ packages:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
|
||||
duplexify@3.7.1:
|
||||
resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==}
|
||||
|
||||
@ -2206,6 +2230,9 @@ packages:
|
||||
resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
event-stream@3.3.4:
|
||||
resolution: {integrity: sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=}
|
||||
|
||||
eventemitter3@4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
|
||||
@ -2349,6 +2376,9 @@ packages:
|
||||
from2@2.3.0:
|
||||
resolution: {integrity: sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=}
|
||||
|
||||
from@0.1.7:
|
||||
resolution: {integrity: sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=}
|
||||
|
||||
fs-constants@1.0.0:
|
||||
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
|
||||
|
||||
@ -2930,6 +2960,9 @@ packages:
|
||||
make-error@1.3.6:
|
||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||
|
||||
map-stream@0.1.0:
|
||||
resolution: {integrity: sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=}
|
||||
|
||||
markdown-table@3.0.4:
|
||||
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
|
||||
|
||||
@ -3431,6 +3464,9 @@ packages:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
pause-stream@0.0.11:
|
||||
resolution: {integrity: sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=}
|
||||
|
||||
pdf-lib@1.17.1:
|
||||
resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==}
|
||||
|
||||
@ -3458,6 +3494,10 @@ packages:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
pidusage@4.0.0:
|
||||
resolution: {integrity: sha512-89hVJc5gq157puLYZaO3CH0qfGyDfbDG1KFCE4lCSwK0l1EuEbNa4pIJJXL93ltU5SsYia/DHJUgMY2qE4XRQg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ping@0.4.4:
|
||||
resolution: {integrity: sha512-56ZMC0j7SCsMMLdOoUg12VZCfj/+ZO+yfOSjaNCRrmZZr6GLbN2X/Ui56T15dI8NhiHckaw5X2pvyfAomanwqQ==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
@ -3506,6 +3546,11 @@ packages:
|
||||
proxy-from-env@1.1.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-+6bkjnf0yQ4+tZV0zJv1017DiIF7y6R4yg17Mrhhkc25L7dtQtXWHgSCrz9BbLL4OeTFbPK4EALXqJUrwCIWXw==}
|
||||
engines: {node: '>=14.16'}
|
||||
@ -3796,6 +3841,9 @@ packages:
|
||||
resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
split@0.3.3:
|
||||
resolution: {integrity: sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=}
|
||||
|
||||
sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=}
|
||||
|
||||
@ -3817,6 +3865,9 @@ packages:
|
||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
stream-combiner@0.0.4:
|
||||
resolution: {integrity: sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=}
|
||||
|
||||
stream-shift@1.0.3:
|
||||
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
|
||||
|
||||
@ -3923,6 +3974,9 @@ packages:
|
||||
through2@4.0.2:
|
||||
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
|
||||
|
||||
through@2.3.8:
|
||||
resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=}
|
||||
|
||||
tiny-worker@2.3.0:
|
||||
resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==}
|
||||
|
||||
@ -5370,6 +5424,25 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- 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':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.1.10
|
||||
@ -7345,6 +7418,8 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
duplexer@0.1.2: {}
|
||||
|
||||
duplexify@3.7.1:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.4
|
||||
@ -7516,6 +7591,16 @@ snapshots:
|
||||
|
||||
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: {}
|
||||
|
||||
execa@5.1.1:
|
||||
@ -7712,6 +7797,8 @@ snapshots:
|
||||
inherits: 2.0.4
|
||||
readable-stream: 2.3.8
|
||||
|
||||
from@0.1.7: {}
|
||||
|
||||
fs-constants@1.0.0: {}
|
||||
|
||||
fs-extra@10.1.0:
|
||||
@ -8377,6 +8464,8 @@ snapshots:
|
||||
|
||||
make-error@1.3.6: {}
|
||||
|
||||
map-stream@0.1.0: {}
|
||||
|
||||
markdown-table@3.0.4: {}
|
||||
|
||||
matcher@3.0.0:
|
||||
@ -9020,6 +9109,10 @@ snapshots:
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
pause-stream@0.0.11:
|
||||
dependencies:
|
||||
through: 2.3.8
|
||||
|
||||
pdf-lib@1.17.1:
|
||||
dependencies:
|
||||
'@pdf-lib/standard-fonts': 1.0.0
|
||||
@ -9043,6 +9136,10 @@ snapshots:
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
|
||||
pidusage@4.0.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
ping@0.4.4: {}
|
||||
|
||||
pkg-dir@4.2.0:
|
||||
@ -9095,6 +9192,10 @@ snapshots:
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
ps-tree@1.2.0:
|
||||
dependencies:
|
||||
event-stream: 3.3.4
|
||||
|
||||
public-ip@6.0.2:
|
||||
dependencies:
|
||||
aggregate-error: 4.0.1
|
||||
@ -9504,6 +9605,10 @@ snapshots:
|
||||
signal-exit: 3.0.7
|
||||
which: 2.0.2
|
||||
|
||||
split@0.3.3:
|
||||
dependencies:
|
||||
through: 2.3.8
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
sprintf-js@1.1.3: {}
|
||||
@ -9518,6 +9623,10 @@ snapshots:
|
||||
|
||||
statuses@2.0.1: {}
|
||||
|
||||
stream-combiner@0.0.4:
|
||||
dependencies:
|
||||
duplexer: 0.1.2
|
||||
|
||||
stream-shift@1.0.3: {}
|
||||
|
||||
streamsearch@0.1.2: {}
|
||||
@ -9652,6 +9761,8 @@ snapshots:
|
||||
dependencies:
|
||||
readable-stream: 3.6.2
|
||||
|
||||
through@2.3.8: {}
|
||||
|
||||
tiny-worker@2.3.0:
|
||||
dependencies:
|
||||
esm: 3.2.25
|
||||
|
19
test/test.ts
19
test/test.ts
@ -6,3 +6,22 @@ tap.test('first test', async () => {
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tspm',
|
||||
version: '1.1.1',
|
||||
version: '1.4.0',
|
||||
description: 'a no fuzz process manager'
|
||||
}
|
||||
|
20
ts/classes.config.ts
Normal file
20
ts/classes.config.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -1,21 +1,23 @@
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import psTree from 'ps-tree';
|
||||
import pidusage from 'pidusage';
|
||||
import * as plugins from './plugins.js';
|
||||
import { ProcessWrapper } from './classes.processwrapper.js';
|
||||
|
||||
interface IMonitorConfig {
|
||||
export interface IMonitorConfig {
|
||||
name?: string; // Optional name to identify the instance
|
||||
projectDir: string; // Directory where the command will run
|
||||
command: string; // Full command to run (e.g., "npm run xyz")
|
||||
args?: string[]; // Optional: arguments for the command
|
||||
memoryLimitBytes: number; // Maximum allowed memory (in bytes) for the process group
|
||||
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 {
|
||||
private child: ChildProcess | null = null;
|
||||
export class ProcessMonitor {
|
||||
private processWrapper: ProcessWrapper | null = null;
|
||||
private config: IMonitorConfig;
|
||||
private intervalId: NodeJS.Timeout | null = null;
|
||||
private stopped: boolean = true; // Initially stopped until start() is called
|
||||
private restartCount: number = 0;
|
||||
|
||||
constructor(config: IMonitorConfig) {
|
||||
this.config = config;
|
||||
@ -25,59 +27,64 @@ class ProcessMonitor {
|
||||
// Reset the stopped flag so that new processes can spawn.
|
||||
this.stopped = false;
|
||||
this.log(`Starting process monitor.`);
|
||||
this.spawnChild();
|
||||
this.spawnProcess();
|
||||
|
||||
// Set the monitoring interval.
|
||||
const interval = this.config.monitorIntervalMs || 5000;
|
||||
this.intervalId = setInterval(() => {
|
||||
if (this.child && this.child.pid) {
|
||||
this.monitorProcessGroup(this.child.pid, this.config.memoryLimitBytes);
|
||||
if (this.processWrapper && this.processWrapper.getPid()) {
|
||||
this.monitorProcessGroup(this.processWrapper.getPid()!, this.config.memoryLimitBytes);
|
||||
}
|
||||
}, interval);
|
||||
}
|
||||
|
||||
private spawnChild(): void {
|
||||
private spawnProcess(): void {
|
||||
// Don't spawn if the monitor has been stopped.
|
||||
if (this.stopped) return;
|
||||
|
||||
if (this.config.args && this.config.args.length > 0) {
|
||||
this.log(
|
||||
`Spawning command "${this.config.command}" with args [${this.config.args.join(
|
||||
', '
|
||||
)}] in directory: ${this.config.projectDir}`
|
||||
);
|
||||
this.child = spawn(this.config.command, this.config.args, {
|
||||
cwd: this.config.projectDir,
|
||||
detached: true,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} 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,
|
||||
});
|
||||
}
|
||||
// Create a new process wrapper
|
||||
this.processWrapper = new ProcessWrapper({
|
||||
name: this.config.name || 'unnamed-process',
|
||||
command: this.config.command,
|
||||
args: this.config.args,
|
||||
cwd: this.config.projectDir,
|
||||
env: this.config.env,
|
||||
logBuffer: this.config.logBufferSize,
|
||||
});
|
||||
|
||||
this.log(`Spawned process with PID ${this.child.pid}`);
|
||||
|
||||
// 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) {
|
||||
this.log('Restarting process...');
|
||||
this.spawnChild();
|
||||
// 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.processWrapper.on('exit', (code, signal) => {
|
||||
this.log(`Process exited with code ${code}, signal ${signal}.`);
|
||||
if (!this.stopped) {
|
||||
this.log('Restarting process...');
|
||||
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 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.
|
||||
*/
|
||||
private async monitorProcessGroup(pid: number, memoryLimit: number): Promise<void> {
|
||||
@ -94,8 +101,10 @@ class ProcessMonitor {
|
||||
memoryUsage
|
||||
)} exceeds limit of ${this.humanReadableBytes(memoryLimit)}. Restarting process.`
|
||||
);
|
||||
// Kill the entire process group by sending a signal to -PID.
|
||||
process.kill(-pid, 'SIGKILL');
|
||||
// Stop the process wrapper, which will trigger the exit handler and restart
|
||||
if (this.processWrapper) {
|
||||
this.processWrapper.stop();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.log('Error monitoring process group: ' + error);
|
||||
@ -107,11 +116,11 @@ class ProcessMonitor {
|
||||
*/
|
||||
private getProcessGroupMemory(pid: number): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
psTree(pid, (err, children) => {
|
||||
plugins.psTree(pid, (err, children) => {
|
||||
if (err) return reject(err);
|
||||
// Include the main process and its children.
|
||||
const pids: number[] = [pid, ...children.map(child => Number(child.PID))];
|
||||
pidusage(pids, (err, stats) => {
|
||||
plugins.pidusage(pids, (err, stats) => {
|
||||
if (err) return reject(err);
|
||||
let totalMemory = 0;
|
||||
for (const key in stats) {
|
||||
@ -144,11 +153,49 @@ class ProcessMonitor {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
if (this.child && this.child.pid) {
|
||||
process.kill(-this.child.pid, 'SIGKILL');
|
||||
if (this.processWrapper) {
|
||||
this.processWrapper.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current logs from the process
|
||||
*/
|
||||
public getLogs(limit?: number): Array<{ timestamp: Date, type: string, message: string }> {
|
||||
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.
|
||||
*/
|
||||
@ -157,22 +204,3 @@ class ProcessMonitor {
|
||||
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();
|
||||
});
|
207
ts/classes.processwrapper.ts
Normal file
207
ts/classes.processwrapper.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -1,6 +1,259 @@
|
||||
import * as plugins from './plugins.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 {
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
29
ts/cli.ts
Normal file
29
ts/cli.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
|
||||
export const run = async () => {
|
||||
const tspmProjectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
||||
|
||||
const smartcliInstance = new plugins.smartcli.Smartcli();
|
||||
smartcliInstance.addVersion(tspmProjectinfo.npm.version);
|
||||
|
||||
smartcliInstance.standardCommand().subscribe({
|
||||
next: (argvArg) => {
|
||||
console.log(`Please specify a command.`)
|
||||
},
|
||||
});
|
||||
|
||||
smartcliInstance.addCommand('restart').subscribe({
|
||||
|
||||
})
|
||||
|
||||
smartcliInstance.addCommand('startAsDaemon').subscribe({
|
||||
|
||||
})
|
||||
|
||||
smartcliInstance.addCommand('stop').subscribe({
|
||||
|
||||
})
|
||||
|
||||
smartcliInstance.startParse();
|
||||
}
|
12
ts/index.ts
12
ts/index.ts
@ -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
4
ts/paths.ts
Normal 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();
|
@ -1,13 +1,30 @@
|
||||
// native scope
|
||||
import * as childProcess from 'child_process';
|
||||
import * as path from 'node:path';
|
||||
|
||||
export {
|
||||
childProcess,
|
||||
path,
|
||||
}
|
||||
|
||||
// @push.rocks scope
|
||||
import * as npmextra from '@push.rocks/npmextra';
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartcli from '@push.rocks/smartcli';
|
||||
|
||||
export {
|
||||
npmextra,
|
||||
projectinfo,
|
||||
smartpath,
|
||||
smartcli,
|
||||
}
|
||||
|
||||
// third-party scope
|
||||
import psTree from 'ps-tree';
|
||||
import pidusage from 'pidusage';
|
||||
|
||||
export {
|
||||
psTree,
|
||||
pidusage,
|
||||
}
|
Reference in New Issue
Block a user