bgrun 3.12.0 → 3.12.2
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/dashboard/app/api/check-port/route.ts +35 -0
- package/dashboard/app/api/dependencies/route.ts +40 -0
- package/dashboard/app/api/deploy/[name]/route.ts +6 -41
- package/dashboard/app/api/deploy-all/route.ts +25 -0
- package/dashboard/app/api/guard/route.ts +4 -1
- package/dashboard/app/api/guard-events/route.ts +5 -0
- package/dashboard/app/api/history/route.ts +39 -0
- package/dashboard/app/api/next-port/route.ts +32 -0
- package/dashboard/app/api/processes/route.ts +11 -3
- package/dashboard/app/api/restart/[name]/route.ts +7 -0
- package/dashboard/app/api/start/route.ts +11 -0
- package/dashboard/app/api/stop/[name]/route.ts +4 -1
- package/dashboard/app/api/templates/route.ts +47 -0
- package/dashboard/app/globals.css +1565 -5
- package/dashboard/app/page.client.tsx +1907 -2
- package/dashboard/app/page.tsx +292 -5
- package/dist/index.js +787 -194
- package/package.json +2 -2
- package/scripts/bgr-startup.ps1 +3 -3
- package/scripts/bgrun-startup.ps1 +91 -0
- package/src/bgrun.test.ts +171 -0
- package/src/commands/details.ts +17 -3
- package/src/commands/list.ts +37 -4
- package/src/commands/run.ts +21 -3
- package/src/db.ts +257 -0
- package/src/deploy.ts +163 -0
- package/src/guard.ts +51 -0
- package/src/index.ts +92 -14
- package/src/index_copy.ts +614 -0
- package/src/logger.ts +12 -2
- package/src/platform.ts +101 -56
- package/src/server.ts +87 -3
- package/src/utils.ts +2 -2
package/src/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type { CommandOptions } from "./types";
|
|
|
12
12
|
import { error, announce } from "./logger";
|
|
13
13
|
// startServer is dynamically imported only when --_serve is used
|
|
14
14
|
// to avoid loading melina (which has side-effects) on every bgrun command
|
|
15
|
-
import { getHomeDir, getShellCommand, findChildPid, isProcessRunning, terminateProcess, getProcessPorts, killProcessOnPort, waitForPortFree, isPortFree } from "./platform";
|
|
15
|
+
import { getHomeDir, getShellCommand, findChildPid, isProcessRunning, terminateProcess, getProcessPorts, killProcessOnPort, waitForPortFree, isPortFree, findPidByPort } from "./platform";
|
|
16
16
|
import { insertProcess, removeProcessByName, getProcess, retryDatabaseOperation, getDbInfo } from "./db";
|
|
17
17
|
import dedent from "dedent";
|
|
18
18
|
import chalk from "chalk";
|
|
@@ -26,6 +26,48 @@ if (!Bun.argv.includes("--_serve")) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Redirect console.log/warn/error to log files when running detached.
|
|
31
|
+
* The parent spawner passes file paths via BGR_STDOUT/BGR_STDERR env vars.
|
|
32
|
+
* Appends timestamped lines so `bgrun <name> --logs` shows real output.
|
|
33
|
+
*/
|
|
34
|
+
function redirectConsoleToFiles() {
|
|
35
|
+
const stdoutPath = Bun.env.BGR_STDOUT;
|
|
36
|
+
const stderrPath = Bun.env.BGR_STDERR;
|
|
37
|
+
if (!stdoutPath && !stderrPath) return; // Not detached, keep normal console
|
|
38
|
+
|
|
39
|
+
const { appendFileSync } = require('fs');
|
|
40
|
+
|
|
41
|
+
// Strip ANSI escape codes for clean log files
|
|
42
|
+
const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
43
|
+
|
|
44
|
+
const timestamp = () => new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
45
|
+
|
|
46
|
+
if (stdoutPath) {
|
|
47
|
+
const origLog = console.log;
|
|
48
|
+
const origWarn = console.warn;
|
|
49
|
+
console.log = (...args: any[]) => {
|
|
50
|
+
const line = `[${timestamp()}] ${stripAnsi(args.map(String).join(' '))}\n`;
|
|
51
|
+
try { appendFileSync(stdoutPath, line); } catch { }
|
|
52
|
+
origLog.apply(console, args); // Also keep original (goes to /dev/null when detached, but useful if attached)
|
|
53
|
+
};
|
|
54
|
+
console.warn = (...args: any[]) => {
|
|
55
|
+
const line = `[${timestamp()}] WARN: ${stripAnsi(args.map(String).join(' '))}\n`;
|
|
56
|
+
try { appendFileSync(stdoutPath, line); } catch { }
|
|
57
|
+
origWarn.apply(console, args);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (stderrPath) {
|
|
62
|
+
const origError = console.error;
|
|
63
|
+
console.error = (...args: any[]) => {
|
|
64
|
+
const line = `[${timestamp()}] ERROR: ${stripAnsi(args.map(String).join(' '))}\n`;
|
|
65
|
+
try { appendFileSync(stderrPath, line); } catch { }
|
|
66
|
+
origError.apply(console, args);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
29
71
|
async function showHelp() {
|
|
30
72
|
const usage = dedent`
|
|
31
73
|
${chalk.bold('bgrun — Bun Background Runner')}
|
|
@@ -120,6 +162,9 @@ async function run() {
|
|
|
120
162
|
// Port is NOT passed explicitly — Melina auto-detects from BUN_PORT env
|
|
121
163
|
// or defaults to 3000 with fallback to next available port.
|
|
122
164
|
if (values['_serve']) {
|
|
165
|
+
// Redirect console output to log files when running detached
|
|
166
|
+
// The spawner passes paths via BGR_STDOUT/BGR_STDERR env vars
|
|
167
|
+
redirectConsoleToFiles();
|
|
123
168
|
const { startServer } = await import("./server");
|
|
124
169
|
await startServer();
|
|
125
170
|
return;
|
|
@@ -127,6 +172,8 @@ async function run() {
|
|
|
127
172
|
|
|
128
173
|
// Internal: actually run the guard loop (spawned by --guard)
|
|
129
174
|
if (values['_guard-loop']) {
|
|
175
|
+
// Redirect console output to log files when running detached
|
|
176
|
+
redirectConsoleToFiles();
|
|
130
177
|
const { startGuardLoop } = await import("./guard");
|
|
131
178
|
const intervalStr = positionals[0];
|
|
132
179
|
const intervalMs = intervalStr ? parseInt(intervalStr) * 1000 : undefined;
|
|
@@ -193,10 +240,13 @@ async function run() {
|
|
|
193
240
|
await Bun.write(stderrPath, '');
|
|
194
241
|
|
|
195
242
|
// Pass BUN_PORT env var only if user explicitly requested a port
|
|
196
|
-
const spawnEnv = { ...Bun.env };
|
|
243
|
+
const spawnEnv: Record<string, string> = { ...Bun.env } as any;
|
|
197
244
|
if (requestedPort) {
|
|
198
245
|
spawnEnv.BUN_PORT = requestedPort;
|
|
199
246
|
}
|
|
247
|
+
// Pass log paths so the detached process can redirect its own console output
|
|
248
|
+
spawnEnv.BGR_STDOUT = stdoutPath;
|
|
249
|
+
spawnEnv.BGR_STDERR = stderrPath;
|
|
200
250
|
|
|
201
251
|
// Resolve the target port: --port flag > BUN_PORT env > default 3000
|
|
202
252
|
const targetPort = parseInt(requestedPort || Bun.env.BUN_PORT || '3000');
|
|
@@ -216,16 +266,18 @@ async function run() {
|
|
|
216
266
|
const newProcess = Bun.spawn(getShellCommand(spawnCommand), {
|
|
217
267
|
env: spawnEnv,
|
|
218
268
|
cwd: bgrDir,
|
|
219
|
-
stdout:
|
|
220
|
-
stderr:
|
|
221
|
-
|
|
269
|
+
stdout: "ignore",
|
|
270
|
+
stderr: "ignore",
|
|
271
|
+
detached: true, // Windows: new process group outside parent's Job Object — survives terminal close
|
|
272
|
+
} as any);
|
|
222
273
|
|
|
223
274
|
newProcess.unref();
|
|
224
275
|
|
|
225
|
-
//
|
|
226
|
-
//
|
|
276
|
+
// With detached: cmd.exe wrapper exits immediately, so findChildPid won't work.
|
|
277
|
+
// Instead, wait for the server to bind a port and find the PID from there.
|
|
227
278
|
await sleep(2000); // Give the server time to start and bind a port
|
|
228
|
-
const
|
|
279
|
+
const resolvedPort = parseInt(requestedPort || Bun.env.BUN_PORT || '3000');
|
|
280
|
+
const actualPid = await findPidByPort(resolvedPort, 10000) ?? await findChildPid(newProcess.pid);
|
|
229
281
|
|
|
230
282
|
// Detect the port the server actually bound to
|
|
231
283
|
let actualPort: number | null = null;
|
|
@@ -310,15 +362,27 @@ async function run() {
|
|
|
310
362
|
await Bun.write(stderrPath, '');
|
|
311
363
|
|
|
312
364
|
const newProcess = Bun.spawn(getShellCommand(spawnCommand), {
|
|
313
|
-
env: { ...Bun.env },
|
|
365
|
+
env: { ...Bun.env, BGR_STDOUT: stdoutPath, BGR_STDERR: stderrPath },
|
|
314
366
|
cwd: bgrDir,
|
|
315
|
-
stdout:
|
|
316
|
-
stderr:
|
|
317
|
-
|
|
367
|
+
stdout: "ignore",
|
|
368
|
+
stderr: "ignore",
|
|
369
|
+
detached: true, // Windows: new process group outside parent's Job Object — survives terminal close
|
|
370
|
+
} as any);
|
|
318
371
|
|
|
319
372
|
newProcess.unref();
|
|
320
373
|
await sleep(1000);
|
|
321
|
-
|
|
374
|
+
// With detached: cmd.exe exits immediately. Search for the guard by command line.
|
|
375
|
+
let actualPid = await findChildPid(newProcess.pid);
|
|
376
|
+
if (!(await isProcessRunning(actualPid))) {
|
|
377
|
+
// cmd.exe already died — search for the bun process running --_guard-loop
|
|
378
|
+
const { psExec: ps } = await import('./platform');
|
|
379
|
+
const result = ps(
|
|
380
|
+
`Get-CimInstance Win32_Process -Filter "Name='bun.exe'" | Where-Object { $_.CommandLine -match '_guard-loop' } | Sort-Object -Property CreationDate -Descending | Select-Object -First 1 -ExpandProperty ProcessId`,
|
|
381
|
+
3000
|
|
382
|
+
);
|
|
383
|
+
const foundPid = parseInt(result.trim());
|
|
384
|
+
if (!isNaN(foundPid) && foundPid > 0) actualPid = foundPid;
|
|
385
|
+
}
|
|
322
386
|
|
|
323
387
|
await retryDatabaseOperation(() =>
|
|
324
388
|
insertProcess({
|
|
@@ -510,6 +574,15 @@ async function run() {
|
|
|
510
574
|
return;
|
|
511
575
|
}
|
|
512
576
|
|
|
577
|
+
// Explicit "list" command
|
|
578
|
+
if (name === 'list') {
|
|
579
|
+
await showAll({
|
|
580
|
+
json: values.json as boolean | undefined,
|
|
581
|
+
filter: values.filter as string | undefined
|
|
582
|
+
});
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
513
586
|
// List or Run or Details
|
|
514
587
|
if (name) {
|
|
515
588
|
if (!values.command && !values.directory) {
|
|
@@ -541,5 +614,10 @@ async function run() {
|
|
|
541
614
|
}
|
|
542
615
|
|
|
543
616
|
run().catch(err => {
|
|
544
|
-
error(
|
|
617
|
+
// BgrunError was already printed by error() — just exit
|
|
618
|
+
// For unexpected errors, print and exit
|
|
619
|
+
if (err.name !== 'BgrunError') {
|
|
620
|
+
console.error(err);
|
|
621
|
+
}
|
|
622
|
+
process.exit(1);
|
|
545
623
|
});
|