bm2 1.0.33 → 1.0.34

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 CHANGED
@@ -451,43 +451,66 @@ bm2 reset all
451
451
 
452
452
  Cluster mode spawns multiple instances of your application, each running in its own process. This is ideal for CPU-bound workloads and for taking full advantage of multi-core servers.
453
453
 
454
- ```
454
+ ```bash
455
455
  bm2 start server.ts --name api --instances max
456
- ```
457
456
 
458
457
  ```
458
+
459
+ ```bash
459
460
  bm2 start server.ts --name api --instances 4
460
- ```
461
461
 
462
462
  ```
463
+
464
+ ```bash
463
465
  bm2 start server.ts --name api --instances 4 --port 3000
466
+
464
467
  ```
465
468
 
469
+ #### ⚠️ Current Status & Limitations
470
+
471
+ While `bm2` provides the orchestration for clustering, please note that **Bun’s native cluster implementation is currently limited by the underlying OS:**
472
+
473
+ * **Linux Only:** Port sharing via `reusePort` is only fully supported on **Linux**.
474
+ * **macOS & Windows:** Due to OS-level limitations with `SO_REUSEPORT`, these platforms ignore the `reusePort` option. On these systems, clustering may result in "Address already in use" errors if attempting to bind multiple workers to the same port.
475
+
476
+ `bm2` leverages the native [Bun.serve cluster logic](https://www.google.com/search?q=https://bun.sh/docs/api/http%23cluster) to ensure maximum performance, but it remains subject to the runtime's maturity.
477
+
478
+
479
+ #### Environment Variables
480
+
466
481
  Each cluster worker receives the following environment variables:
467
482
 
468
483
  | Variable | Description |
469
- |---|---|
484
+ | --- | --- |
470
485
  | `BM2_CLUSTER` | Set to `"true"` in cluster mode |
471
486
  | `BM2_WORKER_ID` | Zero-indexed worker ID |
472
487
  | `BM2_INSTANCES` | Total number of instances |
473
488
  | `NODE_APP_INSTANCE` | Same as `BM2_WORKER_ID` (PM2 compatibility) |
474
489
  | `PORT` | `basePort + workerIndex` (if `--port` is specified) |
475
490
 
476
- Example application using cluster-aware port binding:
491
+ ---
477
492
 
478
- ```
493
+ #### Example: Cluster-Aware Port Binding
494
+
495
+ To enable clustering in Bun, you must explicitly set `reusePort: true`. This allows multiple processes to listen on the same port (on supported OSs).
496
+
497
+ ```typescript
479
498
  // server.ts
480
499
  const workerId = parseInt(process.env.BM2_WORKER_ID || "0");
481
500
  const port = parseInt(process.env.PORT || "3000");
482
501
 
483
502
  Bun.serve({
484
503
  port,
504
+ // Share the same port across multiple processes
505
+ // This is the important part!
506
+ reusePort: true,
485
507
  fetch(req) {
486
508
  return new Response(`Hello from worker ${workerId} on port ${port}`);
487
509
  },
488
510
  });
489
511
 
490
512
  console.log(`Worker ${workerId} listening on :${port}`);
513
+
491
514
  ```
492
515
 
493
516
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bm2",
3
- "version": "1.0.33",
3
+ "version": "1.0.34",
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/api.ts",
6
6
  "module": "src/api.ts",
package/src/daemon.ts CHANGED
@@ -35,6 +35,11 @@ const pm = new ProcessManager();
35
35
  const dashboard = new Dashboard(pm);
36
36
  const moduleManager = new ModuleManager(pm);
37
37
 
38
+ const args = process.argv.slice(2);
39
+
40
+ // Checks if '--debug' exists anywhere in the arguments
41
+ const debugMode = args.includes('--debug');
42
+
38
43
  // Clean up existing socket
39
44
  if (existsSync(DAEMON_SOCKET)) {
40
45
  try { unlinkSync(DAEMON_SOCKET); } catch {}
@@ -223,8 +228,13 @@ async function handleMessage(msg: DaemonMessage): Promise<DaemonResponse> {
223
228
  default:
224
229
  return { type: "error", error: `Unknown command: ${msg.type}`, success: false, id: msg.id };
225
230
  }
226
- } catch (err: any) {
227
- return { type: "error", error: err.message, success: false, id: msg.id };
231
+ } catch (err: Error | any) {
232
+ let error = err.message;
233
+ if (debugMode) {
234
+ error = `Message: ${err.message}\nStack: ${err.stack}`
235
+ console.error(err, err.stack)
236
+ }
237
+ return { type: "error", error, success: false, id: msg.id };
228
238
  }
229
239
  }
230
240
 
package/src/index.ts CHANGED
@@ -41,6 +41,7 @@ import type {
41
41
  } from "./types";
42
42
  import { statusColor } from "./colors";
43
43
  import { liveWatchProcess, printProcessTable } from "./process-table";
44
+ import { exists } from "fs/promises";
44
45
 
45
46
  // ---------------------------------------------------------------------------
46
47
  // Ensure directory structure exists
@@ -143,8 +144,10 @@ async function sendToDaemon(msg: DaemonMessage): Promise<DaemonResponse> {
143
144
  async function loadEcosystemConfig(filePath: string): Promise<EcosystemConfig> {
144
145
 
145
146
  const abs = resolve(filePath);
147
+
148
+ const file = Bun.file(abs);
146
149
 
147
- if (!existsSync(abs)) {
150
+ if (!(await file.exists())) {
148
151
  throw new Error(`Ecosystem file not found: ${abs}`);
149
152
  }
150
153
 
@@ -153,7 +156,7 @@ async function loadEcosystemConfig(filePath: string): Promise<EcosystemConfig> {
153
156
  let config;
154
157
 
155
158
  if (ext === ".json") {
156
- config = (await Bun.file(abs).json()) as EcosystemConfig;
159
+ config = (await file.json()) as EcosystemConfig;
157
160
  } else {
158
161
  // .ts, .js, .mjs — dynamic import
159
162
  const mod = await import(abs);
@@ -362,8 +365,11 @@ async function cmdStart(args: string[]) {
362
365
  opts.script = resolve(scriptOrConfig);
363
366
 
364
367
  const cwd = path.dirname(opts.script);
365
-
366
- const res = await sendToDaemon({ type: "start", data: { config: opts, cwd } });
368
+
369
+ opts.cwd = cwd;
370
+
371
+ const res = await sendToDaemon({ type: "start", data: opts });
372
+
367
373
  if (!res.success) {
368
374
  console.error(colorize(`Error: ${res.error}`, "red"));
369
375
  process.exit(1);
@@ -697,7 +703,8 @@ async function cmdDeploy(args: string[]) {
697
703
  process.exit(1);
698
704
  }
699
705
 
700
- const {config, cwd } = await loadEcosystemConfig(configFile);
706
+ const config = await loadEcosystemConfig(configFile);
707
+
701
708
  if (!config.deploy || !config.deploy[environment]) {
702
709
  console.error(colorize(`Deploy environment "${environment}" not found in config`, "red"));
703
710
  process.exit(1);
@@ -66,11 +66,12 @@ import path from "path";
66
66
  options.script = path.isAbsolute(options.script)
67
67
  ? options.script
68
68
  : path.join(options.cwd!, options.script);
69
-
69
+
70
+
70
71
  if (!(await Bun.file(options.script).exists())) {
71
72
  throw new Error(`Script not found: ${options.script}`);
72
- }
73
-
73
+ }
74
+
74
75
  if (isCluster) {
75
76
  // In cluster mode, each instance is a separate container
76
77
  for (let i = 0; i < resolvedInstances; i++) {
@@ -94,6 +95,7 @@ import path from "path";
94
95
  await container.start();
95
96
  states.push(container.getState());
96
97
  }
98
+
97
99
  } else {
98
100
  const id = this.nextId++;
99
101
  const name =
@@ -103,8 +105,11 @@ import path from "path";
103
105
 
104
106
  const config = this.buildConfig(id, name, options, 1, 0);
105
107
  const container = new ProcessContainer(
106
- id, config, this.logManager, this.clusterManager,
107
- this.healthChecker, this.cronManager
108
+ id, config,
109
+ this.logManager,
110
+ this.clusterManager,
111
+ this.healthChecker,
112
+ this.cronManager
108
113
  );
109
114
 
110
115
  this.processes.set(id, container);
@@ -113,7 +118,7 @@ import path from "path";
113
118
  }
114
119
 
115
120
  return states;
116
- }
121
+ }
117
122
 
118
123
  private buildConfig(
119
124
  id: number,