@zuzjs/pm 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -0
- package/dist/chunk-5RMT5B2R.js +3 -0
- package/dist/chunk-BBXSSJFF.cjs +4 -0
- package/dist/chunk-IIKD4V5E.js +1 -0
- package/dist/chunk-J6AZRPZZ.js +3 -0
- package/dist/chunk-LM53EFPZ.cjs +4 -0
- package/dist/chunk-RDE2YGL7.cjs +2 -0
- package/dist/daemon.cjs +2 -0
- package/dist/daemon.d.cts +2 -0
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +232 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.js +1 -0
- package/dist/zpm.cjs +14 -0
- package/dist/zpm.d.cts +1 -0
- package/dist/zpm.d.ts +1 -0
- package/dist/zpm.js +13 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @zuzjs/pm
|
|
2
|
+
|
|
3
|
+
A modular process manager built for the `@zuzjs` ecosystem.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
├── types.ts – All shared types, enums, and interfaces
|
|
12
|
+
├── logger.ts – Thin, colorized logger (swap for @zuzjs/core)
|
|
13
|
+
├── store.ts – Reactive in-process state store (swap for @zuzjs/store)
|
|
14
|
+
├── probe.ts – Liveness probes: http | tcp | exec
|
|
15
|
+
├── worker.ts – Single-app lifecycle (Fork + Cluster modes)
|
|
16
|
+
├── process-manager.ts– Controller that owns all Worker instances
|
|
17
|
+
├── ipc-server.ts – Unix socket / Named Pipe IPC server
|
|
18
|
+
├── daemon.ts – Long-running background daemon entry point
|
|
19
|
+
├── client.ts – Programmatic API + daemon spawner
|
|
20
|
+
├── cli.ts – `zpm` CLI
|
|
21
|
+
└── index.ts – Public barrel export
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @zuzjs/pm
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## CLI Usage
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Start the daemon implicitly (auto-spawned on first command)
|
|
38
|
+
zpm start ./dist/server.js --name api --port 3000
|
|
39
|
+
|
|
40
|
+
# Cluster mode – one worker per CPU core
|
|
41
|
+
zpm start ./dist/server.js --name api --cluster --port 3000
|
|
42
|
+
|
|
43
|
+
# Dev mode – restart on file change
|
|
44
|
+
zpm start ./dist/server.js --name api --dev
|
|
45
|
+
|
|
46
|
+
# Inspect
|
|
47
|
+
zpm list
|
|
48
|
+
zpm stats
|
|
49
|
+
zpm stats api
|
|
50
|
+
|
|
51
|
+
# Control
|
|
52
|
+
zpm restart api
|
|
53
|
+
zpm stop api
|
|
54
|
+
zpm delete api
|
|
55
|
+
|
|
56
|
+
# Daemon
|
|
57
|
+
zpm kill-daemon
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Programmatic API
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { ZPMClient, WorkerMode } from "@zuzjs/pm";
|
|
66
|
+
|
|
67
|
+
const pm = new ZPMClient();
|
|
68
|
+
|
|
69
|
+
// Spawn daemon if not already running
|
|
70
|
+
await pm.ensureDaemon();
|
|
71
|
+
|
|
72
|
+
// Start a worker
|
|
73
|
+
await pm.start({
|
|
74
|
+
name: "api",
|
|
75
|
+
scriptPath: "/abs/path/to/dist/server.js",
|
|
76
|
+
port: 3000,
|
|
77
|
+
mode: WorkerMode.Fork, // or WorkerMode.Cluster
|
|
78
|
+
instances: 1, // ignored in Fork mode
|
|
79
|
+
devMode: false,
|
|
80
|
+
env: { LOG_LEVEL: "debug" },
|
|
81
|
+
killTimeout: 5000, // ms before SIGKILL
|
|
82
|
+
maxBackoff: 16000, // ms backoff ceiling
|
|
83
|
+
|
|
84
|
+
// Optional liveness probe
|
|
85
|
+
probe: {
|
|
86
|
+
type: "http",
|
|
87
|
+
target: "http://localhost:3000/health",
|
|
88
|
+
intervalSeconds: 10,
|
|
89
|
+
timeoutSeconds: 5,
|
|
90
|
+
failureThreshold: 3,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Telemetry
|
|
95
|
+
const stats = await pm.stats("api");
|
|
96
|
+
console.log(stats);
|
|
97
|
+
// [{ name, status, pid, uptime, restartCount, cpu, memoryRss, memoryHeap, mode, instances }]
|
|
98
|
+
|
|
99
|
+
// Control
|
|
100
|
+
await pm.restart("api");
|
|
101
|
+
await pm.stop("api");
|
|
102
|
+
await pm.delete("api");
|
|
103
|
+
|
|
104
|
+
// Kill the daemon itself
|
|
105
|
+
await pm.killDaemon();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Embedding Without the Daemon
|
|
111
|
+
|
|
112
|
+
For tight integration (e.g., inside an existing long-running process):
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { ProcessManager, WorkerMode } from "@zuzjs/pm";
|
|
116
|
+
|
|
117
|
+
const manager = new ProcessManager();
|
|
118
|
+
|
|
119
|
+
await manager.start({
|
|
120
|
+
name: "worker",
|
|
121
|
+
scriptPath: "./dist/worker.js",
|
|
122
|
+
mode: WorkerMode.Fork,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Graceful shutdown on SIGTERM
|
|
126
|
+
process.on("SIGTERM", async () => {
|
|
127
|
+
await manager.stopAll();
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Resiliency Features
|
|
135
|
+
|
|
136
|
+
| Feature | Details |
|
|
137
|
+
|---|---|
|
|
138
|
+
| **Exponential Backoff** | Starts at 1s, doubles on each crash, caps at 16s (configurable) |
|
|
139
|
+
| **Stability Reset** | After 5s of uptime, restart counter and backoff reset |
|
|
140
|
+
| **Immediate Crash Detection** | Crashes within 1.5s are treated as build errors – no retry until next file change |
|
|
141
|
+
| **Liveness Probes** | `http`, `tcp`, or `exec` – configurable interval, timeout, and failure threshold |
|
|
142
|
+
| **Graceful Shutdown** | `SIGTERM` → wait `killTimeout` ms → `SIGKILL` |
|
|
143
|
+
| **Dev Mode** | Chokidar watches the script's directory; restarts on `change`/`add` events |
|
|
144
|
+
| **Port Clearing** | Calls `fuser -k` to free a busy port before spawning |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Build
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npm install
|
|
152
|
+
npm run build # tsc → dist/
|
|
153
|
+
npm run dev # tsc --watch
|
|
154
|
+
```
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import f from'path';import {fileURLToPath}from'url';import I from'net';import l from'os';import d from'fs';var k=()=>fileURLToPath(import.meta.url),S=()=>f.dirname(k()),c=S();var w=(o=>(o.Fork="fork",o.Cluster="cluster",o))(w||{}),P=(s=>(s.Stopped="stopped",s.Starting="starting",s.Running="running",s.Stopping="stopping",s.Crashed="crashed",s.Errored="errored",s))(P||{});var x={info:"\x1B[36m",warn:"\x1B[33m",error:"\x1B[31m",debug:"\x1B[90m",success:"\x1B[32m"},h="\x1B[0m",v="\x1B[1m";function y(){return new Date().toISOString()}function i(e,r,...o){let n=x[e],t=`${v}${n}[ZPM/${r.toUpperCase()}]${h} ${y()}`;console[e==="success"?"log":e](`${t} \u2192`,...o);}var a={info:(e,...r)=>i("info",e,...r),warn:(e,...r)=>i("warn",e,...r),error:(e,...r)=>i("error",e,...r),debug:(e,...r)=>i("debug",e,...r),success:(e,...r)=>i("success",e,...r)};function L(){return l.platform()==="win32"?f.join("\\\\.\\pipe","zuzjs-pm"):f.join(l.tmpdir(),"zuzjs-pm.sock")}function U(e){let r=L();d.existsSync(r)&&d.unlinkSync(r);let o=I.createServer(n=>{let t="";n.on("data",m=>{t+=m.toString();let s=t.split(`
|
|
2
|
+
`);t=s.pop()??"";for(let g of s)g.trim()&&R(e,n,g);}),n.on("error",m=>{a.error("IPC","Socket error:",m.message);});});return o.listen(r,()=>{a.success("IPC",`Listening on ${r}`);}),o.on("error",n=>{a.error("IPC","Server error:",n);}),o}async function R(e,r,o){let n;try{n=JSON.parse(o);}catch{u(r,{ok:false,error:"Invalid JSON"});return}try{let t=null;switch(n.cmd){case "ping":t="pong";break;case "start":await e.start(n.config),t=`Started "${n.name}"`;break;case "stop":await e.stop(n.name),t=`Stopped "${n.name}"`;break;case "restart":await e.restart(n.name),t=`Restarted "${n.name}"`;break;case "delete":await e.delete(n.name),t=`Deleted "${n.name}"`;break;case "stats":t=await e.getStats(n.name);break;case "list":t=e.list();break;default:throw new Error(`Unknown command: ${n.cmd}`)}u(r,{ok:!0,data:t});}catch(t){u(r,{ok:false,error:String(t.message??t)});}}function u(e,r){e.writable&&e.write(JSON.stringify(r)+`
|
|
3
|
+
`);}export{c as a,w as b,P as c,a as d,L as e,U as f};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
'use strict';var chunkLM53EFPZ_cjs=require('./chunk-LM53EFPZ.cjs'),child_process=require('child_process'),l=require('fs'),f=require('net'),h=require('os'),g=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var l__default=/*#__PURE__*/_interopDefault(l);var f__default=/*#__PURE__*/_interopDefault(f);var h__default=/*#__PURE__*/_interopDefault(h);var g__default=/*#__PURE__*/_interopDefault(g);/* ZuzJS Process Manager */
|
|
2
|
+
function o(u){return new Promise((r,t)=>{let n=f__default.default.createConnection(chunkLM53EFPZ_cjs.d()),e="";n.on("connect",()=>{n.write(JSON.stringify(u)+`
|
|
3
|
+
`);}),n.on("data",i=>{e+=i.toString();let s=e.split(`
|
|
4
|
+
`);e=s.pop()??"";for(let m of s)if(m.trim())try{let a=JSON.parse(m);n.destroy(),a.ok?r(a.data):t(new Error(a.error));}catch(a){n.destroy(),t(a);}}),n.on("error",i=>t(i)),n.setTimeout(1e4,()=>{n.destroy(),t(new Error("IPC timeout"));});})}var d=class{daemonScript;constructor(r){this.daemonScript=r??g__default.default.join(__dirname,"daemon.js");}async isDaemonRunning(){try{return chunkLM53EFPZ_cjs.c.info("[ZPM]","Daemon is Running :?"),await o({cmd:"ping"}),!0}catch{return chunkLM53EFPZ_cjs.c.info("[ZPM]","Daemon is not running."),false}}async ensureDaemon(r){if(await this.isDaemonRunning())return;chunkLM53EFPZ_cjs.c.info("Starting ZPM daemon...");let n=r?.devMode??process.env.NODE_ENV!=="production";child_process.spawn(process.execPath,[this.daemonScript],{detached:true,stdio:n?"inherit":"ignore"}).unref(),await this.waitForDaemon(8e3);}async killDaemon(){let r=g__default.default.join(h__default.default.tmpdir(),"zuzjs-pm.pid");if(!l__default.default.existsSync(r))throw new Error("Daemon PID file not found \u2013 is the daemon running?");let t=Number(l__default.default.readFileSync(r,"utf8").trim());try{process.kill(t,"SIGTERM"),console.log(`[ZPM] Sent SIGTERM to daemon (PID ${t})`);}catch(n){throw new Error(`Failed to kill daemon: ${n.message}`)}}async start(r){return o({cmd:"start",name:r.name,config:r})}async stop(r){return o({cmd:"stop",name:r})}async restart(r){return o({cmd:"restart",name:r})}async delete(r){return o({cmd:"delete",name:r})}async stats(r){return o({cmd:"stats",name:r})}async list(){return o({cmd:"list"})}waitForDaemon(r){let t=Date.now(),n=200;return new Promise((e,i)=>{let s=()=>{this.isDaemonRunning().then(m=>{if(m)return e();if(Date.now()-t>r)return i(new Error("Daemon did not start in time"));setTimeout(s,n);});};setTimeout(s,n);})}},R=new d;exports.a=d;exports.b=R;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {d as d$1}from'./chunk-5RMT5B2R.js';import T from'events';import {spawn,exec}from'child_process';import y from'http';import S from'https';import C from'net';import M from'chokidar';import R from'fs';import _ from'os';import g from'path';import A from'pidusage';var m=class extends T{map=new Map;set(t,e){this.map.set(t,e),this.emit("change",t,e);}get(t){return this.map.get(t)}has(t){return this.map.has(t)}delete(t){this.map.delete(t),this.emit("delete",t);}all(){return new Map(this.map)}onchange(t){return this.on("change",t)}offchange(t){return this.off("change",t)}},c=new m;function W(s,t){return new Promise(e=>{let o=(s.startsWith("https")?S:y).get(s,{timeout:t},n=>{e((n.statusCode??500)<500);});o.on("error",()=>e(false)),o.on("timeout",()=>{o.destroy(),e(false);});})}function F(s,t){let[e,r]=s.split(":"),o=Number(r);return new Promise(n=>{let a=C.createConnection({host:e,port:o},()=>{a.destroy(),n(true);});a.setTimeout(t),a.on("timeout",()=>{a.destroy(),n(false);}),a.on("error",()=>n(false));})}function x(s,t){return new Promise(e=>{let r=setTimeout(()=>e(false),t);exec(s,o=>{clearTimeout(r),e(!o);});})}async function d(s){let t=(s.timeoutSeconds??5)*1e3;switch(s.type){case "http":return W(s.target,t);case "tcp":return F(s.target,t);case "exec":return x(s.target,t);default:return false}}var w=5e3,E=16e3,b=1e3,D=5e3;async function q(s){return new Promise(t=>{C.createServer().once("error",()=>t(false)).once("listening",function(){this.close(()=>t(true));}).listen(s);})}async function N(s){await q(s)||(d$1.warn("port",`Port ${s} busy \u2013 attempting fuser kill`),await new Promise(t=>exec(`fuser -k -9 ${s}/tcp 2>/dev/null; true`,()=>t())),await new Promise(t=>setTimeout(t,500)));}function k(s,t){let e=s.process?.pid??s.pid;if(!e)return;try{process.kill(e,"SIGTERM");}catch{return}let r=setTimeout(()=>{try{process.kill(e,"SIGKILL");}catch{}},t);s.once?.("exit",()=>clearTimeout(r)),s.once?.("exit",()=>clearTimeout(r));}var l=class{cfg;name;watcher=null;constructor(t){this.cfg={mode:"fork",instances:1,...t},this.name=t.name,this.initStore();}async start(){let t=this.mp();if(t.status==="running"||t.status==="starting"){d$1.warn(this.name,"Already running \u2013 ignoring start()");return}this.patch({status:"starting"}),await this.spawnAll(),this.cfg.devMode&&this.watchFiles();}async stop(){let t=this.mp();this.patch({status:"stopping",isRestarting:false}),this.clearTimers(),this.stopProbe();for(let e of t.children)k(e,this.cfg.killTimeout??w);this.stopWatcher(),this.patch({children:[],status:"stopped",startTime:null}),d$1.info(this.name,"Stopped.");}async restart(){d$1.info(this.name,"Restarting...");let t=this.mp();this.patch({isRestarting:true}),this.clearTimers(),this.stopProbe();for(let e of t.children)k(e,this.cfg.killTimeout??w);}async getStats(){let t=this.mp(),e=t.children[0]?.pid??null,r=null,o=null,n=null;if(e&&t.status==="running")try{let a=await A(e);r=a.cpu,o=a.memory;}catch{}return {name:this.name,status:t.status,pid:e,uptime:t.startTime?Date.now()-t.startTime:null,restartCount:t.restartCount,cpu:r,memoryRss:o,memoryHeap:n,mode:this.cfg.mode??"fork",instances:t.children.length}}async spawnAll(){if(!R.existsSync(this.cfg.scriptPath)){d$1.error(this.name,`Script not found: ${this.cfg.scriptPath}. Waiting for build...`),this.patch({status:"errored"});return}this.cfg.port&&await N(this.cfg.port);let t=this.cfg.mode??"fork",e=t==="cluster"?this.cfg.instances??_.cpus().length:1,r=[];for(let n=0;n<e;n++){let a=this.forkChild();a&&r.push(a);}this.patch({children:r,startTime:Date.now(),status:"running"}),d$1.success(this.name,`Started ${r.length} instance(s) [${t}]`);let o=setTimeout(()=>{this.mp().status==="running"&&(this.patch({backoffTime:b,restartCount:0}),d$1.success(this.name,"Process is stable."));},D);this.patch({stabilityTimer:o}),this.cfg.probe&&this.startProbe();}forkChild(){try{let t=spawn("node",[this.cfg.scriptPath,...this.cfg.args??[]],{stdio:"inherit",env:{...process.env,...this.cfg.env??{},NODE_ENV:this.cfg.devMode?"development":"production"},detached:!1,shell:!1}),e=Date.now();return t.on("error",r=>{d$1.error(this.name,"Spawn error:",r);}),t.on("exit",(r,o)=>{let n=Date.now()-e;this.onChildExit(t,r,o,n);}),t}catch(t){return d$1.error(this.name,"Failed to fork child:",t),null}}onChildExit(t,e,r,o){let n=this.mp(),a=n.children.filter(f=>f!==t);if(this.patch({children:a}),n.status!=="stopping"){if(d$1.warn(this.name,`Process exited (code=${e}, signal=${r}, uptime=${o}ms)`),n.isRestarting){a.length===0&&(this.patch({isRestarting:false}),this.spawnAll());return}if(e!==0&&e!==null){if(this.patch({status:"crashed"}),o<1500){d$1.error(this.name,`Immediate crash (${o}ms) \u2013 likely a syntax/build error. Waiting for next file change.`);return}this.scheduleRestart();}}}scheduleRestart(){let t=this.mp(),e=t.backoffTime,r=this.cfg.maxBackoff??E;d$1.warn(this.name,`Scheduling restart in ${e}ms (attempt #${t.restartCount+1})`);let o=setTimeout(async()=>{this.patch({restartCount:t.restartCount+1,backoffTime:Math.min(e*2,r)}),await this.spawnAll();},e);this.patch({restartTimer:o});}startProbe(){let t=this.cfg.probe,e=(t.intervalSeconds??10)*1e3,r=t.failureThreshold??3,n=setInterval(async()=>{let a=this.mp();if(a.status!=="running")return;if(await d(t)){a.probeFailures>0&&this.patch({probeFailures:0});return}let h=a.probeFailures+1;this.patch({probeFailures:h}),d$1.warn(this.name,`Liveness probe failed (${h}/${r})`),h>=r&&(d$1.error(this.name,"Liveness probe threshold exceeded \u2013 restarting."),this.patch({probeFailures:0}),await this.restart());},e);this.patch({probeTimer:n});}stopProbe(){let{probeTimer:t}=this.mp();t&&(clearInterval(t),this.patch({probeTimer:null,probeFailures:0}));}watchFiles(){this.stopWatcher();let t=g.dirname(this.cfg.scriptPath);this.watcher=M.watch(t,{ignored:[/node_modules/,/\.pid$/],persistent:true,ignoreInitial:true,awaitWriteFinish:{stabilityThreshold:1500,pollInterval:500}}),this.watcher.on("all",(e,r)=>{(e==="change"||e==="add")&&(d$1.info(this.name,`File ${e}: ${g.basename(r)} \u2013 restarting`),this.restart());}),this.watcher.on("error",e=>d$1.error(this.name,"Watcher error:",e)),this.watcher.on("ready",()=>d$1.info(this.name,`Watching ${t}`));}stopWatcher(){this.watcher&&(this.watcher.close(),this.watcher=null);}initStore(){c.set(this.name,{config:this.cfg,children:[],status:"stopped",startTime:null,restartCount:0,backoffTime:b,restartTimer:null,stabilityTimer:null,probeTimer:null,probeFailures:0,isRestarting:false});}mp(){return c.get(this.name)}patch(t){c.set(this.name,{...this.mp(),...t});}clearTimers(){let{restartTimer:t,stabilityTimer:e}=this.mp();t&&clearTimeout(t),e&&clearTimeout(e),this.patch({restartTimer:null,stabilityTimer:null});}};var P=class{workers=new Map;async start(t){if(this.workers.has(t.name)){d$1.warn("PM",`Worker "${t.name}" already registered \u2013 use restart()`);return}let e=new l(t);this.workers.set(t.name,e),await e.start();}async stop(t){await this.require(t).stop();}async restart(t){await this.require(t).restart();}async delete(t){await this.require(t).stop(),this.workers.delete(t),c.delete(t),d$1.info("PM",`Deleted worker "${t}"`);}async getStats(t){return t?[await this.require(t).getStats()]:await Promise.all([...this.workers.values()].map(r=>r.getStats()))}list(){return [...this.workers.keys()]}async stopAll(){d$1.info("PM","Stopping all workers..."),await Promise.all([...this.workers.values()].map(t=>t.stop())),d$1.info("PM","All workers stopped.");}require(t){let e=this.workers.get(t);if(!e)throw new Error(`Worker "${t}" not found`);return e}};export{c as a,d as b,l as c,P as d};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import {a,d,e}from'./chunk-5RMT5B2R.js';import {spawn}from'child_process';import g from'fs';import h from'net';import w from'os';import P from'path';function o(d){return new Promise((r,t)=>{let n=h.createConnection(e()),e$1="";n.on("connect",()=>{n.write(JSON.stringify(d)+`
|
|
2
|
+
`);}),n.on("data",i=>{e$1+=i.toString();let s=e$1.split(`
|
|
3
|
+
`);e$1=s.pop()??"";for(let m of s)if(m.trim())try{let a=JSON.parse(m);n.destroy(),a.ok?r(a.data):t(new Error(a.error));}catch(a){n.destroy(),t(a);}}),n.on("error",i=>t(i)),n.setTimeout(1e4,()=>{n.destroy(),t(new Error("IPC timeout"));});})}var u=class{daemonScript;constructor(r){this.daemonScript=r??P.join(a,"daemon.js");}async isDaemonRunning(){try{return d.info("[ZPM]","Daemon is Running :?"),await o({cmd:"ping"}),!0}catch{return d.info("[ZPM]","Daemon is not running."),false}}async ensureDaemon(r){if(await this.isDaemonRunning())return;d.info("Starting ZPM daemon...");let n=r?.devMode??process.env.NODE_ENV!=="production";spawn(process.execPath,[this.daemonScript],{detached:true,stdio:n?"inherit":"ignore"}).unref(),await this.waitForDaemon(8e3);}async killDaemon(){let r=P.join(w.tmpdir(),"zuzjs-pm.pid");if(!g.existsSync(r))throw new Error("Daemon PID file not found \u2013 is the daemon running?");let t=Number(g.readFileSync(r,"utf8").trim());try{process.kill(t,"SIGTERM"),console.log(`[ZPM] Sent SIGTERM to daemon (PID ${t})`);}catch(n){throw new Error(`Failed to kill daemon: ${n.message}`)}}async start(r){return o({cmd:"start",name:r.name,config:r})}async stop(r){return o({cmd:"stop",name:r})}async restart(r){return o({cmd:"restart",name:r})}async delete(r){return o({cmd:"delete",name:r})}async stats(r){return o({cmd:"stats",name:r})}async list(){return o({cmd:"list"})}waitForDaemon(r){let t=Date.now(),n=200;return new Promise((e,i)=>{let s=()=>{this.isDaemonRunning().then(m=>{if(m)return e();if(Date.now()-t>r)return i(new Error("Daemon did not start in time"));setTimeout(s,n);});};setTimeout(s,n);})}},C=new u;export{u as a,C as b};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
'use strict';var P=require('net'),p=require('os'),l=require('fs'),d=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var P__default=/*#__PURE__*/_interopDefault(P);var p__default=/*#__PURE__*/_interopDefault(p);var l__default=/*#__PURE__*/_interopDefault(l);var d__default=/*#__PURE__*/_interopDefault(d);/* ZuzJS Process Manager */
|
|
2
|
+
var f=(o=>(o.Fork="fork",o.Cluster="cluster",o))(f||{}),b=(s=>(s.Stopped="stopped",s.Starting="starting",s.Running="running",s.Stopping="stopping",s.Crashed="crashed",s.Errored="errored",s))(b||{});var C={info:"\x1B[36m",warn:"\x1B[33m",error:"\x1B[31m",debug:"\x1B[90m",success:"\x1B[32m"},k="\x1B[0m",S="\x1B[1m";function w(){return new Date().toISOString()}function i(e,r,...o){let n=C[e],t=`${S}${n}[ZPM/${r.toUpperCase()}]${k} ${w()}`;console[e==="success"?"log":e](`${t} \u2192`,...o);}var a={info:(e,...r)=>i("info",e,...r),warn:(e,...r)=>i("warn",e,...r),error:(e,...r)=>i("error",e,...r),debug:(e,...r)=>i("debug",e,...r),success:(e,...r)=>i("success",e,...r)};function x(){return p__default.default.platform()==="win32"?d__default.default.join("\\\\.\\pipe","zuzjs-pm"):d__default.default.join(p__default.default.tmpdir(),"zuzjs-pm.sock")}function J(e){let r=x();l__default.default.existsSync(r)&&l__default.default.unlinkSync(r);let o=P__default.default.createServer(n=>{let t="";n.on("data",m=>{t+=m.toString();let s=t.split(`
|
|
3
|
+
`);t=s.pop()??"";for(let u of s)u.trim()&&h(e,n,u);}),n.on("error",m=>{a.error("IPC","Socket error:",m.message);});});return o.listen(r,()=>{a.success("IPC",`Listening on ${r}`);}),o.on("error",n=>{a.error("IPC","Server error:",n);}),o}async function h(e,r,o){let n;try{n=JSON.parse(o);}catch{c(r,{ok:false,error:"Invalid JSON"});return}try{let t=null;switch(n.cmd){case "ping":t="pong";break;case "start":await e.start(n.config),t=`Started "${n.name}"`;break;case "stop":await e.stop(n.name),t=`Stopped "${n.name}"`;break;case "restart":await e.restart(n.name),t=`Restarted "${n.name}"`;break;case "delete":await e.delete(n.name),t=`Deleted "${n.name}"`;break;case "stats":t=await e.getStats(n.name);break;case "list":t=e.list();break;default:throw new Error(`Unknown command: ${n.cmd}`)}c(r,{ok:!0,data:t});}catch(t){c(r,{ok:false,error:String(t.message??t)});}}function c(e,r){e.writable&&e.write(JSON.stringify(r)+`
|
|
4
|
+
`);}exports.a=f;exports.b=b;exports.c=a;exports.d=x;exports.e=J;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var chunkLM53EFPZ_cjs=require('./chunk-LM53EFPZ.cjs'),P=require('events'),child_process=require('child_process'),v=require('http'),y=require('https'),S=require('net'),x=require('chokidar'),I=require('fs'),L=require('os'),d=require('path'),_=require('pidusage');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var P__default=/*#__PURE__*/_interopDefault(P);var v__default=/*#__PURE__*/_interopDefault(v);var y__default=/*#__PURE__*/_interopDefault(y);var S__default=/*#__PURE__*/_interopDefault(S);var x__default=/*#__PURE__*/_interopDefault(x);var I__default=/*#__PURE__*/_interopDefault(I);var L__default=/*#__PURE__*/_interopDefault(L);var d__default=/*#__PURE__*/_interopDefault(d);var ___default=/*#__PURE__*/_interopDefault(_);/* ZuzJS Process Manager */
|
|
2
|
+
var m=class extends P__default.default{map=new Map;set(t,e){this.map.set(t,e),this.emit("change",t,e);}get(t){return this.map.get(t)}has(t){return this.map.has(t)}delete(t){this.map.delete(t),this.emit("delete",t);}all(){return new Map(this.map)}onchange(t){return this.on("change",t)}offchange(t){return this.off("change",t)}},c=new m;function C(s,t){return new Promise(e=>{let o=(s.startsWith("https")?y__default.default:v__default.default).get(s,{timeout:t},n=>{e((n.statusCode??500)<500);});o.on("error",()=>e(false)),o.on("timeout",()=>{o.destroy(),e(false);});})}function W(s,t){let[e,r]=s.split(":"),o=Number(r);return new Promise(n=>{let a=S__default.default.createConnection({host:e,port:o},()=>{a.destroy(),n(true);});a.setTimeout(t),a.on("timeout",()=>{a.destroy(),n(false);}),a.on("error",()=>n(false));})}function F(s,t){return new Promise(e=>{let r=setTimeout(()=>e(false),t);child_process.exec(s,o=>{clearTimeout(r),e(!o);});})}async function f(s){let t=(s.timeoutSeconds??5)*1e3;switch(s.type){case "http":return C(s.target,t);case "tcp":return W(s.target,t);case "exec":return F(s.target,t);default:return false}}var g=5e3,A=16e3,w=1e3,E=5e3;async function D(s){return new Promise(t=>{S__default.default.createServer().once("error",()=>t(false)).once("listening",function(){this.close(()=>t(true));}).listen(s);})}async function q(s){await D(s)||(chunkLM53EFPZ_cjs.c.warn("port",`Port ${s} busy \u2013 attempting fuser kill`),await new Promise(t=>child_process.exec(`fuser -k -9 ${s}/tcp 2>/dev/null; true`,()=>t())),await new Promise(t=>setTimeout(t,500)));}function b(s,t){let e=s.process?.pid??s.pid;if(!e)return;try{process.kill(e,"SIGTERM");}catch{return}let r=setTimeout(()=>{try{process.kill(e,"SIGKILL");}catch{}},t);s.once?.("exit",()=>clearTimeout(r)),s.once?.("exit",()=>clearTimeout(r));}var l=class{cfg;name;watcher=null;constructor(t){this.cfg={mode:"fork",instances:1,...t},this.name=t.name,this.initStore();}async start(){let t=this.mp();if(t.status==="running"||t.status==="starting"){chunkLM53EFPZ_cjs.c.warn(this.name,"Already running \u2013 ignoring start()");return}this.patch({status:"starting"}),await this.spawnAll(),this.cfg.devMode&&this.watchFiles();}async stop(){let t=this.mp();this.patch({status:"stopping",isRestarting:false}),this.clearTimers(),this.stopProbe();for(let e of t.children)b(e,this.cfg.killTimeout??g);this.stopWatcher(),this.patch({children:[],status:"stopped",startTime:null}),chunkLM53EFPZ_cjs.c.info(this.name,"Stopped.");}async restart(){chunkLM53EFPZ_cjs.c.info(this.name,"Restarting...");let t=this.mp();this.patch({isRestarting:true}),this.clearTimers(),this.stopProbe();for(let e of t.children)b(e,this.cfg.killTimeout??g);}async getStats(){let t=this.mp(),e=t.children[0]?.pid??null,r=null,o=null,n=null;if(e&&t.status==="running")try{let a=await ___default.default(e);r=a.cpu,o=a.memory;}catch{}return {name:this.name,status:t.status,pid:e,uptime:t.startTime?Date.now()-t.startTime:null,restartCount:t.restartCount,cpu:r,memoryRss:o,memoryHeap:n,mode:this.cfg.mode??"fork",instances:t.children.length}}async spawnAll(){if(!I__default.default.existsSync(this.cfg.scriptPath)){chunkLM53EFPZ_cjs.c.error(this.name,`Script not found: ${this.cfg.scriptPath}. Waiting for build...`),this.patch({status:"errored"});return}this.cfg.port&&await q(this.cfg.port);let t=this.cfg.mode??"fork",e=t==="cluster"?this.cfg.instances??L__default.default.cpus().length:1,r=[];for(let n=0;n<e;n++){let a=this.forkChild();a&&r.push(a);}this.patch({children:r,startTime:Date.now(),status:"running"}),chunkLM53EFPZ_cjs.c.success(this.name,`Started ${r.length} instance(s) [${t}]`);let o=setTimeout(()=>{this.mp().status==="running"&&(this.patch({backoffTime:w,restartCount:0}),chunkLM53EFPZ_cjs.c.success(this.name,"Process is stable."));},E);this.patch({stabilityTimer:o}),this.cfg.probe&&this.startProbe();}forkChild(){try{let t=child_process.spawn("node",[this.cfg.scriptPath,...this.cfg.args??[]],{stdio:"inherit",env:{...process.env,...this.cfg.env??{},NODE_ENV:this.cfg.devMode?"development":"production"},detached:!1,shell:!1}),e=Date.now();return t.on("error",r=>{chunkLM53EFPZ_cjs.c.error(this.name,"Spawn error:",r);}),t.on("exit",(r,o)=>{let n=Date.now()-e;this.onChildExit(t,r,o,n);}),t}catch(t){return chunkLM53EFPZ_cjs.c.error(this.name,"Failed to fork child:",t),null}}onChildExit(t,e,r,o){let n=this.mp(),a=n.children.filter(u=>u!==t);if(this.patch({children:a}),n.status!=="stopping"){if(chunkLM53EFPZ_cjs.c.warn(this.name,`Process exited (code=${e}, signal=${r}, uptime=${o}ms)`),n.isRestarting){a.length===0&&(this.patch({isRestarting:false}),this.spawnAll());return}if(e!==0&&e!==null){if(this.patch({status:"crashed"}),o<1500){chunkLM53EFPZ_cjs.c.error(this.name,`Immediate crash (${o}ms) \u2013 likely a syntax/build error. Waiting for next file change.`);return}this.scheduleRestart();}}}scheduleRestart(){let t=this.mp(),e=t.backoffTime,r=this.cfg.maxBackoff??A;chunkLM53EFPZ_cjs.c.warn(this.name,`Scheduling restart in ${e}ms (attempt #${t.restartCount+1})`);let o=setTimeout(async()=>{this.patch({restartCount:t.restartCount+1,backoffTime:Math.min(e*2,r)}),await this.spawnAll();},e);this.patch({restartTimer:o});}startProbe(){let t=this.cfg.probe,e=(t.intervalSeconds??10)*1e3,r=t.failureThreshold??3,n=setInterval(async()=>{let a=this.mp();if(a.status!=="running")return;if(await f(t)){a.probeFailures>0&&this.patch({probeFailures:0});return}let h=a.probeFailures+1;this.patch({probeFailures:h}),chunkLM53EFPZ_cjs.c.warn(this.name,`Liveness probe failed (${h}/${r})`),h>=r&&(chunkLM53EFPZ_cjs.c.error(this.name,"Liveness probe threshold exceeded \u2013 restarting."),this.patch({probeFailures:0}),await this.restart());},e);this.patch({probeTimer:n});}stopProbe(){let{probeTimer:t}=this.mp();t&&(clearInterval(t),this.patch({probeTimer:null,probeFailures:0}));}watchFiles(){this.stopWatcher();let t=d__default.default.dirname(this.cfg.scriptPath);this.watcher=x__default.default.watch(t,{ignored:[/node_modules/,/\.pid$/],persistent:true,ignoreInitial:true,awaitWriteFinish:{stabilityThreshold:1500,pollInterval:500}}),this.watcher.on("all",(e,r)=>{(e==="change"||e==="add")&&(chunkLM53EFPZ_cjs.c.info(this.name,`File ${e}: ${d__default.default.basename(r)} \u2013 restarting`),this.restart());}),this.watcher.on("error",e=>chunkLM53EFPZ_cjs.c.error(this.name,"Watcher error:",e)),this.watcher.on("ready",()=>chunkLM53EFPZ_cjs.c.info(this.name,`Watching ${t}`));}stopWatcher(){this.watcher&&(this.watcher.close(),this.watcher=null);}initStore(){c.set(this.name,{config:this.cfg,children:[],status:"stopped",startTime:null,restartCount:0,backoffTime:w,restartTimer:null,stabilityTimer:null,probeTimer:null,probeFailures:0,isRestarting:false});}mp(){return c.get(this.name)}patch(t){c.set(this.name,{...this.mp(),...t});}clearTimers(){let{restartTimer:t,stabilityTimer:e}=this.mp();t&&clearTimeout(t),e&&clearTimeout(e),this.patch({restartTimer:null,stabilityTimer:null});}};var k=class{workers=new Map;async start(t){if(this.workers.has(t.name)){chunkLM53EFPZ_cjs.c.warn("PM",`Worker "${t.name}" already registered \u2013 use restart()`);return}let e=new l(t);this.workers.set(t.name,e),await e.start();}async stop(t){await this.require(t).stop();}async restart(t){await this.require(t).restart();}async delete(t){await this.require(t).stop(),this.workers.delete(t),c.delete(t),chunkLM53EFPZ_cjs.c.info("PM",`Deleted worker "${t}"`);}async getStats(t){return t?[await this.require(t).getStats()]:await Promise.all([...this.workers.values()].map(r=>r.getStats()))}list(){return [...this.workers.keys()]}async stopAll(){chunkLM53EFPZ_cjs.c.info("PM","Stopping all workers..."),await Promise.all([...this.workers.values()].map(t=>t.stop())),chunkLM53EFPZ_cjs.c.info("PM","All workers stopped.");}require(t){let e=this.workers.get(t);if(!e)throw new Error(`Worker "${t}" not found`);return e}};exports.a=c;exports.b=f;exports.c=l;exports.d=k;
|
package/dist/daemon.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var chunkRDE2YGL7_cjs=require('./chunk-RDE2YGL7.cjs'),chunkLM53EFPZ_cjs=require('./chunk-LM53EFPZ.cjs'),e=require('fs'),m=require('os'),p=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var e__default=/*#__PURE__*/_interopDefault(e);var m__default=/*#__PURE__*/_interopDefault(m);var p__default=/*#__PURE__*/_interopDefault(p);/* ZuzJS Process Manager */
|
|
2
|
+
var i=p__default.default.join(m__default.default.tmpdir(),"zuzjs-pm.pid");function d(){e__default.default.writeFileSync(i,String(process.pid));}function u(){e__default.default.existsSync(i)&&e__default.default.unlinkSync(i);}var f=p__default.default.join(m__default.default.tmpdir(),"zuzjs-pm.snapshot.json");function l(o){try{let r=o.list();e__default.default.writeFileSync(f,JSON.stringify(r,null,2));}catch{}}async function S(){chunkLM53EFPZ_cjs.c.success("daemon",`Booting ZPM daemon (PID ${process.pid})`),d();let o=new chunkRDE2YGL7_cjs.d,r=chunkLM53EFPZ_cjs.e(o);async function t(s){chunkLM53EFPZ_cjs.c.info("daemon",`Received ${s} \u2013 shutting down\u2026`),l(o),r.close(),await o.stopAll(),u(),process.exit(0);}process.on("SIGINT",()=>t("SIGINT")),process.on("SIGTERM",()=>t("SIGTERM")),process.on("uncaughtException",s=>chunkLM53EFPZ_cjs.c.error("daemon","Uncaught exception:",s)),process.on("unhandledRejection",s=>chunkLM53EFPZ_cjs.c.error("daemon","Unhandled rejection:",s)),chunkLM53EFPZ_cjs.c.success("daemon","Ready \u2013 waiting for IPC commands.");}S().catch(o=>{chunkLM53EFPZ_cjs.c.error("daemon","Fatal startup error:",o),process.exit(1);});
|
package/dist/daemon.d.ts
ADDED
package/dist/daemon.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {d as d$2}from'./chunk-IIKD4V5E.js';import {d as d$1,f as f$1}from'./chunk-5RMT5B2R.js';import e from'fs';import m from'os';import p from'path';var i=p.join(m.tmpdir(),"zuzjs-pm.pid");function d(){e.writeFileSync(i,String(process.pid));}function u(){e.existsSync(i)&&e.unlinkSync(i);}var f=p.join(m.tmpdir(),"zuzjs-pm.snapshot.json");function l(o){try{let r=o.list();e.writeFileSync(f,JSON.stringify(r,null,2));}catch{}}async function S(){d$1.success("daemon",`Booting ZPM daemon (PID ${process.pid})`),d();let o=new d$2,r=f$1(o);async function t(s){d$1.info("daemon",`Received ${s} \u2013 shutting down\u2026`),l(o),r.close(),await o.stopAll(),u(),process.exit(0);}process.on("SIGINT",()=>t("SIGINT")),process.on("SIGTERM",()=>t("SIGTERM")),process.on("uncaughtException",s=>d$1.error("daemon","Uncaught exception:",s)),process.on("unhandledRejection",s=>d$1.error("daemon","Unhandled rejection:",s)),d$1.success("daemon","Ready \u2013 waiting for IPC commands.");}S().catch(o=>{d$1.error("daemon","Fatal startup error:",o),process.exit(1);});
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var chunkBBXSSJFF_cjs=require('./chunk-BBXSSJFF.cjs'),chunkRDE2YGL7_cjs=require('./chunk-RDE2YGL7.cjs'),chunkLM53EFPZ_cjs=require('./chunk-LM53EFPZ.cjs');Object.defineProperty(exports,"ZPMClient",{enumerable:true,get:function(){return chunkBBXSSJFF_cjs.a}});Object.defineProperty(exports,"zpm",{enumerable:true,get:function(){return chunkBBXSSJFF_cjs.b}});Object.defineProperty(exports,"ProcessManager",{enumerable:true,get:function(){return chunkRDE2YGL7_cjs.d}});Object.defineProperty(exports,"Worker",{enumerable:true,get:function(){return chunkRDE2YGL7_cjs.c}});Object.defineProperty(exports,"processStore",{enumerable:true,get:function(){return chunkRDE2YGL7_cjs.a}});Object.defineProperty(exports,"runProbe",{enumerable:true,get:function(){return chunkRDE2YGL7_cjs.b}});Object.defineProperty(exports,"WorkerMode",{enumerable:true,get:function(){return chunkLM53EFPZ_cjs.a}});Object.defineProperty(exports,"WorkerStatus",{enumerable:true,get:function(){return chunkLM53EFPZ_cjs.b}});Object.defineProperty(exports,"getSocketPath",{enumerable:true,get:function(){return chunkLM53EFPZ_cjs.d}});Object.defineProperty(exports,"logger",{enumerable:true,get:function(){return chunkLM53EFPZ_cjs.c}});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { ChildProcess } from 'node:child_process';
|
|
2
|
+
import EventEmitter from 'node:events';
|
|
3
|
+
|
|
4
|
+
declare enum WorkerMode {
|
|
5
|
+
Fork = "fork",// child_process.spawn – isolated scripts
|
|
6
|
+
Cluster = "cluster"
|
|
7
|
+
}
|
|
8
|
+
declare enum WorkerStatus {
|
|
9
|
+
Stopped = "stopped",
|
|
10
|
+
Starting = "starting",
|
|
11
|
+
Running = "running",
|
|
12
|
+
Stopping = "stopping",
|
|
13
|
+
Crashed = "crashed",
|
|
14
|
+
Errored = "errored"
|
|
15
|
+
}
|
|
16
|
+
interface WorkerConfig {
|
|
17
|
+
/** Unique name / tag for this worker */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Absolute path to the entry script */
|
|
20
|
+
scriptPath: string;
|
|
21
|
+
/** CLI arguments forwarded to the child */
|
|
22
|
+
args?: string[];
|
|
23
|
+
/** Extra environment variables merged on top of process.env */
|
|
24
|
+
env?: Record<string, string>;
|
|
25
|
+
/** Fork = spawn isolated child | Cluster = load-balanced workers */
|
|
26
|
+
mode?: WorkerMode;
|
|
27
|
+
/** Number of cluster workers (only used in Cluster mode) */
|
|
28
|
+
instances?: number;
|
|
29
|
+
/** Port the app listens on (used by port-free check) */
|
|
30
|
+
port?: number;
|
|
31
|
+
/** Dev mode – watch files and auto-restart on change */
|
|
32
|
+
devMode?: boolean;
|
|
33
|
+
/** Graceful shutdown timeout before SIGKILL (ms) */
|
|
34
|
+
killTimeout?: number;
|
|
35
|
+
/** Maximum exponential backoff ceiling (ms) */
|
|
36
|
+
maxBackoff?: number;
|
|
37
|
+
/** Liveness probe configuration */
|
|
38
|
+
probe?: LivenessProbeConfig;
|
|
39
|
+
}
|
|
40
|
+
interface LivenessProbeConfig {
|
|
41
|
+
/** "http" polls a URL | "tcp" opens a socket | "exec" runs a command */
|
|
42
|
+
type: "http" | "tcp" | "exec";
|
|
43
|
+
/** HTTP URL or TCP host:port or shell command */
|
|
44
|
+
target: string;
|
|
45
|
+
/** Seconds between probe attempts */
|
|
46
|
+
intervalSeconds?: number;
|
|
47
|
+
/** Seconds until a single probe is considered failed */
|
|
48
|
+
timeoutSeconds?: number;
|
|
49
|
+
/** Consecutive failures before marking the worker as crashed */
|
|
50
|
+
failureThreshold?: number;
|
|
51
|
+
}
|
|
52
|
+
interface WorkerStats {
|
|
53
|
+
name: string;
|
|
54
|
+
status: WorkerStatus;
|
|
55
|
+
pid: number | null;
|
|
56
|
+
uptime: number | null;
|
|
57
|
+
restartCount: number;
|
|
58
|
+
cpu: number | null;
|
|
59
|
+
memoryRss: number | null;
|
|
60
|
+
memoryHeap: number | null;
|
|
61
|
+
mode: WorkerMode;
|
|
62
|
+
instances: number;
|
|
63
|
+
}
|
|
64
|
+
type IPCCommand = {
|
|
65
|
+
cmd: "start";
|
|
66
|
+
name: string;
|
|
67
|
+
config: WorkerConfig;
|
|
68
|
+
} | {
|
|
69
|
+
cmd: "stop";
|
|
70
|
+
name: string;
|
|
71
|
+
} | {
|
|
72
|
+
cmd: "restart";
|
|
73
|
+
name: string;
|
|
74
|
+
} | {
|
|
75
|
+
cmd: "delete";
|
|
76
|
+
name: string;
|
|
77
|
+
} | {
|
|
78
|
+
cmd: "stats";
|
|
79
|
+
name?: string;
|
|
80
|
+
} | {
|
|
81
|
+
cmd: "list";
|
|
82
|
+
} | {
|
|
83
|
+
cmd: "ping";
|
|
84
|
+
};
|
|
85
|
+
type IPCResponse = {
|
|
86
|
+
ok: true;
|
|
87
|
+
data: unknown;
|
|
88
|
+
} | {
|
|
89
|
+
ok: false;
|
|
90
|
+
error: string;
|
|
91
|
+
};
|
|
92
|
+
interface ManagedProcess {
|
|
93
|
+
config: WorkerConfig;
|
|
94
|
+
children: ChildProcess[];
|
|
95
|
+
status: WorkerStatus;
|
|
96
|
+
startTime: number | null;
|
|
97
|
+
restartCount: number;
|
|
98
|
+
backoffTime: number;
|
|
99
|
+
restartTimer: NodeJS.Timeout | null;
|
|
100
|
+
stabilityTimer: NodeJS.Timeout | null;
|
|
101
|
+
probeTimer: NodeJS.Timeout | null;
|
|
102
|
+
probeFailures: number;
|
|
103
|
+
isRestarting: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* process-manager.ts
|
|
108
|
+
* Top-level controller. Owns a registry of Worker instances and
|
|
109
|
+
* exposes the unified API used by both the IPC daemon and programmatic callers.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
declare class ProcessManager {
|
|
113
|
+
private workers;
|
|
114
|
+
start(config: WorkerConfig): Promise<void>;
|
|
115
|
+
stop(name: string): Promise<void>;
|
|
116
|
+
restart(name: string): Promise<void>;
|
|
117
|
+
delete(name: string): Promise<void>;
|
|
118
|
+
getStats(name?: string): Promise<WorkerStats[]>;
|
|
119
|
+
list(): string[];
|
|
120
|
+
stopAll(): Promise<void>;
|
|
121
|
+
private require;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* worker.ts
|
|
126
|
+
* Manages the full lifecycle of one application entry:
|
|
127
|
+
* – Fork mode : child_process.spawn (isolated scripts, daemons, etc.)
|
|
128
|
+
* – Cluster mode: node:cluster workers (load-balanced HTTP servers)
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
declare class Worker {
|
|
132
|
+
private readonly cfg;
|
|
133
|
+
private readonly name;
|
|
134
|
+
private watcher;
|
|
135
|
+
constructor(config: WorkerConfig);
|
|
136
|
+
start(): Promise<void>;
|
|
137
|
+
stop(): Promise<void>;
|
|
138
|
+
restart(): Promise<void>;
|
|
139
|
+
getStats(): Promise<WorkerStats>;
|
|
140
|
+
private spawnAll;
|
|
141
|
+
private forkChild;
|
|
142
|
+
private onChildExit;
|
|
143
|
+
private scheduleRestart;
|
|
144
|
+
private startProbe;
|
|
145
|
+
private stopProbe;
|
|
146
|
+
private watchFiles;
|
|
147
|
+
private stopWatcher;
|
|
148
|
+
private initStore;
|
|
149
|
+
private mp;
|
|
150
|
+
private patch;
|
|
151
|
+
private clearTimers;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* client.ts
|
|
156
|
+
* Programmatic library API for @zuzjs/pm.
|
|
157
|
+
*
|
|
158
|
+
* Usage:
|
|
159
|
+
* import { ZPMClient } from "@zuzjs/pm";
|
|
160
|
+
* const pm = new ZPMClient();
|
|
161
|
+
* await pm.ensureDaemon(); // spawn daemon if not running
|
|
162
|
+
* await pm.start({ name: "api", scriptPath: "./dist/server.js", port: 3000 });
|
|
163
|
+
* const stats = await pm.stats("api");
|
|
164
|
+
* await pm.stop("api");
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
declare class ZPMClient {
|
|
168
|
+
private readonly daemonScript;
|
|
169
|
+
constructor(daemonScript?: string);
|
|
170
|
+
/** Returns true if daemon is reachable */
|
|
171
|
+
isDaemonRunning(): Promise<boolean>;
|
|
172
|
+
/** Spawn the daemon detached if it is not already running */
|
|
173
|
+
ensureDaemon(conf?: {
|
|
174
|
+
devMode?: boolean;
|
|
175
|
+
}): Promise<void>;
|
|
176
|
+
/** Kill the daemon by PID */
|
|
177
|
+
killDaemon(): Promise<void>;
|
|
178
|
+
start(config: WorkerConfig): Promise<string>;
|
|
179
|
+
stop(name: string): Promise<string>;
|
|
180
|
+
restart(name: string): Promise<string>;
|
|
181
|
+
delete(name: string): Promise<string>;
|
|
182
|
+
stats(name?: string): Promise<WorkerStats[]>;
|
|
183
|
+
list(): Promise<string[]>;
|
|
184
|
+
private waitForDaemon;
|
|
185
|
+
}
|
|
186
|
+
declare const zpm: ZPMClient;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* ipc-server.ts
|
|
190
|
+
* Unix-socket IPC server embedded inside daemon.ts.
|
|
191
|
+
* Each message is a newline-delimited JSON string.
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
declare function getSocketPath(): string;
|
|
195
|
+
|
|
196
|
+
declare const logger: {
|
|
197
|
+
info: (tag: string, ...a: unknown[]) => void;
|
|
198
|
+
warn: (tag: string, ...a: unknown[]) => void;
|
|
199
|
+
error: (tag: string, ...a: unknown[]) => void;
|
|
200
|
+
debug: (tag: string, ...a: unknown[]) => void;
|
|
201
|
+
success: (tag: string, ...a: unknown[]) => void;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* probe.ts
|
|
206
|
+
* Liveness probes: http | tcp | exec
|
|
207
|
+
* Returns true if the target is alive, false otherwise.
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
declare function runProbe(cfg: LivenessProbeConfig): Promise<boolean>;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* store.ts
|
|
214
|
+
* Lightweight reactive store for process state.
|
|
215
|
+
* Swap the internals for @zuzjs/store without touching the API.
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
type StoreListener<T> = (key: string, value: T) => void;
|
|
219
|
+
declare class Store<T> extends EventEmitter {
|
|
220
|
+
private map;
|
|
221
|
+
set(key: string, value: T): void;
|
|
222
|
+
get(key: string): T | undefined;
|
|
223
|
+
has(key: string): boolean;
|
|
224
|
+
delete(key: string): void;
|
|
225
|
+
all(): Map<string, T>;
|
|
226
|
+
onchange(listener: StoreListener<T>): this;
|
|
227
|
+
offchange(listener: StoreListener<T>): this;
|
|
228
|
+
}
|
|
229
|
+
/** Singleton process store – one entry per managed worker name */
|
|
230
|
+
declare const processStore: Store<ManagedProcess>;
|
|
231
|
+
|
|
232
|
+
export { type IPCCommand, type IPCResponse, type LivenessProbeConfig, type ManagedProcess, ProcessManager, Worker, type WorkerConfig, WorkerMode, type WorkerStats, WorkerStatus, ZPMClient, getSocketPath, logger, processStore, runProbe, zpm };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { ChildProcess } from 'node:child_process';
|
|
2
|
+
import EventEmitter from 'node:events';
|
|
3
|
+
|
|
4
|
+
declare enum WorkerMode {
|
|
5
|
+
Fork = "fork",// child_process.spawn – isolated scripts
|
|
6
|
+
Cluster = "cluster"
|
|
7
|
+
}
|
|
8
|
+
declare enum WorkerStatus {
|
|
9
|
+
Stopped = "stopped",
|
|
10
|
+
Starting = "starting",
|
|
11
|
+
Running = "running",
|
|
12
|
+
Stopping = "stopping",
|
|
13
|
+
Crashed = "crashed",
|
|
14
|
+
Errored = "errored"
|
|
15
|
+
}
|
|
16
|
+
interface WorkerConfig {
|
|
17
|
+
/** Unique name / tag for this worker */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Absolute path to the entry script */
|
|
20
|
+
scriptPath: string;
|
|
21
|
+
/** CLI arguments forwarded to the child */
|
|
22
|
+
args?: string[];
|
|
23
|
+
/** Extra environment variables merged on top of process.env */
|
|
24
|
+
env?: Record<string, string>;
|
|
25
|
+
/** Fork = spawn isolated child | Cluster = load-balanced workers */
|
|
26
|
+
mode?: WorkerMode;
|
|
27
|
+
/** Number of cluster workers (only used in Cluster mode) */
|
|
28
|
+
instances?: number;
|
|
29
|
+
/** Port the app listens on (used by port-free check) */
|
|
30
|
+
port?: number;
|
|
31
|
+
/** Dev mode – watch files and auto-restart on change */
|
|
32
|
+
devMode?: boolean;
|
|
33
|
+
/** Graceful shutdown timeout before SIGKILL (ms) */
|
|
34
|
+
killTimeout?: number;
|
|
35
|
+
/** Maximum exponential backoff ceiling (ms) */
|
|
36
|
+
maxBackoff?: number;
|
|
37
|
+
/** Liveness probe configuration */
|
|
38
|
+
probe?: LivenessProbeConfig;
|
|
39
|
+
}
|
|
40
|
+
interface LivenessProbeConfig {
|
|
41
|
+
/** "http" polls a URL | "tcp" opens a socket | "exec" runs a command */
|
|
42
|
+
type: "http" | "tcp" | "exec";
|
|
43
|
+
/** HTTP URL or TCP host:port or shell command */
|
|
44
|
+
target: string;
|
|
45
|
+
/** Seconds between probe attempts */
|
|
46
|
+
intervalSeconds?: number;
|
|
47
|
+
/** Seconds until a single probe is considered failed */
|
|
48
|
+
timeoutSeconds?: number;
|
|
49
|
+
/** Consecutive failures before marking the worker as crashed */
|
|
50
|
+
failureThreshold?: number;
|
|
51
|
+
}
|
|
52
|
+
interface WorkerStats {
|
|
53
|
+
name: string;
|
|
54
|
+
status: WorkerStatus;
|
|
55
|
+
pid: number | null;
|
|
56
|
+
uptime: number | null;
|
|
57
|
+
restartCount: number;
|
|
58
|
+
cpu: number | null;
|
|
59
|
+
memoryRss: number | null;
|
|
60
|
+
memoryHeap: number | null;
|
|
61
|
+
mode: WorkerMode;
|
|
62
|
+
instances: number;
|
|
63
|
+
}
|
|
64
|
+
type IPCCommand = {
|
|
65
|
+
cmd: "start";
|
|
66
|
+
name: string;
|
|
67
|
+
config: WorkerConfig;
|
|
68
|
+
} | {
|
|
69
|
+
cmd: "stop";
|
|
70
|
+
name: string;
|
|
71
|
+
} | {
|
|
72
|
+
cmd: "restart";
|
|
73
|
+
name: string;
|
|
74
|
+
} | {
|
|
75
|
+
cmd: "delete";
|
|
76
|
+
name: string;
|
|
77
|
+
} | {
|
|
78
|
+
cmd: "stats";
|
|
79
|
+
name?: string;
|
|
80
|
+
} | {
|
|
81
|
+
cmd: "list";
|
|
82
|
+
} | {
|
|
83
|
+
cmd: "ping";
|
|
84
|
+
};
|
|
85
|
+
type IPCResponse = {
|
|
86
|
+
ok: true;
|
|
87
|
+
data: unknown;
|
|
88
|
+
} | {
|
|
89
|
+
ok: false;
|
|
90
|
+
error: string;
|
|
91
|
+
};
|
|
92
|
+
interface ManagedProcess {
|
|
93
|
+
config: WorkerConfig;
|
|
94
|
+
children: ChildProcess[];
|
|
95
|
+
status: WorkerStatus;
|
|
96
|
+
startTime: number | null;
|
|
97
|
+
restartCount: number;
|
|
98
|
+
backoffTime: number;
|
|
99
|
+
restartTimer: NodeJS.Timeout | null;
|
|
100
|
+
stabilityTimer: NodeJS.Timeout | null;
|
|
101
|
+
probeTimer: NodeJS.Timeout | null;
|
|
102
|
+
probeFailures: number;
|
|
103
|
+
isRestarting: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* process-manager.ts
|
|
108
|
+
* Top-level controller. Owns a registry of Worker instances and
|
|
109
|
+
* exposes the unified API used by both the IPC daemon and programmatic callers.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
declare class ProcessManager {
|
|
113
|
+
private workers;
|
|
114
|
+
start(config: WorkerConfig): Promise<void>;
|
|
115
|
+
stop(name: string): Promise<void>;
|
|
116
|
+
restart(name: string): Promise<void>;
|
|
117
|
+
delete(name: string): Promise<void>;
|
|
118
|
+
getStats(name?: string): Promise<WorkerStats[]>;
|
|
119
|
+
list(): string[];
|
|
120
|
+
stopAll(): Promise<void>;
|
|
121
|
+
private require;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* worker.ts
|
|
126
|
+
* Manages the full lifecycle of one application entry:
|
|
127
|
+
* – Fork mode : child_process.spawn (isolated scripts, daemons, etc.)
|
|
128
|
+
* – Cluster mode: node:cluster workers (load-balanced HTTP servers)
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
declare class Worker {
|
|
132
|
+
private readonly cfg;
|
|
133
|
+
private readonly name;
|
|
134
|
+
private watcher;
|
|
135
|
+
constructor(config: WorkerConfig);
|
|
136
|
+
start(): Promise<void>;
|
|
137
|
+
stop(): Promise<void>;
|
|
138
|
+
restart(): Promise<void>;
|
|
139
|
+
getStats(): Promise<WorkerStats>;
|
|
140
|
+
private spawnAll;
|
|
141
|
+
private forkChild;
|
|
142
|
+
private onChildExit;
|
|
143
|
+
private scheduleRestart;
|
|
144
|
+
private startProbe;
|
|
145
|
+
private stopProbe;
|
|
146
|
+
private watchFiles;
|
|
147
|
+
private stopWatcher;
|
|
148
|
+
private initStore;
|
|
149
|
+
private mp;
|
|
150
|
+
private patch;
|
|
151
|
+
private clearTimers;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* client.ts
|
|
156
|
+
* Programmatic library API for @zuzjs/pm.
|
|
157
|
+
*
|
|
158
|
+
* Usage:
|
|
159
|
+
* import { ZPMClient } from "@zuzjs/pm";
|
|
160
|
+
* const pm = new ZPMClient();
|
|
161
|
+
* await pm.ensureDaemon(); // spawn daemon if not running
|
|
162
|
+
* await pm.start({ name: "api", scriptPath: "./dist/server.js", port: 3000 });
|
|
163
|
+
* const stats = await pm.stats("api");
|
|
164
|
+
* await pm.stop("api");
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
declare class ZPMClient {
|
|
168
|
+
private readonly daemonScript;
|
|
169
|
+
constructor(daemonScript?: string);
|
|
170
|
+
/** Returns true if daemon is reachable */
|
|
171
|
+
isDaemonRunning(): Promise<boolean>;
|
|
172
|
+
/** Spawn the daemon detached if it is not already running */
|
|
173
|
+
ensureDaemon(conf?: {
|
|
174
|
+
devMode?: boolean;
|
|
175
|
+
}): Promise<void>;
|
|
176
|
+
/** Kill the daemon by PID */
|
|
177
|
+
killDaemon(): Promise<void>;
|
|
178
|
+
start(config: WorkerConfig): Promise<string>;
|
|
179
|
+
stop(name: string): Promise<string>;
|
|
180
|
+
restart(name: string): Promise<string>;
|
|
181
|
+
delete(name: string): Promise<string>;
|
|
182
|
+
stats(name?: string): Promise<WorkerStats[]>;
|
|
183
|
+
list(): Promise<string[]>;
|
|
184
|
+
private waitForDaemon;
|
|
185
|
+
}
|
|
186
|
+
declare const zpm: ZPMClient;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* ipc-server.ts
|
|
190
|
+
* Unix-socket IPC server embedded inside daemon.ts.
|
|
191
|
+
* Each message is a newline-delimited JSON string.
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
declare function getSocketPath(): string;
|
|
195
|
+
|
|
196
|
+
declare const logger: {
|
|
197
|
+
info: (tag: string, ...a: unknown[]) => void;
|
|
198
|
+
warn: (tag: string, ...a: unknown[]) => void;
|
|
199
|
+
error: (tag: string, ...a: unknown[]) => void;
|
|
200
|
+
debug: (tag: string, ...a: unknown[]) => void;
|
|
201
|
+
success: (tag: string, ...a: unknown[]) => void;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* probe.ts
|
|
206
|
+
* Liveness probes: http | tcp | exec
|
|
207
|
+
* Returns true if the target is alive, false otherwise.
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
declare function runProbe(cfg: LivenessProbeConfig): Promise<boolean>;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* store.ts
|
|
214
|
+
* Lightweight reactive store for process state.
|
|
215
|
+
* Swap the internals for @zuzjs/store without touching the API.
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
type StoreListener<T> = (key: string, value: T) => void;
|
|
219
|
+
declare class Store<T> extends EventEmitter {
|
|
220
|
+
private map;
|
|
221
|
+
set(key: string, value: T): void;
|
|
222
|
+
get(key: string): T | undefined;
|
|
223
|
+
has(key: string): boolean;
|
|
224
|
+
delete(key: string): void;
|
|
225
|
+
all(): Map<string, T>;
|
|
226
|
+
onchange(listener: StoreListener<T>): this;
|
|
227
|
+
offchange(listener: StoreListener<T>): this;
|
|
228
|
+
}
|
|
229
|
+
/** Singleton process store – one entry per managed worker name */
|
|
230
|
+
declare const processStore: Store<ManagedProcess>;
|
|
231
|
+
|
|
232
|
+
export { type IPCCommand, type IPCResponse, type LivenessProbeConfig, type ManagedProcess, ProcessManager, Worker, type WorkerConfig, WorkerMode, type WorkerStats, WorkerStatus, ZPMClient, getSocketPath, logger, processStore, runProbe, zpm };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{a as ZPMClient,b as zpm}from'./chunk-J6AZRPZZ.js';export{d as ProcessManager,c as Worker,a as processStore,b as runProbe}from'./chunk-IIKD4V5E.js';export{b as WorkerMode,c as WorkerStatus,e as getSocketPath,d as logger}from'./chunk-5RMT5B2R.js';
|
package/dist/zpm.cjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';var chunkBBXSSJFF_cjs=require('./chunk-BBXSSJFF.cjs');require('./chunk-LM53EFPZ.cjs');/* ZuzJS Process Manager */
|
|
3
|
+
var[,,l,...a]=process.argv,o=new chunkBBXSSJFF_cjs.a;function m(e){let s={};for(let t=0;t<e.length;t++){let n=e[t];if(n.startsWith("--")){let r=n.slice(2),c=e[t+1];c&&!c.startsWith("--")?(s[r]=c,t++):s[r]=true;}else s._=n;}return s}function p(e){for(let s of e){let t=s.uptime!=null?`${Math.round(s.uptime/1e3)}s`:"\u2013",n=s.memoryRss!=null?`${Math.round(s.memoryRss/1024/1024)} MB`:"\u2013",r=s.cpu!=null?`${s.cpu.toFixed(1)}%`:"\u2013";console.log(` [${s.status.toUpperCase().padEnd(8)}] ${s.name.padEnd(20)}PID: ${String(s.pid??"\u2013").padEnd(7)}UP: ${t.padEnd(8)} CPU: ${r.padEnd(7)} MEM: ${n} Restarts: ${s.restartCount}`);}}async function d(){switch(l){case "start":{let e=m(a),s=e._;s||(console.error("Usage: zpm start <script> [--name <n>]"),process.exit(1)),await o.ensureDaemon();let t=await o.start({name:e.name??s,scriptPath:s,port:e.port?Number(e.port):void 0,instances:e.instances?Number(e.instances):1,devMode:!!e.dev,mode:e.cluster?"cluster":"fork"});console.log("[ZPM]",t);break}case "stop":{let[e]=a;e||(console.error("Usage: zpm stop <name>"),process.exit(1));let s=await o.stop(e);console.log("[ZPM]",s);break}case "restart":{let[e]=a;e||(console.error("Usage: zpm restart <name>"),process.exit(1));let s=await o.restart(e);console.log("[ZPM]",s);break}case "delete":{let[e]=a;e||(console.error("Usage: zpm delete <name>"),process.exit(1));let s=await o.delete(e);console.log("[ZPM]",s);break}case "list":{let e=await o.list();if(e.length===0){console.log("[ZPM] No workers registered.");break}e.forEach(s=>console.log(" \u2022",s));break}case "stats":{let[e]=a,s=await o.stats(e);if(s.length===0){console.log("[ZPM] No stats available.");break}p(s);break}case "kill-daemon":{await o.killDaemon();break}default:console.log(`
|
|
4
|
+
@zuzjs/pm \u2013 Process Manager
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
zpm start <script> [--name <n>] [--port <p>] [--instances <i>] [--dev] [--cluster]
|
|
8
|
+
zpm stop <name>
|
|
9
|
+
zpm restart <name>
|
|
10
|
+
zpm delete <name>
|
|
11
|
+
zpm list
|
|
12
|
+
zpm stats [name]
|
|
13
|
+
zpm kill-daemon
|
|
14
|
+
`);}}d().catch(e=>{console.error("[ZPM] Error:",e.message??e),process.exit(1);});
|
package/dist/zpm.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/zpm.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/zpm.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {a as a$1}from'./chunk-J6AZRPZZ.js';import'./chunk-5RMT5B2R.js';var[,,l,...a]=process.argv,o=new a$1;function m(e){let s={};for(let t=0;t<e.length;t++){let n=e[t];if(n.startsWith("--")){let r=n.slice(2),c=e[t+1];c&&!c.startsWith("--")?(s[r]=c,t++):s[r]=true;}else s._=n;}return s}function p(e){for(let s of e){let t=s.uptime!=null?`${Math.round(s.uptime/1e3)}s`:"\u2013",n=s.memoryRss!=null?`${Math.round(s.memoryRss/1024/1024)} MB`:"\u2013",r=s.cpu!=null?`${s.cpu.toFixed(1)}%`:"\u2013";console.log(` [${s.status.toUpperCase().padEnd(8)}] ${s.name.padEnd(20)}PID: ${String(s.pid??"\u2013").padEnd(7)}UP: ${t.padEnd(8)} CPU: ${r.padEnd(7)} MEM: ${n} Restarts: ${s.restartCount}`);}}async function d(){switch(l){case "start":{let e=m(a),s=e._;s||(console.error("Usage: zpm start <script> [--name <n>]"),process.exit(1)),await o.ensureDaemon();let t=await o.start({name:e.name??s,scriptPath:s,port:e.port?Number(e.port):void 0,instances:e.instances?Number(e.instances):1,devMode:!!e.dev,mode:e.cluster?"cluster":"fork"});console.log("[ZPM]",t);break}case "stop":{let[e]=a;e||(console.error("Usage: zpm stop <name>"),process.exit(1));let s=await o.stop(e);console.log("[ZPM]",s);break}case "restart":{let[e]=a;e||(console.error("Usage: zpm restart <name>"),process.exit(1));let s=await o.restart(e);console.log("[ZPM]",s);break}case "delete":{let[e]=a;e||(console.error("Usage: zpm delete <name>"),process.exit(1));let s=await o.delete(e);console.log("[ZPM]",s);break}case "list":{let e=await o.list();if(e.length===0){console.log("[ZPM] No workers registered.");break}e.forEach(s=>console.log(" \u2022",s));break}case "stats":{let[e]=a,s=await o.stats(e);if(s.length===0){console.log("[ZPM] No stats available.");break}p(s);break}case "kill-daemon":{await o.killDaemon();break}default:console.log(`
|
|
3
|
+
@zuzjs/pm \u2013 Process Manager
|
|
4
|
+
|
|
5
|
+
Commands:
|
|
6
|
+
zpm start <script> [--name <n>] [--port <p>] [--instances <i>] [--dev] [--cluster]
|
|
7
|
+
zpm stop <name>
|
|
8
|
+
zpm restart <name>
|
|
9
|
+
zpm delete <name>
|
|
10
|
+
zpm list
|
|
11
|
+
zpm stats [name]
|
|
12
|
+
zpm kill-daemon
|
|
13
|
+
`);}}d().catch(e=>{console.error("[ZPM] Error:",e.message??e),process.exit(1);});
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zuzjs/pm",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"core",
|
|
6
|
+
"zuz",
|
|
7
|
+
"zuz.js",
|
|
8
|
+
"zuz pm",
|
|
9
|
+
"zuz process manager",
|
|
10
|
+
"zuzjs"
|
|
11
|
+
],
|
|
12
|
+
"description": "Production grade process manager for the @zuzjs ecosystem",
|
|
13
|
+
"author": "Zuz.js Team <support@zuz.com.pk>",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.cjs",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"bin": {
|
|
20
|
+
"zpm": "./dist/cli.cjs"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"require": "./dist/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.17.0"
|
|
34
|
+
},
|
|
35
|
+
"sideEffects": [
|
|
36
|
+
"reflect-metadata"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"chokidar": "^5.0.0",
|
|
40
|
+
"commander": "^14.0.3",
|
|
41
|
+
"date-fns": "^4.1.0",
|
|
42
|
+
"hashids": "^2.3.0",
|
|
43
|
+
"moment": "^2.30.1",
|
|
44
|
+
"nanoid": "^5.1.6",
|
|
45
|
+
"picocolors": "^1.1.1",
|
|
46
|
+
"pidusage": "^4.0.1"
|
|
47
|
+
}
|
|
48
|
+
}
|