@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 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;
@@ -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);});
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }
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}});
@@ -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 };
@@ -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
+ }