bm2 1.0.0 → 1.0.1
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 +1 -1
- package/src/index.ts +1062 -0
- /package/src/{startup.ts → startup-manager.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bm2",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
1
2
|
/**
|
|
2
3
|
* BM2 — Bun Process Manager
|
|
3
4
|
* A production-grade process manager for Bun.
|
|
@@ -13,3 +14,1064 @@
|
|
|
13
14
|
* License: GPL-3.0-only
|
|
14
15
|
* Author: Zak <zak@maxxpainn.com>
|
|
15
16
|
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, readFileSync, unlinkSync } from "fs";
|
|
19
|
+
import { resolve, join, extname } from "path";
|
|
20
|
+
import {
|
|
21
|
+
APP_NAME,
|
|
22
|
+
VERSION,
|
|
23
|
+
DAEMON_SOCKET,
|
|
24
|
+
DAEMON_PID_FILE,
|
|
25
|
+
BM2_HOME,
|
|
26
|
+
DASHBOARD_PORT,
|
|
27
|
+
METRICS_PORT,
|
|
28
|
+
} from "./constants";
|
|
29
|
+
import { ensureDirs, formatBytes, formatUptime, colorize, padRight } from "./utils";
|
|
30
|
+
import { DeployManager } from "./deploy";
|
|
31
|
+
import { StartupManager } from "./startup-manager";
|
|
32
|
+
import { EnvManager } from "./env-manager";
|
|
33
|
+
import type {
|
|
34
|
+
DaemonMessage,
|
|
35
|
+
DaemonResponse,
|
|
36
|
+
StartOptions,
|
|
37
|
+
EcosystemConfig,
|
|
38
|
+
ProcessState,
|
|
39
|
+
} from "./types";
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Ensure directory structure exists
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
ensureDirs();
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Daemon communication helpers
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
function isDaemonRunning(): boolean {
|
|
51
|
+
if (!existsSync(DAEMON_PID_FILE)) return false;
|
|
52
|
+
try {
|
|
53
|
+
const pid = parseInt(readFileSync(DAEMON_PID_FILE, "utf-8").trim());
|
|
54
|
+
process.kill(pid, 0); // signal 0 — just check existence
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function startDaemon(): Promise<void> {
|
|
62
|
+
if (isDaemonRunning()) return;
|
|
63
|
+
|
|
64
|
+
const daemonScript = join(import.meta.dir, "daemon.ts");
|
|
65
|
+
const bunPath = Bun.which("bun") || "bun";
|
|
66
|
+
|
|
67
|
+
const child = Bun.spawn([bunPath, "run", daemonScript], {
|
|
68
|
+
stdout: "ignore",
|
|
69
|
+
stderr: "ignore",
|
|
70
|
+
stdin: "ignore",
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Detach so the daemon outlives the CLI
|
|
74
|
+
child.unref();
|
|
75
|
+
|
|
76
|
+
// Wait for socket to appear
|
|
77
|
+
for (let i = 0; i < 50; i++) {
|
|
78
|
+
if (existsSync(DAEMON_SOCKET)) return;
|
|
79
|
+
await Bun.sleep(100);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
throw new Error("Daemon failed to start (socket not found after 5 s)");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
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
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Table rendering
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
function statusColor(status: string): string {
|
|
133
|
+
switch (status) {
|
|
134
|
+
case "online":
|
|
135
|
+
return "green";
|
|
136
|
+
case "stopped":
|
|
137
|
+
return "gray";
|
|
138
|
+
case "errored":
|
|
139
|
+
return "red";
|
|
140
|
+
case "launching":
|
|
141
|
+
case "waiting-restart":
|
|
142
|
+
return "yellow";
|
|
143
|
+
case "stopping":
|
|
144
|
+
return "magenta";
|
|
145
|
+
default:
|
|
146
|
+
return "white";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function printProcessTable(processes: ProcessState[]) {
|
|
151
|
+
if (!processes || processes.length === 0) {
|
|
152
|
+
console.log(colorize("No processes running", "dim"));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const header = [
|
|
157
|
+
padRight("id", 4),
|
|
158
|
+
padRight("name", 20),
|
|
159
|
+
padRight("namespace", 12),
|
|
160
|
+
padRight("version", 10),
|
|
161
|
+
padRight("mode", 8),
|
|
162
|
+
padRight("pid", 8),
|
|
163
|
+
padRight("uptime", 10),
|
|
164
|
+
padRight("↺", 4),
|
|
165
|
+
padRight("status", 16),
|
|
166
|
+
padRight("cpu", 8),
|
|
167
|
+
padRight("mem", 10),
|
|
168
|
+
].join(" ");
|
|
169
|
+
|
|
170
|
+
console.log(colorize(header, "dim"));
|
|
171
|
+
console.log(colorize("─".repeat(header.length), "dim"));
|
|
172
|
+
|
|
173
|
+
for (const p of processes) {
|
|
174
|
+
const uptime =
|
|
175
|
+
p.status === "online" ? formatUptime(Date.now() - p.pm2_env.pm_uptime) : "0s";
|
|
176
|
+
|
|
177
|
+
const row = [
|
|
178
|
+
padRight(String(p.pm_id), 4),
|
|
179
|
+
padRight(p.name, 20),
|
|
180
|
+
padRight(p.namespace || "default", 12),
|
|
181
|
+
padRight(p.pm2_env.version || "N/A", 10),
|
|
182
|
+
padRight(p.pm2_env.execMode, 8),
|
|
183
|
+
padRight(p.pid ? String(p.pid) : "N/A", 8),
|
|
184
|
+
padRight(uptime, 10),
|
|
185
|
+
padRight(String(p.pm2_env.restart_time), 4),
|
|
186
|
+
padRight(p.status, 16),
|
|
187
|
+
padRight(p.monit.cpu.toFixed(1) + "%", 8),
|
|
188
|
+
padRight(formatBytes(p.monit.memory), 10),
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
const line = row.join(" ");
|
|
192
|
+
// Colorize the status cell inline
|
|
193
|
+
const colored = line.replace(
|
|
194
|
+
p.status,
|
|
195
|
+
colorize(p.status, statusColor(p.status))
|
|
196
|
+
);
|
|
197
|
+
console.log(colored);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Ecosystem config loader
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
async function loadEcosystemConfig(filePath: string): Promise<EcosystemConfig> {
|
|
206
|
+
const abs = resolve(filePath);
|
|
207
|
+
if (!existsSync(abs)) {
|
|
208
|
+
throw new Error(`Ecosystem file not found: ${abs}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const ext = extname(abs);
|
|
212
|
+
if (ext === ".json") {
|
|
213
|
+
return await Bun.file(abs).json();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// .ts, .js, .mjs — dynamic import
|
|
217
|
+
const mod = await import(abs);
|
|
218
|
+
return mod.default || mod;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Parse CLI flags into StartOptions
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
function parseStartFlags(args: string[], scriptOrConfig: string): StartOptions {
|
|
226
|
+
const opts: StartOptions = { script: scriptOrConfig };
|
|
227
|
+
|
|
228
|
+
let i = 0;
|
|
229
|
+
const positionalArgs: string[] = [];
|
|
230
|
+
|
|
231
|
+
while (i < args.length) {
|
|
232
|
+
const arg = args[i]!;
|
|
233
|
+
|
|
234
|
+
switch (arg) {
|
|
235
|
+
case "--name":
|
|
236
|
+
case "-n":
|
|
237
|
+
opts.name = args[++i];
|
|
238
|
+
break;
|
|
239
|
+
case "--instances":
|
|
240
|
+
case "-i":
|
|
241
|
+
opts.instances = parseInt(args[++i]!) || 1;
|
|
242
|
+
break;
|
|
243
|
+
case "--cwd":
|
|
244
|
+
opts.cwd = args[++i];
|
|
245
|
+
break;
|
|
246
|
+
case "--interpreter":
|
|
247
|
+
opts.interpreter = args[++i];
|
|
248
|
+
break;
|
|
249
|
+
case "--interpreter-args":
|
|
250
|
+
opts.interpreterArgs = args[++i]!.split(" ");
|
|
251
|
+
break;
|
|
252
|
+
case "--node-args":
|
|
253
|
+
opts.nodeArgs = args[++i]!.split(" ");
|
|
254
|
+
break;
|
|
255
|
+
case "--watch":
|
|
256
|
+
case "-w":
|
|
257
|
+
opts.watch = true;
|
|
258
|
+
break;
|
|
259
|
+
case "--watch-path":
|
|
260
|
+
if (!Array.isArray(opts.watch)) opts.watch = [];
|
|
261
|
+
(opts.watch as string[]).push(args[++i]!);
|
|
262
|
+
break;
|
|
263
|
+
case "--ignore-watch":
|
|
264
|
+
opts.ignoreWatch = args[++i]!.split(",");
|
|
265
|
+
break;
|
|
266
|
+
case "--exec-mode":
|
|
267
|
+
case "-x":
|
|
268
|
+
opts.execMode = args[++i] as "fork" | "cluster";
|
|
269
|
+
break;
|
|
270
|
+
case "--max-memory-restart":
|
|
271
|
+
opts.maxMemoryRestart = args[++i];
|
|
272
|
+
break;
|
|
273
|
+
case "--max-restarts":
|
|
274
|
+
opts.maxRestarts = parseInt(args[++i]!);
|
|
275
|
+
break;
|
|
276
|
+
case "--min-uptime":
|
|
277
|
+
opts.minUptime = parseInt(args[++i]!);
|
|
278
|
+
break;
|
|
279
|
+
case "--kill-timeout":
|
|
280
|
+
opts.killTimeout = parseInt(args[++i]!);
|
|
281
|
+
break;
|
|
282
|
+
case "--restart-delay":
|
|
283
|
+
opts.restartDelay = parseInt(args[++i]!);
|
|
284
|
+
break;
|
|
285
|
+
case "--cron":
|
|
286
|
+
case "--cron-restart":
|
|
287
|
+
opts.cron = args[++i];
|
|
288
|
+
break;
|
|
289
|
+
case "--no-autorestart":
|
|
290
|
+
opts.autorestart = false;
|
|
291
|
+
break;
|
|
292
|
+
case "--env": {
|
|
293
|
+
const envPair = args[++i]!;
|
|
294
|
+
const eqIdx = envPair.indexOf("=");
|
|
295
|
+
if (eqIdx !== -1) {
|
|
296
|
+
if (!opts.env) opts.env = {};
|
|
297
|
+
opts.env[envPair.substring(0, eqIdx)] = envPair.substring(eqIdx + 1);
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case "--log":
|
|
302
|
+
case "--output":
|
|
303
|
+
case "-o":
|
|
304
|
+
opts.outFile = args[++i];
|
|
305
|
+
break;
|
|
306
|
+
case "--error":
|
|
307
|
+
case "-e":
|
|
308
|
+
opts.errorFile = args[++i];
|
|
309
|
+
break;
|
|
310
|
+
case "--merge-logs":
|
|
311
|
+
opts.mergeLogs = true;
|
|
312
|
+
break;
|
|
313
|
+
case "--log-date-format":
|
|
314
|
+
opts.logDateFormat = args[++i];
|
|
315
|
+
break;
|
|
316
|
+
case "--log-max-size":
|
|
317
|
+
opts.logMaxSize = args[++i];
|
|
318
|
+
break;
|
|
319
|
+
case "--log-retain":
|
|
320
|
+
opts.logRetain = parseInt(args[++i]!);
|
|
321
|
+
break;
|
|
322
|
+
case "--log-compress":
|
|
323
|
+
opts.logCompress = true;
|
|
324
|
+
break;
|
|
325
|
+
case "--port":
|
|
326
|
+
case "-p":
|
|
327
|
+
opts.port = parseInt(args[++i]!);
|
|
328
|
+
break;
|
|
329
|
+
case "--health-check-url":
|
|
330
|
+
opts.healthCheckUrl = args[++i];
|
|
331
|
+
break;
|
|
332
|
+
case "--health-check-interval":
|
|
333
|
+
opts.healthCheckInterval = parseInt(args[++i]!);
|
|
334
|
+
break;
|
|
335
|
+
case "--health-check-timeout":
|
|
336
|
+
opts.healthCheckTimeout = parseInt(args[++i]!);
|
|
337
|
+
break;
|
|
338
|
+
case "--health-check-max-fails":
|
|
339
|
+
opts.healthCheckMaxFails = parseInt(args[++i]!);
|
|
340
|
+
break;
|
|
341
|
+
case "--wait-ready":
|
|
342
|
+
opts.waitReady = true;
|
|
343
|
+
break;
|
|
344
|
+
case "--listen-timeout":
|
|
345
|
+
opts.listenTimeout = parseInt(args[++i]!);
|
|
346
|
+
break;
|
|
347
|
+
case "--namespace":
|
|
348
|
+
opts.namespace = args[++i];
|
|
349
|
+
break;
|
|
350
|
+
case "--source-map-support":
|
|
351
|
+
opts.sourceMapSupport = true;
|
|
352
|
+
break;
|
|
353
|
+
case "--":
|
|
354
|
+
// Everything after -- is passed as script args
|
|
355
|
+
positionalArgs.push(...args.slice(i + 1));
|
|
356
|
+
i = args.length;
|
|
357
|
+
break;
|
|
358
|
+
default:
|
|
359
|
+
if (!arg.startsWith("-")) {
|
|
360
|
+
positionalArgs.push(arg);
|
|
361
|
+
}
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
i++;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (positionalArgs.length > 0) {
|
|
368
|
+
opts.args = positionalArgs;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return opts;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
// Commands
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
async function cmdStart(args: string[]) {
|
|
379
|
+
const scriptOrConfig = args[0];
|
|
380
|
+
if (!scriptOrConfig) {
|
|
381
|
+
console.error(colorize("Usage: bm2 start <script|config> [options]", "red"));
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const ext = extname(scriptOrConfig);
|
|
386
|
+
|
|
387
|
+
// Ecosystem file
|
|
388
|
+
if (
|
|
389
|
+
ext === ".json" ||
|
|
390
|
+
scriptOrConfig.includes("ecosystem") ||
|
|
391
|
+
scriptOrConfig.includes("bm2.config")
|
|
392
|
+
) {
|
|
393
|
+
const config = await loadEcosystemConfig(scriptOrConfig);
|
|
394
|
+
const res = await sendToDaemon({ type: "ecosystem", data: config });
|
|
395
|
+
if (!res.success) {
|
|
396
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
printProcessTable(res.data);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Single script
|
|
404
|
+
const opts = parseStartFlags(args.slice(1), resolve(scriptOrConfig));
|
|
405
|
+
opts.script = resolve(scriptOrConfig);
|
|
406
|
+
|
|
407
|
+
const res = await sendToDaemon({ type: "start", data: opts });
|
|
408
|
+
if (!res.success) {
|
|
409
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
printProcessTable(res.data);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function cmdStop(args: string[]) {
|
|
416
|
+
const target = args[0] || "all";
|
|
417
|
+
const type = target === "all" ? "stopAll" : "stop";
|
|
418
|
+
const data = target === "all" ? undefined : { target };
|
|
419
|
+
|
|
420
|
+
const res = await sendToDaemon({ type, data });
|
|
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 cmdRestart(args: string[]) {
|
|
429
|
+
const target = args[0] || "all";
|
|
430
|
+
const type = target === "all" ? "restartAll" : "restart";
|
|
431
|
+
const data = target === "all" ? undefined : { target };
|
|
432
|
+
|
|
433
|
+
const res = await sendToDaemon({ type, data });
|
|
434
|
+
if (!res.success) {
|
|
435
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
printProcessTable(res.data);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function cmdReload(args: string[]) {
|
|
442
|
+
const target = args[0] || "all";
|
|
443
|
+
const type = target === "all" ? "reloadAll" : "reload";
|
|
444
|
+
const data = target === "all" ? undefined : { target };
|
|
445
|
+
|
|
446
|
+
const res = await sendToDaemon({ type, data });
|
|
447
|
+
if (!res.success) {
|
|
448
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
printProcessTable(res.data);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function cmdDelete(args: string[]) {
|
|
455
|
+
const target = args[0] || "all";
|
|
456
|
+
const type = target === "all" ? "deleteAll" : "delete";
|
|
457
|
+
const data = target === "all" ? undefined : { target };
|
|
458
|
+
|
|
459
|
+
const res = await sendToDaemon({ type, data });
|
|
460
|
+
if (!res.success) {
|
|
461
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
console.log(colorize("✓ Deleted", "green"));
|
|
465
|
+
printProcessTable(res.data);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function cmdList() {
|
|
469
|
+
const res = await sendToDaemon({ type: "list" });
|
|
470
|
+
if (!res.success) {
|
|
471
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
printProcessTable(res.data);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async function cmdDescribe(args: string[]) {
|
|
478
|
+
const target = args[0];
|
|
479
|
+
if (!target) {
|
|
480
|
+
console.error(colorize("Usage: bm2 describe <id|name>", "red"));
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const res = await sendToDaemon({ type: "describe", data: { target } });
|
|
485
|
+
if (!res.success) {
|
|
486
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const processes: ProcessState[] = res.data;
|
|
491
|
+
for (const p of processes) {
|
|
492
|
+
console.log(colorize(`\n─── ${p.name} (id: ${p.pm_id}) ───`, "bold"));
|
|
493
|
+
console.log(` Status : ${colorize(p.status, statusColor(p.status))}`);
|
|
494
|
+
console.log(` PID : ${p.pid || "N/A"}`);
|
|
495
|
+
console.log(` Exec mode : ${p.pm2_env.execMode}`);
|
|
496
|
+
console.log(` Instances : ${p.pm2_env.instances}`);
|
|
497
|
+
console.log(` Namespace : ${p.namespace || "default"}`);
|
|
498
|
+
console.log(` Script : ${p.pm2_env.script}`);
|
|
499
|
+
console.log(` CWD : ${p.pm2_env.cwd}`);
|
|
500
|
+
console.log(` Args : ${p.pm2_env.args.join(" ") || "(none)"}`);
|
|
501
|
+
console.log(` Interpreter : ${p.pm2_env.interpreter || "bun"}`);
|
|
502
|
+
console.log(` Restarts : ${p.pm2_env.restart_time}`);
|
|
503
|
+
console.log(` Unstable : ${p.pm2_env.unstable_restarts}`);
|
|
504
|
+
console.log(
|
|
505
|
+
` Uptime : ${
|
|
506
|
+
p.status === "online" ? formatUptime(Date.now() - p.pm2_env.pm_uptime) : "N/A"
|
|
507
|
+
}`
|
|
508
|
+
);
|
|
509
|
+
console.log(` Created at : ${new Date(p.pm2_env.created_at).toISOString()}`);
|
|
510
|
+
console.log(` CPU : ${p.monit.cpu.toFixed(1)}%`);
|
|
511
|
+
console.log(` Memory : ${formatBytes(p.monit.memory)}`);
|
|
512
|
+
if (p.monit.handles !== undefined)
|
|
513
|
+
console.log(` Handles : ${p.monit.handles}`);
|
|
514
|
+
if (p.monit.eventLoopLatency !== undefined)
|
|
515
|
+
console.log(` EL Latency : ${p.monit.eventLoopLatency.toFixed(2)} ms`);
|
|
516
|
+
console.log(` Watch : ${p.pm2_env.watch}`);
|
|
517
|
+
console.log(` Autorestart : ${p.pm2_env.autorestart}`);
|
|
518
|
+
console.log(` Max restarts : ${p.pm2_env.maxRestarts}`);
|
|
519
|
+
console.log(` Kill timeout : ${p.pm2_env.killTimeout} ms`);
|
|
520
|
+
if (p.pm2_env.healthCheckUrl)
|
|
521
|
+
console.log(` Health URL : ${p.pm2_env.healthCheckUrl}`);
|
|
522
|
+
if (p.pm2_env.cronRestart)
|
|
523
|
+
console.log(` Cron restart : ${p.pm2_env.cronRestart}`);
|
|
524
|
+
if (p.pm2_env.port)
|
|
525
|
+
console.log(` Port : ${p.pm2_env.port}`);
|
|
526
|
+
console.log();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async function cmdLogs(args: string[]) {
|
|
531
|
+
const target = args[0] || "all";
|
|
532
|
+
let lines = 20;
|
|
533
|
+
const linesIdx = args.indexOf("--lines");
|
|
534
|
+
if (linesIdx !== -1 && args[linesIdx + 1]) {
|
|
535
|
+
lines = parseInt(args[linesIdx + 1]!);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const res = await sendToDaemon({ type: "logs", data: { target, lines } });
|
|
539
|
+
if (!res.success) {
|
|
540
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
for (const log of res.data) {
|
|
545
|
+
console.log(colorize(`\n─── ${log.name} (id: ${log.id}) ───`, "bold"));
|
|
546
|
+
if (log.out) {
|
|
547
|
+
console.log(colorize("--- stdout ---", "dim"));
|
|
548
|
+
console.log(log.out);
|
|
549
|
+
}
|
|
550
|
+
if (log.err) {
|
|
551
|
+
console.log(colorize("--- stderr ---", "red"));
|
|
552
|
+
console.log(log.err);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async function cmdFlush(args: string[]) {
|
|
558
|
+
const target = args[0];
|
|
559
|
+
const res = await sendToDaemon({ type: "flush", data: target ? { target } : undefined });
|
|
560
|
+
if (!res.success) {
|
|
561
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
console.log(colorize("✓ Logs flushed", "green"));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async function cmdScale(args: string[]) {
|
|
568
|
+
const target = args[0];
|
|
569
|
+
const count = parseInt(args[1]!);
|
|
570
|
+
if (!target || isNaN(count)) {
|
|
571
|
+
console.error(colorize("Usage: bm2 scale <name|id> <count>", "red"));
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const res = await sendToDaemon({ type: "scale", data: { target, count } });
|
|
576
|
+
if (!res.success) {
|
|
577
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
printProcessTable(res.data);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async function cmdSave() {
|
|
584
|
+
const res = await sendToDaemon({ type: "save" });
|
|
585
|
+
if (!res.success) {
|
|
586
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
console.log(colorize("✓ Process list saved", "green"));
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async function cmdResurrect() {
|
|
593
|
+
const res = await sendToDaemon({ type: "resurrect" });
|
|
594
|
+
if (!res.success) {
|
|
595
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
printProcessTable(res.data);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function cmdSignal(args: string[]) {
|
|
602
|
+
const signal = args[0];
|
|
603
|
+
const target = args[1];
|
|
604
|
+
if (!signal || !target) {
|
|
605
|
+
console.error(colorize("Usage: bm2 sendSignal <signal> <id|name>", "red"));
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const res = await sendToDaemon({ type: "signal", data: { target, signal } });
|
|
610
|
+
if (!res.success) {
|
|
611
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
612
|
+
process.exit(1);
|
|
613
|
+
}
|
|
614
|
+
console.log(colorize(`✓ Signal ${signal} sent to ${target}`, "green"));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async function cmdReset(args: string[]) {
|
|
618
|
+
const target = args[0] || "all";
|
|
619
|
+
const res = await sendToDaemon({ type: "reset", data: { target } });
|
|
620
|
+
if (!res.success) {
|
|
621
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
622
|
+
process.exit(1);
|
|
623
|
+
}
|
|
624
|
+
console.log(colorize("✓ Restart counters reset", "green"));
|
|
625
|
+
printProcessTable(res.data);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function cmdMonit() {
|
|
629
|
+
const res = await sendToDaemon({ type: "metrics" });
|
|
630
|
+
if (!res.success) {
|
|
631
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const snapshot = res.data;
|
|
636
|
+
console.log(colorize("\n⚡ BM2 Monitor\n", "bold"));
|
|
637
|
+
|
|
638
|
+
console.log(colorize("System:", "cyan"));
|
|
639
|
+
console.log(` Platform : ${snapshot.system.platform}`);
|
|
640
|
+
console.log(` CPUs : ${snapshot.system.cpuCount}`);
|
|
641
|
+
console.log(` Memory : ${formatBytes(snapshot.system.totalMemory - snapshot.system.freeMemory)} / ${formatBytes(snapshot.system.totalMemory)}`);
|
|
642
|
+
console.log(` Load avg : ${snapshot.system.loadAvg.map((l: number) => l.toFixed(2)).join(", ")}`);
|
|
643
|
+
console.log();
|
|
644
|
+
|
|
645
|
+
console.log(colorize("Processes:", "cyan"));
|
|
646
|
+
for (const p of snapshot.processes) {
|
|
647
|
+
const statusStr = colorize(padRight(p.status, 14), statusColor(p.status));
|
|
648
|
+
console.log(
|
|
649
|
+
` ${padRight(String(p.id), 4)} ${padRight(p.name, 20)} ${statusStr} CPU: ${padRight(p.cpu.toFixed(1) + "%", 8)} MEM: ${padRight(formatBytes(p.memory), 10)} ↺ ${p.restarts}`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
console.log();
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async function cmdDashboard(args: string[]) {
|
|
656
|
+
let port = DASHBOARD_PORT;
|
|
657
|
+
let metricsPort = METRICS_PORT;
|
|
658
|
+
|
|
659
|
+
const portIdx = args.indexOf("--port");
|
|
660
|
+
if (portIdx !== -1 && args[portIdx + 1]) port = parseInt(args[portIdx + 1]!);
|
|
661
|
+
const mIdx = args.indexOf("--metrics-port");
|
|
662
|
+
if (mIdx !== -1 && args[mIdx + 1]) metricsPort = parseInt(args[mIdx + 1]!);
|
|
663
|
+
|
|
664
|
+
const res = await sendToDaemon({ type: "dashboard", data: { port, metricsPort } });
|
|
665
|
+
if (!res.success) {
|
|
666
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
console.log(colorize(`✓ Dashboard running at http://localhost:${res.data.port}`, "green"));
|
|
670
|
+
console.log(colorize(` Prometheus metrics at http://localhost:${res.data.metricsPort}/metrics`, "dim"));
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function cmdDashboardStop() {
|
|
674
|
+
const res = await sendToDaemon({ type: "dashboardStop" });
|
|
675
|
+
if (!res.success) {
|
|
676
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
console.log(colorize("✓ Dashboard stopped", "green"));
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async function cmdPing() {
|
|
683
|
+
try {
|
|
684
|
+
const res = await sendToDaemon({ type: "ping" });
|
|
685
|
+
if (res.success) {
|
|
686
|
+
console.log(colorize("✓ Daemon is alive", "green"));
|
|
687
|
+
console.log(` PID : ${res.data.pid}`);
|
|
688
|
+
console.log(` Uptime : ${formatUptime(res.data.uptime * 1000)}`);
|
|
689
|
+
} else {
|
|
690
|
+
console.log(colorize("✗ Daemon responded with error", "red"));
|
|
691
|
+
}
|
|
692
|
+
} catch {
|
|
693
|
+
console.log(colorize("✗ Daemon is not running", "red"));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
async function cmdKill() {
|
|
698
|
+
try {
|
|
699
|
+
await sendToDaemon({ type: "kill" });
|
|
700
|
+
} catch {
|
|
701
|
+
// Expected — daemon exits
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Clean up leftover files
|
|
705
|
+
try {
|
|
706
|
+
if (existsSync(DAEMON_SOCKET)) unlinkSync(DAEMON_SOCKET);
|
|
707
|
+
} catch {}
|
|
708
|
+
try {
|
|
709
|
+
if (existsSync(DAEMON_PID_FILE)) unlinkSync(DAEMON_PID_FILE);
|
|
710
|
+
} catch {}
|
|
711
|
+
|
|
712
|
+
console.log(colorize("✓ Daemon killed", "green"));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
async function cmdDeploy(args: string[]) {
|
|
716
|
+
const configFile = args[0];
|
|
717
|
+
const environment = args[1];
|
|
718
|
+
|
|
719
|
+
if (!configFile || !environment) {
|
|
720
|
+
console.error(colorize("Usage: bm2 deploy <config> <environment> [setup]", "red"));
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const config = await loadEcosystemConfig(configFile);
|
|
725
|
+
if (!config.deploy || !config.deploy[environment]) {
|
|
726
|
+
console.error(colorize(`Deploy environment "${environment}" not found in config`, "red"));
|
|
727
|
+
process.exit(1);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const deployConfig = config.deploy[environment]!;
|
|
731
|
+
const deployer = new DeployManager();
|
|
732
|
+
|
|
733
|
+
if (args[2] === "setup") {
|
|
734
|
+
await deployer.setup(deployConfig);
|
|
735
|
+
} else {
|
|
736
|
+
await deployer.deploy(deployConfig, args[2]);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
async function cmdStartup(args: string[]) {
|
|
741
|
+
const startup = new StartupManager();
|
|
742
|
+
|
|
743
|
+
if (args[0] === "remove" || args[0] === "uninstall") {
|
|
744
|
+
const result = await startup.uninstall();
|
|
745
|
+
console.log(result);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (args[0] === "install") {
|
|
750
|
+
const result = await startup.install();
|
|
751
|
+
console.log(result);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Just print the config
|
|
756
|
+
const content = await startup.generate(args[0]);
|
|
757
|
+
console.log(content);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
async function cmdEnv(args: string[]) {
|
|
761
|
+
const envMgr = new EnvManager();
|
|
762
|
+
const subCmd = args[0];
|
|
763
|
+
|
|
764
|
+
switch (subCmd) {
|
|
765
|
+
case "set": {
|
|
766
|
+
const name = args[1];
|
|
767
|
+
const key = args[2];
|
|
768
|
+
const value = args[3];
|
|
769
|
+
if (!name || !key || value === undefined) {
|
|
770
|
+
console.error(colorize("Usage: bm2 env set <name> <key> <value>", "red"));
|
|
771
|
+
process.exit(1);
|
|
772
|
+
}
|
|
773
|
+
await envMgr.setEnv(name, key, value);
|
|
774
|
+
console.log(colorize(`✓ Set ${key}=${value} for ${name}`, "green"));
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
case "get": {
|
|
778
|
+
const name = args[1];
|
|
779
|
+
if (!name) {
|
|
780
|
+
console.error(colorize("Usage: bm2 env get <name>", "red"));
|
|
781
|
+
process.exit(1);
|
|
782
|
+
}
|
|
783
|
+
const env = await envMgr.getEnv(name);
|
|
784
|
+
for (const [k, v] of Object.entries(env)) {
|
|
785
|
+
console.log(`${colorize(k, "cyan")}=${v}`);
|
|
786
|
+
}
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
case "delete":
|
|
790
|
+
case "rm": {
|
|
791
|
+
const name = args[1];
|
|
792
|
+
const key = args[2];
|
|
793
|
+
if (!name) {
|
|
794
|
+
console.error(colorize("Usage: bm2 env delete <name> [key]", "red"));
|
|
795
|
+
process.exit(1);
|
|
796
|
+
}
|
|
797
|
+
await envMgr.deleteEnv(name, key);
|
|
798
|
+
console.log(colorize(`✓ Deleted`, "green"));
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
case "list": {
|
|
802
|
+
const all = await envMgr.getEnvs();
|
|
803
|
+
for (const [name, env] of Object.entries(all)) {
|
|
804
|
+
console.log(colorize(`\n${name}:`, "bold"));
|
|
805
|
+
for (const [k, v] of Object.entries(env)) {
|
|
806
|
+
console.log(` ${colorize(k, "cyan")}=${v}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
default:
|
|
812
|
+
console.error(colorize("Usage: bm2 env <set|get|delete|list> ...", "red"));
|
|
813
|
+
process.exit(1);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
async function cmdModule(args: string[]) {
|
|
818
|
+
const subCmd = args[0];
|
|
819
|
+
|
|
820
|
+
switch (subCmd) {
|
|
821
|
+
case "install": {
|
|
822
|
+
const mod = args[1];
|
|
823
|
+
if (!mod) {
|
|
824
|
+
console.error(colorize("Usage: bm2 module install <name|url|path>", "red"));
|
|
825
|
+
process.exit(1);
|
|
826
|
+
}
|
|
827
|
+
const res = await sendToDaemon({ type: "moduleInstall", data: { module: mod } });
|
|
828
|
+
if (!res.success) {
|
|
829
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
830
|
+
process.exit(1);
|
|
831
|
+
}
|
|
832
|
+
console.log(colorize(`✓ Module installed at ${res.data.path}`, "green"));
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
case "uninstall":
|
|
836
|
+
case "remove": {
|
|
837
|
+
const mod = args[1];
|
|
838
|
+
if (!mod) {
|
|
839
|
+
console.error(colorize("Usage: bm2 module uninstall <name>", "red"));
|
|
840
|
+
process.exit(1);
|
|
841
|
+
}
|
|
842
|
+
const res = await sendToDaemon({ type: "moduleUninstall", data: { module: mod } });
|
|
843
|
+
if (!res.success) {
|
|
844
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
845
|
+
process.exit(1);
|
|
846
|
+
}
|
|
847
|
+
console.log(colorize("✓ Module uninstalled", "green"));
|
|
848
|
+
break;
|
|
849
|
+
}
|
|
850
|
+
case "list":
|
|
851
|
+
case "ls": {
|
|
852
|
+
const res = await sendToDaemon({ type: "moduleList" });
|
|
853
|
+
if (!res.success) {
|
|
854
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
if (res.data.length === 0) {
|
|
858
|
+
console.log(colorize("No modules installed", "dim"));
|
|
859
|
+
} else {
|
|
860
|
+
for (const m of res.data) {
|
|
861
|
+
console.log(` ${colorize(m.name, "cyan")} @ ${m.version}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
default:
|
|
867
|
+
console.error(colorize("Usage: bm2 module <install|uninstall|list> ...", "red"));
|
|
868
|
+
process.exit(1);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
async function cmdPrometheus() {
|
|
873
|
+
const res = await sendToDaemon({ type: "prometheus" });
|
|
874
|
+
if (!res.success) {
|
|
875
|
+
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
876
|
+
process.exit(1);
|
|
877
|
+
}
|
|
878
|
+
console.log(res.data);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// ---------------------------------------------------------------------------
|
|
882
|
+
// Help
|
|
883
|
+
// ---------------------------------------------------------------------------
|
|
884
|
+
|
|
885
|
+
function printHelp() {
|
|
886
|
+
console.log(`
|
|
887
|
+
${colorize("⚡ BM2", "bold")} ${colorize(`v${VERSION}`, "dim")} — Bun Process Manager
|
|
888
|
+
|
|
889
|
+
${colorize("Usage:", "bold")} bm2 <command> [options]
|
|
890
|
+
|
|
891
|
+
${colorize("Process Management:", "cyan")}
|
|
892
|
+
start <script|config> [opts] Start a process or ecosystem config
|
|
893
|
+
stop [id|name|all] Stop process(es)
|
|
894
|
+
restart [id|name|all] Restart process(es)
|
|
895
|
+
reload [id|name|all] Graceful zero-downtime reload
|
|
896
|
+
delete [id|name|all] Stop and remove process(es)
|
|
897
|
+
scale <id|name> <count> Scale to N instances
|
|
898
|
+
list | ls | status List all processes
|
|
899
|
+
describe <id|name> Show detailed process info
|
|
900
|
+
reset <id|name|all> Reset restart counters
|
|
901
|
+
|
|
902
|
+
${colorize("Logs:", "cyan")}
|
|
903
|
+
logs [id|name|all] [--lines N] Show recent logs
|
|
904
|
+
flush [id|name] Clear log files
|
|
905
|
+
|
|
906
|
+
${colorize("Monitoring:", "cyan")}
|
|
907
|
+
monit Show live metrics snapshot
|
|
908
|
+
dashboard [--port N] Start web dashboard
|
|
909
|
+
dashboard stop Stop web dashboard
|
|
910
|
+
prometheus Print Prometheus metrics
|
|
911
|
+
|
|
912
|
+
${colorize("Persistence:", "cyan")}
|
|
913
|
+
save Save current process list
|
|
914
|
+
resurrect Restore saved process list
|
|
915
|
+
startup [install|remove] Generate/install startup script
|
|
916
|
+
|
|
917
|
+
${colorize("Deploy:", "cyan")}
|
|
918
|
+
deploy <config> <env> [setup] Deploy using ecosystem config
|
|
919
|
+
|
|
920
|
+
${colorize("Environment:", "cyan")}
|
|
921
|
+
env set <name> <key> <val> Set env variable
|
|
922
|
+
env get <name> List env vars for a process
|
|
923
|
+
env delete <name> [key] Delete env variable(s)
|
|
924
|
+
env list List all env registries
|
|
925
|
+
|
|
926
|
+
${colorize("Modules:", "cyan")}
|
|
927
|
+
module install <name|url> Install a BM2 module
|
|
928
|
+
module uninstall <name> Remove a module
|
|
929
|
+
module list List installed modules
|
|
930
|
+
|
|
931
|
+
${colorize("Daemon:", "cyan")}
|
|
932
|
+
ping Check if daemon is alive
|
|
933
|
+
kill Kill the daemon and all processes
|
|
934
|
+
sendSignal <sig> <id|name> Send OS signal to process
|
|
935
|
+
|
|
936
|
+
${colorize("Start Options:", "dim")}
|
|
937
|
+
--name, -n <name> Process name
|
|
938
|
+
--instances, -i <N> Number of instances (cluster)
|
|
939
|
+
--exec-mode, -x <mode> fork or cluster
|
|
940
|
+
--watch, -w Watch for file changes
|
|
941
|
+
--cwd <path> Working directory
|
|
942
|
+
--interpreter <bin> Custom interpreter
|
|
943
|
+
--node-args <args> Extra runtime arguments
|
|
944
|
+
--max-memory-restart <size> e.g. 200M, 1G
|
|
945
|
+
--max-restarts <N> Max restart attempts
|
|
946
|
+
--cron, --cron-restart <expr> Cron-based restart schedule
|
|
947
|
+
--port, -p <port> Base port for cluster
|
|
948
|
+
--env <KEY=VALUE> Set environment variable
|
|
949
|
+
--no-autorestart Disable auto-restart
|
|
950
|
+
--log, -o <file> Custom stdout log path
|
|
951
|
+
--error, -e <file> Custom stderr log path
|
|
952
|
+
--namespace <ns> Namespace grouping
|
|
953
|
+
--wait-ready Wait for ready signal
|
|
954
|
+
--health-check-url <url> HTTP health check endpoint
|
|
955
|
+
-- <args...> Pass arguments to script
|
|
956
|
+
|
|
957
|
+
${colorize("Examples:", "dim")}
|
|
958
|
+
bm2 start app.ts
|
|
959
|
+
bm2 start server.ts --name api -i 4 --watch
|
|
960
|
+
bm2 start ecosystem.config.ts
|
|
961
|
+
bm2 restart api
|
|
962
|
+
bm2 scale api 8
|
|
963
|
+
bm2 logs api --lines 100
|
|
964
|
+
bm2 monit
|
|
965
|
+
bm2 save && bm2 resurrect
|
|
966
|
+
`);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// ---------------------------------------------------------------------------
|
|
970
|
+
// Main dispatch
|
|
971
|
+
// ---------------------------------------------------------------------------
|
|
972
|
+
|
|
973
|
+
const args = process.argv.slice(2);
|
|
974
|
+
const command = args[0];
|
|
975
|
+
const commandArgs = args.slice(1);
|
|
976
|
+
|
|
977
|
+
switch (command) {
|
|
978
|
+
case "start":
|
|
979
|
+
await cmdStart(commandArgs);
|
|
980
|
+
break;
|
|
981
|
+
case "stop":
|
|
982
|
+
await cmdStop(commandArgs);
|
|
983
|
+
break;
|
|
984
|
+
case "restart":
|
|
985
|
+
await cmdRestart(commandArgs);
|
|
986
|
+
break;
|
|
987
|
+
case "reload":
|
|
988
|
+
await cmdReload(commandArgs);
|
|
989
|
+
break;
|
|
990
|
+
case "delete":
|
|
991
|
+
case "del":
|
|
992
|
+
case "rm":
|
|
993
|
+
await cmdDelete(commandArgs);
|
|
994
|
+
break;
|
|
995
|
+
case "scale":
|
|
996
|
+
await cmdScale(commandArgs);
|
|
997
|
+
break;
|
|
998
|
+
case "list":
|
|
999
|
+
case "ls":
|
|
1000
|
+
case "status":
|
|
1001
|
+
await cmdList();
|
|
1002
|
+
break;
|
|
1003
|
+
case "describe":
|
|
1004
|
+
case "show":
|
|
1005
|
+
case "info":
|
|
1006
|
+
await cmdDescribe(commandArgs);
|
|
1007
|
+
break;
|
|
1008
|
+
case "logs":
|
|
1009
|
+
case "log":
|
|
1010
|
+
await cmdLogs(commandArgs);
|
|
1011
|
+
break;
|
|
1012
|
+
case "flush":
|
|
1013
|
+
await cmdFlush(commandArgs);
|
|
1014
|
+
break;
|
|
1015
|
+
case "monit":
|
|
1016
|
+
case "monitor":
|
|
1017
|
+
await cmdMonit();
|
|
1018
|
+
break;
|
|
1019
|
+
case "dashboard":
|
|
1020
|
+
if (commandArgs[0] === "stop") {
|
|
1021
|
+
await cmdDashboardStop();
|
|
1022
|
+
} else {
|
|
1023
|
+
await cmdDashboard(commandArgs);
|
|
1024
|
+
}
|
|
1025
|
+
break;
|
|
1026
|
+
case "prometheus":
|
|
1027
|
+
await cmdPrometheus();
|
|
1028
|
+
break;
|
|
1029
|
+
case "save":
|
|
1030
|
+
case "dump":
|
|
1031
|
+
await cmdSave();
|
|
1032
|
+
break;
|
|
1033
|
+
case "resurrect":
|
|
1034
|
+
case "restore":
|
|
1035
|
+
await cmdResurrect();
|
|
1036
|
+
break;
|
|
1037
|
+
case "reset":
|
|
1038
|
+
await cmdReset(commandArgs);
|
|
1039
|
+
break;
|
|
1040
|
+
case "sendSignal":
|
|
1041
|
+
case "signal":
|
|
1042
|
+
await cmdSignal(commandArgs);
|
|
1043
|
+
break;
|
|
1044
|
+
case "ping":
|
|
1045
|
+
await cmdPing();
|
|
1046
|
+
break;
|
|
1047
|
+
case "kill":
|
|
1048
|
+
await cmdKill();
|
|
1049
|
+
break;
|
|
1050
|
+
case "deploy":
|
|
1051
|
+
await cmdDeploy(commandArgs);
|
|
1052
|
+
break;
|
|
1053
|
+
case "startup":
|
|
1054
|
+
await cmdStartup(commandArgs);
|
|
1055
|
+
break;
|
|
1056
|
+
case "env":
|
|
1057
|
+
await cmdEnv(commandArgs);
|
|
1058
|
+
break;
|
|
1059
|
+
case "module":
|
|
1060
|
+
await cmdModule(commandArgs);
|
|
1061
|
+
break;
|
|
1062
|
+
case "version":
|
|
1063
|
+
case "-v":
|
|
1064
|
+
case "--version":
|
|
1065
|
+
console.log(`${APP_NAME} v${VERSION}`);
|
|
1066
|
+
break;
|
|
1067
|
+
case "help":
|
|
1068
|
+
case "-h":
|
|
1069
|
+
case "--help":
|
|
1070
|
+
case undefined:
|
|
1071
|
+
printHelp();
|
|
1072
|
+
break;
|
|
1073
|
+
default:
|
|
1074
|
+
console.error(colorize(`Unknown command: ${command}`, "red"));
|
|
1075
|
+
console.error(`Run ${colorize("bm2 --help", "cyan")} for usage information.`);
|
|
1076
|
+
process.exit(1);
|
|
1077
|
+
}
|
|
File without changes
|