bm2 1.0.0 → 1.0.3

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.0",
3
+ "version": "1.0.3",
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/hello.js ADDED
@@ -0,0 +1,2 @@
1
+
2
+ console.log("Hello ===>")
package/src/index.ts CHANGED
@@ -1,15 +1,587 @@
1
+ #!/usr/bin/env bun
1
2
  /**
2
3
  * BM2 — Bun Process Manager
3
4
  * A production-grade process manager for Bun.
4
- *
5
- * Features:
6
- * - Fork & cluster execution modes
7
- * - Auto-restart & crash recovery
8
- * - Health checks & monitoring
9
- * - Log management & rotation
10
- * - Deployment support
11
- *
12
- * https://github.com/your-org/bm2
13
- * License: GPL-3.0-only
14
- * Author: Zak <zak@maxxpainn.com>
15
5
  */
6
+
7
+ import { existsSync, readFileSync, unlinkSync, openSync } from "fs";
8
+ import { resolve, join, extname } from "path";
9
+ import { createConnection } from "net";
10
+ import {
11
+ APP_NAME,
12
+ VERSION,
13
+ DAEMON_SOCKET,
14
+ DAEMON_PID_FILE,
15
+ BM2_HOME,
16
+ DASHBOARD_PORT,
17
+ METRICS_PORT,
18
+ } from "./constants";
19
+ import { ensureDirs, formatBytes, formatUptime, colorize, padRight } from "./utils";
20
+ import { DeployManager } from "./deploy";
21
+ import { StartupManager } from "./startup-manager";
22
+ import { EnvManager } from "./env-manager";
23
+ import type {
24
+ StartOptions,
25
+ EcosystemConfig,
26
+ ProcessState,
27
+ } from "./types";
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Ensure directory structure exists
31
+ // ---------------------------------------------------------------------------
32
+ ensureDirs();
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Helpers
36
+ // ---------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Reads the last N lines from the daemon error log to help debug crashes.
40
+ */
41
+ function getDaemonErrorLog(linesToRead = 10): string {
42
+ const logPath = join(BM2_HOME, "daemon.err.log");
43
+ if (!existsSync(logPath)) return "";
44
+ try {
45
+ const content = readFileSync(logPath, "utf-8").trim();
46
+ if (!content) return "";
47
+ const lines = content.split("\n");
48
+ return lines.slice(-linesToRead).join("\n");
49
+ } catch {
50
+ return "";
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Check if the daemon process is actually running by reading the PID file
56
+ * and sending signal 0 to verify the process exists.
57
+ */
58
+ function isDaemonRunning(): boolean {
59
+ if (!existsSync(DAEMON_PID_FILE)) return false;
60
+ try {
61
+ const pid = parseInt(readFileSync(DAEMON_PID_FILE, "utf-8").trim());
62
+ if (isNaN(pid)) return false;
63
+ process.kill(pid, 0); // signal 0 — just check existence
64
+ return true;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Remove stale socket and PID files left behind by a crashed daemon.
72
+ */
73
+ function cleanupStaleFiles(): void {
74
+ try {
75
+ if (existsSync(DAEMON_SOCKET)) unlinkSync(DAEMON_SOCKET);
76
+ } catch { /* ignore */ }
77
+ try {
78
+ if (existsSync(DAEMON_PID_FILE)) unlinkSync(DAEMON_PID_FILE);
79
+ } catch { /* ignore */ }
80
+ }
81
+
82
+ /**
83
+ * Start the daemon process if it is not already running.
84
+ * Redirects daemon stdout/stderr to ~/.bm2/daemon.{out|err}.log
85
+ */
86
+ async function startDaemon(): Promise<void> {
87
+ if (isDaemonRunning()) {
88
+ // Daemon is alive. Check for socket.
89
+ if (existsSync(DAEMON_SOCKET)) return;
90
+
91
+ // Socket missing but process alive — wait briefly
92
+ for (let i = 0; i < 20; i++) {
93
+ if (existsSync(DAEMON_SOCKET)) return;
94
+ await Bun.sleep(100);
95
+ }
96
+
97
+ // Still no socket — kill stale process
98
+ try {
99
+ const pid = parseInt(readFileSync(DAEMON_PID_FILE, "utf-8").trim());
100
+ process.kill(pid, "SIGTERM");
101
+ } catch { /* ignore */ }
102
+ await Bun.sleep(500);
103
+ }
104
+
105
+ cleanupStaleFiles();
106
+
107
+ const daemonScript = join(import.meta.dir, "daemon.ts");
108
+ const bunPath = Bun.which("bun") || "bun";
109
+
110
+ if (!existsSync(daemonScript)) {
111
+ throw new Error(`Daemon script not found at: ${daemonScript}`);
112
+ }
113
+
114
+ // Prepare log files for the daemon so we can see why it crashes
115
+ const outLog = join(BM2_HOME, "daemon.out.log");
116
+ const errLog = join(BM2_HOME, "daemon.err.log");
117
+
118
+ // Open file descriptors (append mode)
119
+ const outFd = openSync(outLog, "a");
120
+ const errFd = openSync(errLog, "a");
121
+
122
+ // Spawn detached
123
+ const child = Bun.spawn([bunPath, "run", daemonScript], {
124
+ stdin: "ignore",
125
+ stdout: outFd,
126
+ stderr: errFd,
127
+ detached: true,
128
+ });
129
+
130
+ child.unref();
131
+
132
+ // Wait for socket
133
+ const maxRetries = 50; // 5 seconds
134
+ for (let i = 0; i < maxRetries; i++) {
135
+ if (existsSync(DAEMON_SOCKET)) return;
136
+ await Bun.sleep(100);
137
+ }
138
+
139
+ // If we timed out, read the error log to show the user
140
+ const recentErrors = getDaemonErrorLog();
141
+ throw new Error(
142
+ "Daemon failed to start (socket not found).\n" +
143
+ (recentErrors
144
+ ? `\n--- Daemon Stderr (last 10 lines) ---\n${recentErrors}\n-------------------------------------`
145
+ : `Check logs at: ${errLog}`)
146
+ );
147
+ }
148
+
149
+ /**
150
+ * Send a JSON message to the daemon and wait for a response.
151
+ */
152
+ async function sendToDaemon(message: object): Promise<any> {
153
+ await startDaemon();
154
+
155
+ return new Promise((resolve, reject) => {
156
+ let settled = false;
157
+
158
+ function settle(fn: typeof resolve | typeof reject, value: any) {
159
+ if (settled) return;
160
+ settled = true;
161
+ clearTimeout(timeout);
162
+ fn(value);
163
+ }
164
+
165
+ // Timeout (10s)
166
+ const timeout = setTimeout(() => {
167
+ settle(reject, new Error("Daemon response timed out"));
168
+ try { socket.destroy(); } catch { /* ignore */ }
169
+ }, 10_000);
170
+
171
+ const socket = createConnection(DAEMON_SOCKET, () => {
172
+ socket.write(JSON.stringify(message) + "\n");
173
+ });
174
+
175
+ let data = "";
176
+
177
+ socket.on("data", (chunk) => {
178
+ data += chunk.toString();
179
+ const lines = data.split("\n").filter((l) => l.trim());
180
+ for (const line of lines) {
181
+ try {
182
+ const parsed = JSON.parse(line);
183
+ socket.end();
184
+ settle(resolve, parsed);
185
+ return;
186
+ } catch {
187
+ // Partial JSON, wait for more
188
+ }
189
+ }
190
+ });
191
+
192
+ socket.on("close", () => {
193
+ if (!settled) {
194
+ // Try to parse partial data
195
+ if (data.trim()) {
196
+ try {
197
+ const parsed = JSON.parse(data);
198
+ settle(resolve, parsed);
199
+ return;
200
+ } catch { /* ignore */ }
201
+ }
202
+
203
+ // Check if daemon logged an error before dying
204
+ const recentErrors = getDaemonErrorLog(5);
205
+ const errorDetails = recentErrors
206
+ ? `\n\nDaemon Error Log:\n${colorize(recentErrors, "red")}`
207
+ : `\nCheck logs at: ${join(BM2_HOME, "daemon.err.log")}`;
208
+
209
+ settle(
210
+ reject,
211
+ new Error(
212
+ "Daemon connection closed unexpectedly. The daemon likely crashed while processing your command." +
213
+ errorDetails
214
+ )
215
+ );
216
+ }
217
+ });
218
+
219
+ socket.on("error", (err: NodeJS.ErrnoException) => {
220
+ if (err.code === "ECONNREFUSED" || err.code === "ENOENT") {
221
+ cleanupStaleFiles();
222
+ settle(
223
+ reject,
224
+ new Error("Daemon is unreachable (stale socket). Please run the command again to restart it.")
225
+ );
226
+ } else {
227
+ settle(reject, err);
228
+ }
229
+ });
230
+ });
231
+ }
232
+
233
+ // ---------------------------------------------------------------------------
234
+ // Table Rendering & Utilities
235
+ // ---------------------------------------------------------------------------
236
+
237
+ function statusColor(status: string): string {
238
+ switch (status) {
239
+ case "online": return "green";
240
+ case "stopped": return "gray";
241
+ case "errored": return "red";
242
+ case "launching":
243
+ case "waiting-restart": return "yellow";
244
+ case "stopping": return "magenta";
245
+ default: return "white";
246
+ }
247
+ }
248
+
249
+ function printProcessTable(processes: ProcessState[]) {
250
+ if (!processes || processes.length === 0) {
251
+ console.log(colorize("No processes running", "dim"));
252
+ return;
253
+ }
254
+
255
+ const header = [
256
+ padRight("id", 4),
257
+ padRight("name", 20),
258
+ padRight("namespace", 12),
259
+ padRight("ver", 8),
260
+ padRight("mode", 8),
261
+ padRight("pid", 8),
262
+ padRight("uptime", 10),
263
+ padRight("↺", 4),
264
+ padRight("status", 16),
265
+ padRight("cpu", 8),
266
+ padRight("mem", 10),
267
+ ].join(" ");
268
+
269
+ console.log(colorize(header, "dim"));
270
+ console.log(colorize("─".repeat(header.length), "dim"));
271
+
272
+ for (const p of processes) {
273
+ const uptime = p.status === "online" ? formatUptime(Date.now() - p.pm2_env.pm_uptime) : "0s";
274
+ const row = [
275
+ padRight(String(p.pm_id), 4),
276
+ padRight(p.name, 20),
277
+ padRight(p.namespace || "default", 12),
278
+ padRight(p.pm2_env.version || "N/A", 8),
279
+ padRight(p.pm2_env.execMode, 8),
280
+ padRight(p.pid ? String(p.pid) : "N/A", 8),
281
+ padRight(uptime, 10),
282
+ padRight(String(p.pm2_env.restart_time), 4),
283
+ padRight(p.status, 16),
284
+ padRight(p.monit.cpu.toFixed(1) + "%", 8),
285
+ padRight(formatBytes(p.monit.memory), 10),
286
+ ];
287
+ console.log(row.join(" ").replace(p.status, colorize(p.status, statusColor(p.status))));
288
+ }
289
+ }
290
+
291
+ async function loadEcosystemConfig(filePath: string): Promise<EcosystemConfig> {
292
+ const abs = resolve(filePath);
293
+ if (!existsSync(abs)) throw new Error(`Ecosystem file not found: ${abs}`);
294
+ const ext = extname(abs);
295
+ if (ext === ".json") return await Bun.file(abs).json();
296
+ const mod = await import(abs);
297
+ return mod.default || mod;
298
+ }
299
+
300
+ function parseStartFlags(args: string[], scriptOrConfig: string): StartOptions {
301
+ const opts: StartOptions = { script: scriptOrConfig };
302
+ let i = 0;
303
+ const positionalArgs: string[] = [];
304
+
305
+ while (i < args.length) {
306
+ const arg = args[i]!;
307
+ switch (arg) {
308
+ case "--name": case "-n": opts.name = args[++i]; break;
309
+ case "--instances": case "-i": opts.instances = parseInt(args[++i]!) || 1; break;
310
+ case "--cwd": opts.cwd = args[++i]; break;
311
+ case "--interpreter": opts.interpreter = args[++i]; break;
312
+ case "--interpreter-args": opts.interpreterArgs = args[++i]!.split(" "); break;
313
+ case "--node-args": opts.nodeArgs = args[++i]!.split(" "); break;
314
+ case "--watch": case "-w": opts.watch = true; break;
315
+ case "--watch-path":
316
+ if (!Array.isArray(opts.watch)) opts.watch = [];
317
+ (opts.watch as string[]).push(args[++i]!);
318
+ break;
319
+ case "--ignore-watch": opts.ignoreWatch = args[++i]!.split(","); break;
320
+ case "--exec-mode": case "-x": opts.execMode = args[++i] as "fork" | "cluster"; break;
321
+ case "--max-memory-restart": opts.maxMemoryRestart = args[++i]; break;
322
+ case "--max-restarts": opts.maxRestarts = parseInt(args[++i]!); break;
323
+ case "--min-uptime": opts.minUptime = parseInt(args[++i]!); break;
324
+ case "--kill-timeout": opts.killTimeout = parseInt(args[++i]!); break;
325
+ case "--restart-delay": opts.restartDelay = parseInt(args[++i]!); break;
326
+ case "--cron": case "--cron-restart": opts.cron = args[++i]; break;
327
+ case "--no-autorestart": opts.autorestart = false; break;
328
+ case "--env": {
329
+ const p = args[++i]!;
330
+ const idx = p.indexOf("=");
331
+ if (idx !== -1) {
332
+ if (!opts.env) opts.env = {};
333
+ opts.env[p.substring(0, idx)] = p.substring(idx + 1);
334
+ }
335
+ break;
336
+ }
337
+ case "--log": case "--output": case "-o": opts.outFile = args[++i]; break;
338
+ case "--error": case "-e": opts.errorFile = args[++i]; break;
339
+ case "--merge-logs": opts.mergeLogs = true; break;
340
+ case "--log-date-format": opts.logDateFormat = args[++i]; break;
341
+ case "--log-max-size": opts.logMaxSize = args[++i]; break;
342
+ case "--log-retain": opts.logRetain = parseInt(args[++i]!); break;
343
+ case "--log-compress": opts.logCompress = true; break;
344
+ case "--port": case "-p": opts.port = parseInt(args[++i]!); break;
345
+ case "--namespace": opts.namespace = args[++i]; break;
346
+ case "--": positionalArgs.push(...args.slice(i + 1)); i = args.length; break;
347
+ default: if (!arg.startsWith("-")) positionalArgs.push(arg); break;
348
+ }
349
+ i++;
350
+ }
351
+ if (positionalArgs.length > 0) opts.args = positionalArgs;
352
+ return opts;
353
+ }
354
+
355
+ // ---------------------------------------------------------------------------
356
+ // Commands
357
+ // ---------------------------------------------------------------------------
358
+
359
+ async function cmdStart(args: string[]) {
360
+ const scriptOrConfig = args[0];
361
+ if (!scriptOrConfig) {
362
+ console.error(colorize("Usage: bm2 start <script|config> [options]", "red"));
363
+ process.exit(1);
364
+ }
365
+
366
+ // Check if file exists before sending to daemon
367
+ if (!existsSync(scriptOrConfig)) {
368
+ console.error(colorize(`Error: Script or config not found: ${scriptOrConfig}`, "red"));
369
+ process.exit(1);
370
+ }
371
+
372
+ const ext = extname(scriptOrConfig);
373
+ if (ext === ".json" || scriptOrConfig.includes("ecosystem") || scriptOrConfig.includes("bm2.config")) {
374
+ const config = await loadEcosystemConfig(scriptOrConfig);
375
+ const res = await sendToDaemon({ type: "ecosystem", data: config });
376
+ if (!res.success) {
377
+ console.error(colorize(`Error: ${res.error}`, "red"));
378
+ process.exit(1);
379
+ }
380
+ printProcessTable(res.data);
381
+ return;
382
+ }
383
+
384
+ const opts = parseStartFlags(args.slice(1), resolve(scriptOrConfig));
385
+ opts.script = resolve(scriptOrConfig);
386
+
387
+ const res = await sendToDaemon({ type: "start", data: opts });
388
+ if (!res.success) {
389
+ console.error(colorize(`Error: ${res.error}`, "red"));
390
+ process.exit(1);
391
+ }
392
+ printProcessTable(res.data);
393
+ }
394
+
395
+ async function cmdStop(args: string[]) {
396
+ const target = args[0] || "all";
397
+ const type = target === "all" ? "stopAll" : "stop";
398
+ const res = await sendToDaemon({ type, data: target === "all" ? undefined : { target } });
399
+ if (!res.success) {
400
+ console.error(colorize(`Error: ${res.error}`, "red"));
401
+ process.exit(1);
402
+ }
403
+ printProcessTable(res.data);
404
+ }
405
+
406
+ async function cmdRestart(args: string[]) {
407
+ const target = args[0] || "all";
408
+ const type = target === "all" ? "restartAll" : "restart";
409
+ const res = await sendToDaemon({ type, data: target === "all" ? undefined : { target } });
410
+ if (!res.success) {
411
+ console.error(colorize(`Error: ${res.error}`, "red"));
412
+ process.exit(1);
413
+ }
414
+ printProcessTable(res.data);
415
+ }
416
+
417
+ async function cmdReload(args: string[]) {
418
+ const target = args[0] || "all";
419
+ const type = target === "all" ? "reloadAll" : "reload";
420
+ const res = await sendToDaemon({ type, data: target === "all" ? undefined : { target } });
421
+ if (!res.success) {
422
+ console.error(colorize(`Error: ${res.error}`, "red"));
423
+ process.exit(1);
424
+ }
425
+ printProcessTable(res.data);
426
+ }
427
+
428
+ async function cmdDelete(args: string[]) {
429
+ const target = args[0] || "all";
430
+ const type = target === "all" ? "deleteAll" : "delete";
431
+ const res = await sendToDaemon({ type, data: target === "all" ? undefined : { target } });
432
+ if (!res.success) {
433
+ console.error(colorize(`Error: ${res.error}`, "red"));
434
+ process.exit(1);
435
+ }
436
+ console.log(colorize("✓ Deleted", "green"));
437
+ printProcessTable(res.data);
438
+ }
439
+
440
+ async function cmdList() {
441
+ const res = await sendToDaemon({ type: "list" });
442
+ if (!res.success) {
443
+ console.error(colorize(`Error: ${res.error}`, "red"));
444
+ process.exit(1);
445
+ }
446
+ printProcessTable(res.data);
447
+ }
448
+
449
+ async function cmdDescribe(args: string[]) {
450
+ const target = args[0];
451
+ if (!target) {
452
+ console.error(colorize("Usage: bm2 describe <id|name>", "red"));
453
+ process.exit(1);
454
+ }
455
+ const res = await sendToDaemon({ type: "describe", data: { target } });
456
+ if (!res.success) {
457
+ console.error(colorize(`Error: ${res.error}`, "red"));
458
+ process.exit(1);
459
+ }
460
+ const processes: ProcessState[] = res.data;
461
+ for (const p of processes) {
462
+ console.log(colorize(`\n─── ${p.name} (id: ${p.pm_id}) ───`, "bold"));
463
+ console.log(` Status : ${colorize(p.status, statusColor(p.status))}`);
464
+ console.log(` Script : ${p.pm2_env.script}`);
465
+ console.log(` Log (out) : ${p.pm2_env.pm_out_log_path}`);
466
+ console.log(` Log (err) : ${p.pm2_env.pm_err_log_path}`);
467
+ console.log(` Restarts : ${p.pm2_env.restart_time}`);
468
+ console.log(` Memory : ${formatBytes(p.monit.memory)}`);
469
+ console.log(` CPU : ${p.monit.cpu.toFixed(1)}%`);
470
+ }
471
+ }
472
+
473
+ async function cmdLogs(args: string[]) {
474
+ const target = args[0] || "all";
475
+ let lines = 20;
476
+ const linesIdx = args.indexOf("--lines");
477
+ if (linesIdx !== -1 && args[linesIdx + 1]) lines = parseInt(args[linesIdx + 1]!);
478
+
479
+ const res = await sendToDaemon({ type: "logs", data: { target, lines } });
480
+ if (!res.success) {
481
+ console.error(colorize(`Error: ${res.error}`, "red"));
482
+ process.exit(1);
483
+ }
484
+ for (const log of res.data) {
485
+ console.log(colorize(`\n─── ${log.name} (id: ${log.id}) ───`, "bold"));
486
+ if (log.out) console.log(log.out);
487
+ if (log.err) console.log(colorize(log.err, "red"));
488
+ }
489
+ }
490
+
491
+ async function cmdFlush(args: string[]) {
492
+ const target = args[0];
493
+ const res = await sendToDaemon({ type: "flush", data: target ? { target } : undefined });
494
+ if (!res.success) {
495
+ console.error(colorize(`Error: ${res.error}`, "red"));
496
+ process.exit(1);
497
+ }
498
+ console.log(colorize("✓ Logs flushed", "green"));
499
+ }
500
+
501
+ async function cmdScale(args: string[]) {
502
+ const target = args[0];
503
+ const count = parseInt(args[1]!);
504
+ if (!target || isNaN(count)) {
505
+ console.error(colorize("Usage: bm2 scale <name|id> <count>", "red"));
506
+ process.exit(1);
507
+ }
508
+ const res = await sendToDaemon({ type: "scale", data: { target, count } });
509
+ if (!res.success) {
510
+ console.error(colorize(`Error: ${res.error}`, "red"));
511
+ process.exit(1);
512
+ }
513
+ printProcessTable(res.data);
514
+ }
515
+
516
+ async function cmdSave() {
517
+ const res = await sendToDaemon({ type: "save" });
518
+ if (!res.success) {
519
+ console.error(colorize(`Error: ${res.error}`, "red"));
520
+ process.exit(1);
521
+ }
522
+ console.log(colorize("✓ Process list saved", "green"));
523
+ }
524
+
525
+ async function cmdResurrect() {
526
+ const res = await sendToDaemon({ type: "resurrect" });
527
+ if (!res.success) {
528
+ console.error(colorize(`Error: ${res.error}`, "red"));
529
+ process.exit(1);
530
+ }
531
+ printProcessTable(res.data);
532
+ }
533
+
534
+ async function cmdKill() {
535
+ try {
536
+ await sendToDaemon({ type: "kill" });
537
+ } catch { /* ignore */ }
538
+ await Bun.sleep(500);
539
+ cleanupStaleFiles();
540
+ console.log(colorize("✓ Daemon killed", "green"));
541
+ }
542
+
543
+ async function cmdMonit() {
544
+ const res = await sendToDaemon({ type: "metrics" });
545
+ if (!res.success) {
546
+ console.error(colorize(`Error: ${res.error}`, "red"));
547
+ process.exit(1);
548
+ }
549
+ const snapshot = res.data;
550
+ console.log(colorize("\n⚡ BM2 Monitor\n", "bold"));
551
+ console.log(`System: CPU ${snapshot.system.cpuCount} cores | Load ${snapshot.system.loadAvg[0]?.toFixed(2)}`);
552
+ for (const p of snapshot.processes) {
553
+ console.log(`${padRight(p.name, 20)} ${colorize(p.status, statusColor(p.status))} CPU: ${p.cpu.toFixed(1)}% Mem: ${formatBytes(p.memory)}`);
554
+ }
555
+ }
556
+
557
+ // ---------------------------------------------------------------------------
558
+ // Main Dispatch
559
+ // ---------------------------------------------------------------------------
560
+
561
+ const args = process.argv.slice(2);
562
+ const command = args[0];
563
+ const commandArgs = args.slice(1);
564
+
565
+ switch (command) {
566
+ case "start": await cmdStart(commandArgs); break;
567
+ case "stop": await cmdStop(commandArgs); break;
568
+ case "restart": await cmdRestart(commandArgs); break;
569
+ case "reload": await cmdReload(commandArgs); break;
570
+ case "delete": case "rm": await cmdDelete(commandArgs); break;
571
+ case "scale": await cmdScale(commandArgs); break;
572
+ case "list": case "ls": await cmdList(); break;
573
+ case "describe": await cmdDescribe(commandArgs); break;
574
+ case "logs": await cmdLogs(commandArgs); break;
575
+ case "flush": await cmdFlush(commandArgs); break;
576
+ case "monit": await cmdMonit(); break;
577
+ case "save": await cmdSave(); break;
578
+ case "resurrect": await cmdResurrect(); break;
579
+ case "kill": await cmdKill(); break;
580
+ case "help":
581
+ case undefined:
582
+ console.log(`BM2 v${VERSION} - Usage: bm2 <command> [options]`);
583
+ break;
584
+ default:
585
+ console.error(colorize(`Unknown command: ${command}`, "red"));
586
+ process.exit(1);
587
+ }
File without changes