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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bm2",
3
- "version": "1.0.1",
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
- // Handle uncaught errors to keep daemon alive
224
- process.on("uncaughtException", (err) => {
225
- console.error("[bm2] Uncaught exception:", err);
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
- process.on("unhandledRejection", (err) => {
229
- console.error("[bm2] Unhandled rejection:", err);
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(`[bm2] Daemon running (PID: ${process.pid})`);
233
- console.log(`[bm2] Socket: ${DAEMON_SOCKET}`);
216
+ console.log(`Listening on ${server.url}`);
package/src/hello.js ADDED
@@ -0,0 +1,2 @@
1
+
2
+ console.log("Hello ===>")
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: "ignore",
69
- stderr: "ignore",
70
- stdin: "ignore",
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
- await startDaemon();
87
-
88
- return new Promise((resolvePromise, reject) => {
89
- const id = crypto.randomUUID();
90
- msg.id = id;
91
-
92
- const timeout = setTimeout(() => {
93
- reject(new Error("Daemon response timed out"));
94
- }, 30_000);
95
-
96
- const ws = new WebSocket(`ws+unix://${DAEMON_SOCKET}`);
97
-
98
- ws.onopen = () => {
99
- ws.send(JSON.stringify(msg));
100
- };
101
-
102
- ws.onmessage = (event) => {
103
- try {
104
- const res: DaemonResponse = JSON.parse(String(event.data));
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
  // ---------------------------------------------------------------------------
@@ -57,49 +57,49 @@
57
57
  }
58
58
 
59
59
  async start(options: StartOptions): Promise<ProcessState[]> {
60
- const resolvedInstances = this.clusterManager.resolveInstances(options.instances);
61
- const isCluster = options.execMode === "cluster" || resolvedInstances > 1;
62
- const states: ProcessState[] = [];
63
-
64
- if (isCluster) {
65
- // In cluster mode, each instance is a separate container
66
- for (let i = 0; i < resolvedInstances; i++) {
67
- const id = this.nextId++;
68
- const baseName =
69
- options.name ||
70
- options.script.split("/").pop()?.replace(/\.\w+$/, "") ||
71
- `app-${id}`;
72
- const name = resolvedInstances > 1 ? `${baseName}-${i}` : baseName;
73
-
74
- const config = this.buildConfig(id, name, options, resolvedInstances, i);
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;
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(