bm2 1.0.1 → 1.0.4
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/package.json +5 -2
- package/src/constants.ts +2 -0
- package/src/daemon.ts +27 -44
- package/src/hello.js +2 -0
- package/src/index.ts +23 -43
- package/src/process-manager.ts +43 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bm2",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "A blazing-fast, full-featured process manager built entirely on Bun native APIs. The modern PM2 replacement — zero Node.js dependencies, pure Bun performance.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"module": "src/index.ts",
|
|
@@ -59,9 +59,12 @@
|
|
|
59
59
|
"linux",
|
|
60
60
|
"darwin"
|
|
61
61
|
],
|
|
62
|
-
"dependencies": {
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"ws": "^8.19.0"
|
|
64
|
+
},
|
|
63
65
|
"devDependencies": {
|
|
64
66
|
"@types/bun": "^1.3.9",
|
|
67
|
+
"@types/ws": "^8.18.1",
|
|
65
68
|
"bun-types": "latest",
|
|
66
69
|
"typescript": "^5.9.3"
|
|
67
70
|
}
|
package/src/constants.ts
CHANGED
|
@@ -23,6 +23,8 @@ export const VERSION = "1.0.0";
|
|
|
23
23
|
export const BM2_HOME = join(homedir(), ".bm2");
|
|
24
24
|
export const DAEMON_SOCKET = join(BM2_HOME, "daemon.sock");
|
|
25
25
|
export const DAEMON_PID_FILE = join(BM2_HOME, "daemon.pid");
|
|
26
|
+
export const DAEMON_OUT_LOG_FILE = join(BM2_HOME, "daemon.out.log");
|
|
27
|
+
export const DAEMON_ERR_LOG_FILE = join(BM2_HOME, "daemon.err.log");
|
|
26
28
|
export const LOG_DIR = join(BM2_HOME, "logs");
|
|
27
29
|
export const PID_DIR = join(BM2_HOME, "pids");
|
|
28
30
|
export const DUMP_FILE = join(BM2_HOME, "dump.json");
|
package/src/daemon.ts
CHANGED
|
@@ -51,6 +51,7 @@ const metricsInterval = setInterval(() => {
|
|
|
51
51
|
}, 2000);
|
|
52
52
|
|
|
53
53
|
async function handleMessage(msg: DaemonMessage): Promise<DaemonResponse> {
|
|
54
|
+
console.log("msg.type===>", msg)
|
|
54
55
|
try {
|
|
55
56
|
switch (msg.type) {
|
|
56
57
|
case "start": {
|
|
@@ -183,51 +184,33 @@ async function handleMessage(msg: DaemonMessage): Promise<DaemonResponse> {
|
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
// Unix socket server
|
|
187
|
-
const server = Bun.serve({
|
|
188
|
-
unix: DAEMON_SOCKET,
|
|
189
|
-
fetch(req, server) {
|
|
190
|
-
if (server.upgrade(req)) return;
|
|
191
|
-
return new Response("bm2 daemon");
|
|
192
|
-
},
|
|
193
|
-
websocket: {
|
|
194
|
-
async message(ws: ServerWebSocket<unknown>, message) {
|
|
195
|
-
try {
|
|
196
|
-
const msg: DaemonMessage = JSON.parse(String(message));
|
|
197
|
-
const response = await handleMessage(msg);
|
|
198
|
-
ws.send(JSON.stringify(response));
|
|
199
|
-
} catch (err: any) {
|
|
200
|
-
ws.send(JSON.stringify({ type: "error", error: err.message, success: false }));
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
open(ws) {},
|
|
204
|
-
close(ws) {},
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// Signal handlers
|
|
209
|
-
const shutdown = async () => {
|
|
210
|
-
console.log("\n[bm2] Shutting down daemon...");
|
|
211
|
-
await pm.stopAll();
|
|
212
|
-
dashboard.stop();
|
|
213
|
-
clearInterval(metricsInterval);
|
|
214
|
-
try { unlinkSync(DAEMON_SOCKET); } catch {}
|
|
215
|
-
try { unlinkSync(DAEMON_PID_FILE); } catch {}
|
|
216
|
-
process.exit(0);
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
process.on("SIGTERM", shutdown);
|
|
220
|
-
process.on("SIGINT", shutdown);
|
|
221
|
-
process.on("SIGHUP", shutdown);
|
|
222
187
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
188
|
+
const server = Bun.serve({
|
|
189
|
+
unix: DAEMON_SOCKET,
|
|
190
|
+
async fetch(req) {
|
|
191
|
+
|
|
192
|
+
if (req.method !== "POST") {
|
|
193
|
+
return Response.json(
|
|
194
|
+
{ type: "error", error: "Method Not Allowed", success: false },
|
|
195
|
+
{ status: 405 }
|
|
196
|
+
)
|
|
197
|
+
}
|
|
227
198
|
|
|
228
|
-
|
|
229
|
-
|
|
199
|
+
try {
|
|
200
|
+
|
|
201
|
+
const msg: DaemonMessage = await req.json() as DaemonMessage;
|
|
202
|
+
|
|
203
|
+
const response = await handleMessage(msg);
|
|
204
|
+
|
|
205
|
+
return Response.json(response);
|
|
206
|
+
|
|
207
|
+
} catch (err: any) {
|
|
208
|
+
return Response.json(
|
|
209
|
+
{ type: "error", error: err.message, success: false },
|
|
210
|
+
{ status: 500 }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
230
214
|
});
|
|
231
215
|
|
|
232
|
-
console.log(`
|
|
233
|
-
console.log(`[bm2] Socket: ${DAEMON_SOCKET}`);
|
|
216
|
+
console.log(`Listening on ${server.url}`);
|
package/src/hello.js
ADDED
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
BM2_HOME,
|
|
26
26
|
DASHBOARD_PORT,
|
|
27
27
|
METRICS_PORT,
|
|
28
|
+
DAEMON_OUT_LOG_FILE,
|
|
28
29
|
} from "./constants";
|
|
29
30
|
import { ensureDirs, formatBytes, formatUptime, colorize, padRight } from "./utils";
|
|
30
31
|
import { DeployManager } from "./deploy";
|
|
@@ -65,9 +66,9 @@ async function startDaemon(): Promise<void> {
|
|
|
65
66
|
const bunPath = Bun.which("bun") || "bun";
|
|
66
67
|
|
|
67
68
|
const child = Bun.spawn([bunPath, "run", daemonScript], {
|
|
68
|
-
stdout:
|
|
69
|
-
stderr:
|
|
70
|
-
stdin:
|
|
69
|
+
stdout: Bun.file(DAEMON_OUT_LOG_FILE),
|
|
70
|
+
stderr: Bun.file(DAEMON_OUT_LOG_FILE),
|
|
71
|
+
stdin: "ignore",
|
|
71
72
|
});
|
|
72
73
|
|
|
73
74
|
// Detach so the daemon outlives the CLI
|
|
@@ -83,46 +84,25 @@ async function startDaemon(): Promise<void> {
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
async function sendToDaemon(msg: DaemonMessage): Promise<DaemonResponse> {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (res.id === id || !res.id) {
|
|
106
|
-
clearTimeout(timeout);
|
|
107
|
-
ws.close();
|
|
108
|
-
resolvePromise(res);
|
|
109
|
-
}
|
|
110
|
-
} catch (err: any) {
|
|
111
|
-
clearTimeout(timeout);
|
|
112
|
-
ws.close();
|
|
113
|
-
reject(err);
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
ws.onerror = (err) => {
|
|
118
|
-
clearTimeout(timeout);
|
|
119
|
-
reject(new Error(`WebSocket error: ${err}`));
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
ws.onclose = () => {
|
|
123
|
-
clearTimeout(timeout);
|
|
124
|
-
};
|
|
125
|
-
});
|
|
87
|
+
|
|
88
|
+
await startDaemon();
|
|
89
|
+
|
|
90
|
+
const res = await fetch("http://localhost/command", {
|
|
91
|
+
unix: DAEMON_SOCKET,
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify(msg),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
throw new Error(`Daemon error: ${res.status}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const resJson: DaemonResponse = await res.json() as DaemonResponse;
|
|
104
|
+
|
|
105
|
+
return resJson;
|
|
126
106
|
}
|
|
127
107
|
|
|
128
108
|
// ---------------------------------------------------------------------------
|
package/src/process-manager.ts
CHANGED
|
@@ -57,49 +57,49 @@
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
async start(options: StartOptions): Promise<ProcessState[]> {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
60
|
+
|
|
61
|
+
const resolvedInstances = this.clusterManager.resolveInstances(options.instances);
|
|
62
|
+
const isCluster = options.execMode === "cluster" || resolvedInstances > 1;
|
|
63
|
+
const states: ProcessState[] = [];
|
|
64
|
+
|
|
65
|
+
if (isCluster) {
|
|
66
|
+
// In cluster mode, each instance is a separate container
|
|
67
|
+
for (let i = 0; i < resolvedInstances; i++) {
|
|
68
|
+
|
|
69
|
+
const id = this.nextId++;
|
|
70
|
+
const baseName = options.name || options.script.split("/").pop()?.replace(/\.\w+$/, "") || `app-${id}`;
|
|
71
|
+
const name = resolvedInstances > 1 ? `${baseName}-${i}` : baseName;
|
|
72
|
+
|
|
73
|
+
const config = this.buildConfig(id, name, options, resolvedInstances, i);
|
|
74
|
+
|
|
75
|
+
const container = new ProcessContainer(
|
|
76
|
+
id, config, this.logManager, this.clusterManager,
|
|
77
|
+
this.healthChecker, this.cronManager
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
this.processes.set(id, container);
|
|
81
|
+
await container.start();
|
|
82
|
+
states.push(container.getState());
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
const id = this.nextId++;
|
|
86
|
+
const name =
|
|
87
|
+
options.name ||
|
|
88
|
+
options.script.split("/").pop()?.replace(/\.\w+$/, "") ||
|
|
89
|
+
`app-${id}`;
|
|
90
|
+
|
|
91
|
+
const config = this.buildConfig(id, name, options, 1, 0);
|
|
92
|
+
const container = new ProcessContainer(
|
|
93
|
+
id, config, this.logManager, this.clusterManager,
|
|
94
|
+
this.healthChecker, this.cronManager
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
this.processes.set(id, container);
|
|
98
|
+
await container.start();
|
|
99
|
+
states.push(container.getState());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return states;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
private buildConfig(
|