bm2 1.0.1 → 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 +5 -2
- package/src/hello.js +2 -0
- package/src/index.ts +251 -741
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bm2",
|
|
3
|
-
"version": "1.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
package/src/index.ts
CHANGED
|
@@ -2,21 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* BM2 — Bun Process Manager
|
|
4
4
|
* A production-grade process manager for Bun.
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - Fork & cluster execution modes
|
|
8
|
-
* - Auto-restart & crash recovery
|
|
9
|
-
* - Health checks & monitoring
|
|
10
|
-
* - Log management & rotation
|
|
11
|
-
* - Deployment support
|
|
12
|
-
*
|
|
13
|
-
* https://github.com/your-org/bm2
|
|
14
|
-
* License: GPL-3.0-only
|
|
15
|
-
* Author: Zak <zak@maxxpainn.com>
|
|
16
5
|
*/
|
|
17
6
|
|
|
18
|
-
import { existsSync, readFileSync, unlinkSync } from "fs";
|
|
7
|
+
import { existsSync, readFileSync, unlinkSync, openSync } from "fs";
|
|
19
8
|
import { resolve, join, extname } from "path";
|
|
9
|
+
import { createConnection } from "net";
|
|
20
10
|
import {
|
|
21
11
|
APP_NAME,
|
|
22
12
|
VERSION,
|
|
@@ -31,8 +21,6 @@ import { DeployManager } from "./deploy";
|
|
|
31
21
|
import { StartupManager } from "./startup-manager";
|
|
32
22
|
import { EnvManager } from "./env-manager";
|
|
33
23
|
import type {
|
|
34
|
-
DaemonMessage,
|
|
35
|
-
DaemonResponse,
|
|
36
24
|
StartOptions,
|
|
37
25
|
EcosystemConfig,
|
|
38
26
|
ProcessState,
|
|
@@ -44,13 +32,34 @@ import type {
|
|
|
44
32
|
ensureDirs();
|
|
45
33
|
|
|
46
34
|
// ---------------------------------------------------------------------------
|
|
47
|
-
//
|
|
35
|
+
// Helpers
|
|
48
36
|
// ---------------------------------------------------------------------------
|
|
49
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
|
+
*/
|
|
50
58
|
function isDaemonRunning(): boolean {
|
|
51
59
|
if (!existsSync(DAEMON_PID_FILE)) return false;
|
|
52
60
|
try {
|
|
53
61
|
const pid = parseInt(readFileSync(DAEMON_PID_FILE, "utf-8").trim());
|
|
62
|
+
if (isNaN(pid)) return false;
|
|
54
63
|
process.kill(pid, 0); // signal 0 — just check existence
|
|
55
64
|
return true;
|
|
56
65
|
} catch {
|
|
@@ -58,92 +67,182 @@ function isDaemonRunning(): boolean {
|
|
|
58
67
|
}
|
|
59
68
|
}
|
|
60
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
|
+
*/
|
|
61
86
|
async function startDaemon(): Promise<void> {
|
|
62
|
-
if (isDaemonRunning())
|
|
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();
|
|
63
106
|
|
|
64
107
|
const daemonScript = join(import.meta.dir, "daemon.ts");
|
|
65
108
|
const bunPath = Bun.which("bun") || "bun";
|
|
66
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
|
|
67
123
|
const child = Bun.spawn([bunPath, "run", daemonScript], {
|
|
68
|
-
stdout: "ignore",
|
|
69
|
-
stderr: "ignore",
|
|
70
124
|
stdin: "ignore",
|
|
125
|
+
stdout: outFd,
|
|
126
|
+
stderr: errFd,
|
|
127
|
+
detached: true,
|
|
71
128
|
});
|
|
72
129
|
|
|
73
|
-
// Detach so the daemon outlives the CLI
|
|
74
130
|
child.unref();
|
|
75
131
|
|
|
76
|
-
// Wait for socket
|
|
77
|
-
|
|
132
|
+
// Wait for socket
|
|
133
|
+
const maxRetries = 50; // 5 seconds
|
|
134
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
78
135
|
if (existsSync(DAEMON_SOCKET)) return;
|
|
79
136
|
await Bun.sleep(100);
|
|
80
137
|
}
|
|
81
138
|
|
|
82
|
-
|
|
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
|
+
);
|
|
83
147
|
}
|
|
84
148
|
|
|
85
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Send a JSON message to the daemon and wait for a response.
|
|
151
|
+
*/
|
|
152
|
+
async function sendToDaemon(message: object): Promise<any> {
|
|
86
153
|
await startDaemon();
|
|
87
154
|
|
|
88
|
-
return new Promise((
|
|
89
|
-
|
|
90
|
-
msg.id = id;
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
let settled = false;
|
|
91
157
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
ws.onopen = () => {
|
|
99
|
-
ws.send(JSON.stringify(msg));
|
|
100
|
-
};
|
|
158
|
+
function settle(fn: typeof resolve | typeof reject, value: any) {
|
|
159
|
+
if (settled) return;
|
|
160
|
+
settled = true;
|
|
161
|
+
clearTimeout(timeout);
|
|
162
|
+
fn(value);
|
|
163
|
+
}
|
|
101
164
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
109
188
|
}
|
|
110
|
-
} catch (err: any) {
|
|
111
|
-
clearTimeout(timeout);
|
|
112
|
-
ws.close();
|
|
113
|
-
reject(err);
|
|
114
189
|
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
}
|
|
121
202
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
});
|
|
125
230
|
});
|
|
126
231
|
}
|
|
127
232
|
|
|
128
233
|
// ---------------------------------------------------------------------------
|
|
129
|
-
// Table
|
|
234
|
+
// Table Rendering & Utilities
|
|
130
235
|
// ---------------------------------------------------------------------------
|
|
131
236
|
|
|
132
237
|
function statusColor(status: string): string {
|
|
133
238
|
switch (status) {
|
|
134
|
-
case "online":
|
|
135
|
-
|
|
136
|
-
case "
|
|
137
|
-
return "gray";
|
|
138
|
-
case "errored":
|
|
139
|
-
return "red";
|
|
239
|
+
case "online": return "green";
|
|
240
|
+
case "stopped": return "gray";
|
|
241
|
+
case "errored": return "red";
|
|
140
242
|
case "launching":
|
|
141
|
-
case "waiting-restart":
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return "magenta";
|
|
145
|
-
default:
|
|
146
|
-
return "white";
|
|
243
|
+
case "waiting-restart": return "yellow";
|
|
244
|
+
case "stopping": return "magenta";
|
|
245
|
+
default: return "white";
|
|
147
246
|
}
|
|
148
247
|
}
|
|
149
248
|
|
|
@@ -157,7 +256,7 @@ function printProcessTable(processes: ProcessState[]) {
|
|
|
157
256
|
padRight("id", 4),
|
|
158
257
|
padRight("name", 20),
|
|
159
258
|
padRight("namespace", 12),
|
|
160
|
-
padRight("
|
|
259
|
+
padRight("ver", 8),
|
|
161
260
|
padRight("mode", 8),
|
|
162
261
|
padRight("pid", 8),
|
|
163
262
|
padRight("uptime", 10),
|
|
@@ -171,14 +270,12 @@ function printProcessTable(processes: ProcessState[]) {
|
|
|
171
270
|
console.log(colorize("─".repeat(header.length), "dim"));
|
|
172
271
|
|
|
173
272
|
for (const p of processes) {
|
|
174
|
-
const uptime =
|
|
175
|
-
p.status === "online" ? formatUptime(Date.now() - p.pm2_env.pm_uptime) : "0s";
|
|
176
|
-
|
|
273
|
+
const uptime = p.status === "online" ? formatUptime(Date.now() - p.pm2_env.pm_uptime) : "0s";
|
|
177
274
|
const row = [
|
|
178
275
|
padRight(String(p.pm_id), 4),
|
|
179
276
|
padRight(p.name, 20),
|
|
180
277
|
padRight(p.namespace || "default", 12),
|
|
181
|
-
padRight(p.pm2_env.version || "N/A",
|
|
278
|
+
padRight(p.pm2_env.version || "N/A", 8),
|
|
182
279
|
padRight(p.pm2_env.execMode, 8),
|
|
183
280
|
padRight(p.pid ? String(p.pid) : "N/A", 8),
|
|
184
281
|
padRight(uptime, 10),
|
|
@@ -187,187 +284,71 @@ function printProcessTable(processes: ProcessState[]) {
|
|
|
187
284
|
padRight(p.monit.cpu.toFixed(1) + "%", 8),
|
|
188
285
|
padRight(formatBytes(p.monit.memory), 10),
|
|
189
286
|
];
|
|
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);
|
|
287
|
+
console.log(row.join(" ").replace(p.status, colorize(p.status, statusColor(p.status))));
|
|
198
288
|
}
|
|
199
289
|
}
|
|
200
290
|
|
|
201
|
-
// ---------------------------------------------------------------------------
|
|
202
|
-
// Ecosystem config loader
|
|
203
|
-
// ---------------------------------------------------------------------------
|
|
204
|
-
|
|
205
291
|
async function loadEcosystemConfig(filePath: string): Promise<EcosystemConfig> {
|
|
206
292
|
const abs = resolve(filePath);
|
|
207
|
-
if (!existsSync(abs)) {
|
|
208
|
-
throw new Error(`Ecosystem file not found: ${abs}`);
|
|
209
|
-
}
|
|
210
|
-
|
|
293
|
+
if (!existsSync(abs)) throw new Error(`Ecosystem file not found: ${abs}`);
|
|
211
294
|
const ext = extname(abs);
|
|
212
|
-
if (ext === ".json")
|
|
213
|
-
return await Bun.file(abs).json();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// .ts, .js, .mjs — dynamic import
|
|
295
|
+
if (ext === ".json") return await Bun.file(abs).json();
|
|
217
296
|
const mod = await import(abs);
|
|
218
297
|
return mod.default || mod;
|
|
219
298
|
}
|
|
220
299
|
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
// Parse CLI flags into StartOptions
|
|
223
|
-
// ---------------------------------------------------------------------------
|
|
224
|
-
|
|
225
300
|
function parseStartFlags(args: string[], scriptOrConfig: string): StartOptions {
|
|
226
301
|
const opts: StartOptions = { script: scriptOrConfig };
|
|
227
|
-
|
|
228
302
|
let i = 0;
|
|
229
303
|
const positionalArgs: string[] = [];
|
|
230
304
|
|
|
231
305
|
while (i < args.length) {
|
|
232
306
|
const arg = args[i]!;
|
|
233
|
-
|
|
234
307
|
switch (arg) {
|
|
235
|
-
case "--name":
|
|
236
|
-
case "-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
case "--
|
|
240
|
-
case "-i"
|
|
241
|
-
|
|
242
|
-
|
|
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":
|
|
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":
|
|
260
316
|
if (!Array.isArray(opts.watch)) opts.watch = [];
|
|
261
|
-
(opts.watch as string[]).push(args[++i]!);
|
|
262
|
-
break;
|
|
263
|
-
case "--ignore-watch":
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
case "--
|
|
267
|
-
case "-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
case "--
|
|
271
|
-
|
|
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;
|
|
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;
|
|
292
328
|
case "--env": {
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
329
|
+
const p = args[++i]!;
|
|
330
|
+
const idx = p.indexOf("=");
|
|
331
|
+
if (idx !== -1) {
|
|
296
332
|
if (!opts.env) opts.env = {};
|
|
297
|
-
opts.env[
|
|
333
|
+
opts.env[p.substring(0, idx)] = p.substring(idx + 1);
|
|
298
334
|
}
|
|
299
335
|
break;
|
|
300
336
|
}
|
|
301
|
-
case "--log":
|
|
302
|
-
case "--
|
|
303
|
-
case "-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
case "--
|
|
307
|
-
case "-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
case "--
|
|
311
|
-
|
|
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;
|
|
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;
|
|
363
348
|
}
|
|
364
349
|
i++;
|
|
365
350
|
}
|
|
366
|
-
|
|
367
|
-
if (positionalArgs.length > 0) {
|
|
368
|
-
opts.args = positionalArgs;
|
|
369
|
-
}
|
|
370
|
-
|
|
351
|
+
if (positionalArgs.length > 0) opts.args = positionalArgs;
|
|
371
352
|
return opts;
|
|
372
353
|
}
|
|
373
354
|
|
|
@@ -382,14 +363,14 @@ async function cmdStart(args: string[]) {
|
|
|
382
363
|
process.exit(1);
|
|
383
364
|
}
|
|
384
365
|
|
|
385
|
-
|
|
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
|
+
}
|
|
386
371
|
|
|
387
|
-
|
|
388
|
-
if (
|
|
389
|
-
ext === ".json" ||
|
|
390
|
-
scriptOrConfig.includes("ecosystem") ||
|
|
391
|
-
scriptOrConfig.includes("bm2.config")
|
|
392
|
-
) {
|
|
372
|
+
const ext = extname(scriptOrConfig);
|
|
373
|
+
if (ext === ".json" || scriptOrConfig.includes("ecosystem") || scriptOrConfig.includes("bm2.config")) {
|
|
393
374
|
const config = await loadEcosystemConfig(scriptOrConfig);
|
|
394
375
|
const res = await sendToDaemon({ type: "ecosystem", data: config });
|
|
395
376
|
if (!res.success) {
|
|
@@ -400,7 +381,6 @@ async function cmdStart(args: string[]) {
|
|
|
400
381
|
return;
|
|
401
382
|
}
|
|
402
383
|
|
|
403
|
-
// Single script
|
|
404
384
|
const opts = parseStartFlags(args.slice(1), resolve(scriptOrConfig));
|
|
405
385
|
opts.script = resolve(scriptOrConfig);
|
|
406
386
|
|
|
@@ -415,9 +395,7 @@ async function cmdStart(args: string[]) {
|
|
|
415
395
|
async function cmdStop(args: string[]) {
|
|
416
396
|
const target = args[0] || "all";
|
|
417
397
|
const type = target === "all" ? "stopAll" : "stop";
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
const res = await sendToDaemon({ type, data });
|
|
398
|
+
const res = await sendToDaemon({ type, data: target === "all" ? undefined : { target } });
|
|
421
399
|
if (!res.success) {
|
|
422
400
|
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
423
401
|
process.exit(1);
|
|
@@ -428,9 +406,7 @@ async function cmdStop(args: string[]) {
|
|
|
428
406
|
async function cmdRestart(args: string[]) {
|
|
429
407
|
const target = args[0] || "all";
|
|
430
408
|
const type = target === "all" ? "restartAll" : "restart";
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
const res = await sendToDaemon({ type, data });
|
|
409
|
+
const res = await sendToDaemon({ type, data: target === "all" ? undefined : { target } });
|
|
434
410
|
if (!res.success) {
|
|
435
411
|
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
436
412
|
process.exit(1);
|
|
@@ -441,9 +417,7 @@ async function cmdRestart(args: string[]) {
|
|
|
441
417
|
async function cmdReload(args: string[]) {
|
|
442
418
|
const target = args[0] || "all";
|
|
443
419
|
const type = target === "all" ? "reloadAll" : "reload";
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
const res = await sendToDaemon({ type, data });
|
|
420
|
+
const res = await sendToDaemon({ type, data: target === "all" ? undefined : { target } });
|
|
447
421
|
if (!res.success) {
|
|
448
422
|
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
449
423
|
process.exit(1);
|
|
@@ -454,9 +428,7 @@ async function cmdReload(args: string[]) {
|
|
|
454
428
|
async function cmdDelete(args: string[]) {
|
|
455
429
|
const target = args[0] || "all";
|
|
456
430
|
const type = target === "all" ? "deleteAll" : "delete";
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
const res = await sendToDaemon({ type, data });
|
|
431
|
+
const res = await sendToDaemon({ type, data: target === "all" ? undefined : { target } });
|
|
460
432
|
if (!res.success) {
|
|
461
433
|
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
462
434
|
process.exit(1);
|
|
@@ -480,50 +452,21 @@ async function cmdDescribe(args: string[]) {
|
|
|
480
452
|
console.error(colorize("Usage: bm2 describe <id|name>", "red"));
|
|
481
453
|
process.exit(1);
|
|
482
454
|
}
|
|
483
|
-
|
|
484
455
|
const res = await sendToDaemon({ type: "describe", data: { target } });
|
|
485
456
|
if (!res.success) {
|
|
486
457
|
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
487
458
|
process.exit(1);
|
|
488
459
|
}
|
|
489
|
-
|
|
490
460
|
const processes: ProcessState[] = res.data;
|
|
491
461
|
for (const p of processes) {
|
|
492
462
|
console.log(colorize(`\n─── ${p.name} (id: ${p.pm_id}) ───`, "bold"));
|
|
493
463
|
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
464
|
console.log(` Script : ${p.pm2_env.script}`);
|
|
499
|
-
console.log(`
|
|
500
|
-
console.log(`
|
|
501
|
-
console.log(` Interpreter : ${p.pm2_env.interpreter || "bun"}`);
|
|
465
|
+
console.log(` Log (out) : ${p.pm2_env.pm_out_log_path}`);
|
|
466
|
+
console.log(` Log (err) : ${p.pm2_env.pm_err_log_path}`);
|
|
502
467
|
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
468
|
console.log(` Memory : ${formatBytes(p.monit.memory)}`);
|
|
512
|
-
|
|
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();
|
|
469
|
+
console.log(` CPU : ${p.monit.cpu.toFixed(1)}%`);
|
|
527
470
|
}
|
|
528
471
|
}
|
|
529
472
|
|
|
@@ -531,26 +474,17 @@ async function cmdLogs(args: string[]) {
|
|
|
531
474
|
const target = args[0] || "all";
|
|
532
475
|
let lines = 20;
|
|
533
476
|
const linesIdx = args.indexOf("--lines");
|
|
534
|
-
if (linesIdx !== -1 && args[linesIdx + 1])
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
|
|
477
|
+
if (linesIdx !== -1 && args[linesIdx + 1]) lines = parseInt(args[linesIdx + 1]!);
|
|
478
|
+
|
|
538
479
|
const res = await sendToDaemon({ type: "logs", data: { target, lines } });
|
|
539
480
|
if (!res.success) {
|
|
540
481
|
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
541
482
|
process.exit(1);
|
|
542
483
|
}
|
|
543
|
-
|
|
544
484
|
for (const log of res.data) {
|
|
545
485
|
console.log(colorize(`\n─── ${log.name} (id: ${log.id}) ───`, "bold"));
|
|
546
|
-
if (log.out)
|
|
547
|
-
|
|
548
|
-
console.log(log.out);
|
|
549
|
-
}
|
|
550
|
-
if (log.err) {
|
|
551
|
-
console.log(colorize("--- stderr ---", "red"));
|
|
552
|
-
console.log(log.err);
|
|
553
|
-
}
|
|
486
|
+
if (log.out) console.log(log.out);
|
|
487
|
+
if (log.err) console.log(colorize(log.err, "red"));
|
|
554
488
|
}
|
|
555
489
|
}
|
|
556
490
|
|
|
@@ -571,7 +505,6 @@ async function cmdScale(args: string[]) {
|
|
|
571
505
|
console.error(colorize("Usage: bm2 scale <name|id> <count>", "red"));
|
|
572
506
|
process.exit(1);
|
|
573
507
|
}
|
|
574
|
-
|
|
575
508
|
const res = await sendToDaemon({ type: "scale", data: { target, count } });
|
|
576
509
|
if (!res.success) {
|
|
577
510
|
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
@@ -598,31 +531,13 @@ async function cmdResurrect() {
|
|
|
598
531
|
printProcessTable(res.data);
|
|
599
532
|
}
|
|
600
533
|
|
|
601
|
-
async function
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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);
|
|
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"));
|
|
626
541
|
}
|
|
627
542
|
|
|
628
543
|
async function cmdMonit() {
|
|
@@ -631,343 +546,16 @@ async function cmdMonit() {
|
|
|
631
546
|
console.error(colorize(`Error: ${res.error}`, "red"));
|
|
632
547
|
process.exit(1);
|
|
633
548
|
}
|
|
634
|
-
|
|
635
549
|
const snapshot = res.data;
|
|
636
550
|
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"));
|
|
551
|
+
console.log(`System: CPU ${snapshot.system.cpuCount} cores | Load ${snapshot.system.loadAvg[0]?.toFixed(2)}`);
|
|
646
552
|
for (const p of snapshot.processes) {
|
|
647
|
-
|
|
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);
|
|
553
|
+
console.log(`${padRight(p.name, 20)} ${colorize(p.status, statusColor(p.status))} CPU: ${p.cpu.toFixed(1)}% Mem: ${formatBytes(p.memory)}`);
|
|
877
554
|
}
|
|
878
|
-
console.log(res.data);
|
|
879
555
|
}
|
|
880
556
|
|
|
881
557
|
// ---------------------------------------------------------------------------
|
|
882
|
-
//
|
|
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
|
|
558
|
+
// Main Dispatch
|
|
971
559
|
// ---------------------------------------------------------------------------
|
|
972
560
|
|
|
973
561
|
const args = process.argv.slice(2);
|
|
@@ -975,103 +563,25 @@ const command = args[0];
|
|
|
975
563
|
const commandArgs = args.slice(1);
|
|
976
564
|
|
|
977
565
|
switch (command) {
|
|
978
|
-
case "start":
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
case "
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
case "
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
case "
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
case "
|
|
991
|
-
case "
|
|
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;
|
|
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;
|
|
1067
580
|
case "help":
|
|
1068
|
-
case "-h":
|
|
1069
|
-
case "--help":
|
|
1070
581
|
case undefined:
|
|
1071
|
-
|
|
582
|
+
console.log(`BM2 v${VERSION} - Usage: bm2 <command> [options]`);
|
|
1072
583
|
break;
|
|
1073
584
|
default:
|
|
1074
585
|
console.error(colorize(`Unknown command: ${command}`, "red"));
|
|
1075
|
-
console.error(`Run ${colorize("bm2 --help", "cyan")} for usage information.`);
|
|
1076
586
|
process.exit(1);
|
|
1077
587
|
}
|