bgrun 3.12.24 → 3.12.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -8
- package/dist/api.js +86 -8
- package/dist/deploy.js +86 -8
- package/dist/deps.js +1 -1
- package/dist/index.js +126 -18
- package/dist/server.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -719,16 +719,24 @@ Not directly — bgrun manages processes on the local machine. For remote manage
|
|
|
719
719
|
|
|
720
720
|
---
|
|
721
721
|
|
|
722
|
-
## Custom Log Paths
|
|
723
|
-
|
|
724
|
-
By default, logs go to `~/.bgr/<name>-out.txt` and `~/.bgr/<name>-err.txt`. Override with:
|
|
722
|
+
## Custom Log Paths
|
|
723
|
+
|
|
724
|
+
By default, logs go to `~/.bgr/<name>-out.txt` and `~/.bgr/<name>-err.txt`. Override with:
|
|
725
725
|
|
|
726
726
|
```bash
|
|
727
|
-
bgrun --name api \
|
|
728
|
-
--command "bun run server.ts" \
|
|
729
|
-
--stdout /var/log/api/stdout.log \
|
|
730
|
-
--stderr /var/log/api/stderr.log
|
|
731
|
-
```
|
|
727
|
+
bgrun --name api \
|
|
728
|
+
--command "bun run server.ts" \
|
|
729
|
+
--stdout /var/log/api/stdout.log \
|
|
730
|
+
--stderr /var/log/api/stderr.log
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
Or provide a directory and let `bgrun` derive both log filenames from the process name:
|
|
734
|
+
|
|
735
|
+
```bash
|
|
736
|
+
bgrun --name api \
|
|
737
|
+
--command "bun run server.ts" \
|
|
738
|
+
--logs-dir /var/log/api
|
|
739
|
+
```
|
|
732
740
|
|
|
733
741
|
---
|
|
734
742
|
|
package/dist/api.js
CHANGED
|
@@ -240,7 +240,7 @@ function ensureDir(dirPath) {
|
|
|
240
240
|
}
|
|
241
241
|
function getShellCommand(command) {
|
|
242
242
|
if (isWindows()) {
|
|
243
|
-
return ["cmd", "/c", command];
|
|
243
|
+
return [process.env.ComSpec || "cmd.exe", "/c", command];
|
|
244
244
|
} else {
|
|
245
245
|
return ["sh", "-c", command];
|
|
246
246
|
}
|
|
@@ -1184,7 +1184,8 @@ async function loadConfigEnv(directory, configPath = ".config.toml") {
|
|
|
1184
1184
|
// src/commands/run.ts
|
|
1185
1185
|
var {$: $2 } = globalThis.Bun;
|
|
1186
1186
|
var {sleep: sleep2 } = globalThis.Bun;
|
|
1187
|
-
import {
|
|
1187
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
1188
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
1188
1189
|
import { createMeasure as createMeasure2 } from "measure-fn";
|
|
1189
1190
|
|
|
1190
1191
|
// src/watcher.ts
|
|
@@ -1427,6 +1428,78 @@ function getRecentGuardEvents(limit = 100) {
|
|
|
1427
1428
|
var homePath2 = getHomeDir();
|
|
1428
1429
|
var run = createMeasure2("run");
|
|
1429
1430
|
var INTERNAL_BUNX_PREFIX = "bunx bgrun";
|
|
1431
|
+
async function resolveSpawnedProcessPid(parentPid, command, workdir) {
|
|
1432
|
+
if (parentPid <= 0)
|
|
1433
|
+
return 0;
|
|
1434
|
+
let candidatePid = parentPid;
|
|
1435
|
+
const spawnedAt = Date.now();
|
|
1436
|
+
for (let attempt = 0;attempt < 6; attempt++) {
|
|
1437
|
+
const descendantPid = await findChildPid(parentPid);
|
|
1438
|
+
if (descendantPid > 0) {
|
|
1439
|
+
candidatePid = descendantPid;
|
|
1440
|
+
}
|
|
1441
|
+
if (candidatePid > 0 && await isProcessRunning(candidatePid, command)) {
|
|
1442
|
+
return candidatePid;
|
|
1443
|
+
}
|
|
1444
|
+
await sleep2(250);
|
|
1445
|
+
}
|
|
1446
|
+
const reconciled = await reconcileProcessPids([{ name: "__spawn__", pid: parentPid, command, workdir }], new Set([parentPid]));
|
|
1447
|
+
const matchedPid = reconciled.get("__spawn__") ?? 0;
|
|
1448
|
+
if (matchedPid > 0 && await isProcessRunning(matchedPid, command)) {
|
|
1449
|
+
return matchedPid;
|
|
1450
|
+
}
|
|
1451
|
+
if (process.platform === "win32") {
|
|
1452
|
+
try {
|
|
1453
|
+
const commandParts = command.toLowerCase().split(/\s+/).map((part) => part.trim()).filter((part) => part.length > 2);
|
|
1454
|
+
const output = await psExec(`Get-CimInstance Win32_Process -Filter "Name='bun.exe'" | ` + `ForEach-Object { Write-Output "$($_.ProcessId)|$($_.ParentProcessId)|$($_.CreationDate)|$($_.CommandLine)" }`, 5000);
|
|
1455
|
+
let bestPid = 0;
|
|
1456
|
+
let bestScore = -1;
|
|
1457
|
+
for (const line of output.split(`
|
|
1458
|
+
`)) {
|
|
1459
|
+
const parts = line.split("|");
|
|
1460
|
+
if (parts.length < 4)
|
|
1461
|
+
continue;
|
|
1462
|
+
const pid = parseInt(parts[0]?.trim(), 10);
|
|
1463
|
+
const candidateParentPid = parseInt(parts[1]?.trim(), 10);
|
|
1464
|
+
const creationDateRaw = parts[2]?.trim() || "";
|
|
1465
|
+
const candidateCommand = parts.slice(3).join("|").trim().toLowerCase();
|
|
1466
|
+
if (isNaN(pid) || pid <= 0 || pid === process.pid)
|
|
1467
|
+
continue;
|
|
1468
|
+
if (!candidateCommand)
|
|
1469
|
+
continue;
|
|
1470
|
+
let score = 0;
|
|
1471
|
+
if (candidateParentPid === parentPid)
|
|
1472
|
+
score += 10;
|
|
1473
|
+
for (const part of commandParts) {
|
|
1474
|
+
if (candidateCommand.includes(part))
|
|
1475
|
+
score += 2;
|
|
1476
|
+
}
|
|
1477
|
+
if (candidateCommand.includes("run server.ts"))
|
|
1478
|
+
score += 2;
|
|
1479
|
+
if (candidateCommand.includes(workdir.toLowerCase().replace(/\\/g, "/")))
|
|
1480
|
+
score += 4;
|
|
1481
|
+
if (candidateCommand.includes(workdir.toLowerCase()))
|
|
1482
|
+
score += 4;
|
|
1483
|
+
const createdAt = creationDateRaw ? Date.parse(creationDateRaw) : NaN;
|
|
1484
|
+
if (!isNaN(createdAt)) {
|
|
1485
|
+
const ageMs = Math.abs(createdAt - spawnedAt);
|
|
1486
|
+
if (ageMs <= 15000)
|
|
1487
|
+
score += 6;
|
|
1488
|
+
else if (ageMs <= 60000)
|
|
1489
|
+
score += 2;
|
|
1490
|
+
}
|
|
1491
|
+
if (score > bestScore && await isProcessRunning(pid, command)) {
|
|
1492
|
+
bestScore = score;
|
|
1493
|
+
bestPid = pid;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
if (bestScore >= 4) {
|
|
1497
|
+
return bestPid;
|
|
1498
|
+
}
|
|
1499
|
+
} catch {}
|
|
1500
|
+
}
|
|
1501
|
+
return 0;
|
|
1502
|
+
}
|
|
1430
1503
|
function resolveInternalBgrunCommand(command) {
|
|
1431
1504
|
const trimmed = command.trim();
|
|
1432
1505
|
if (!trimmed.startsWith("bgrun --_") && !trimmed.startsWith(`${INTERNAL_BUNX_PREFIX} --_`)) {
|
|
@@ -1446,6 +1519,7 @@ async function handleRun(options) {
|
|
|
1446
1519
|
configPath,
|
|
1447
1520
|
force,
|
|
1448
1521
|
fetch,
|
|
1522
|
+
logsDir,
|
|
1449
1523
|
stdout,
|
|
1450
1524
|
stderr
|
|
1451
1525
|
} = options;
|
|
@@ -1625,9 +1699,11 @@ async function handleRun(options) {
|
|
|
1625
1699
|
console.log(`Config file '${finalConfigPath}' not found, continuing without it.`);
|
|
1626
1700
|
}
|
|
1627
1701
|
}
|
|
1628
|
-
const stdoutPath = stdout || existingProcess?.stdout_path || join5(homePath2, ".bgr", `${name}-out.txt`);
|
|
1702
|
+
const stdoutPath = stdout || (logsDir ? join5(logsDir, `${name}-out.txt`) : undefined) || existingProcess?.stdout_path || join5(homePath2, ".bgr", `${name}-out.txt`);
|
|
1703
|
+
mkdirSync3(dirname2(stdoutPath), { recursive: true });
|
|
1629
1704
|
Bun.write(stdoutPath, "");
|
|
1630
|
-
const stderrPath = stderr || existingProcess?.stderr_path || join5(homePath2, ".bgr", `${name}-err.txt`);
|
|
1705
|
+
const stderrPath = stderr || (logsDir ? join5(logsDir, `${name}-err.txt`) : undefined) || existingProcess?.stderr_path || join5(homePath2, ".bgr", `${name}-err.txt`);
|
|
1706
|
+
mkdirSync3(dirname2(stderrPath), { recursive: true });
|
|
1631
1707
|
Bun.write(stderrPath, "");
|
|
1632
1708
|
const actualPid = await run.measure(`Spawn "${name}" \u2192 ${finalCommand}`, async () => {
|
|
1633
1709
|
const newProcess = Bun.spawn(getShellCommand(finalCommand), {
|
|
@@ -1637,11 +1713,13 @@ async function handleRun(options) {
|
|
|
1637
1713
|
stderr: Bun.file(stderrPath)
|
|
1638
1714
|
});
|
|
1639
1715
|
newProcess.unref();
|
|
1640
|
-
await
|
|
1641
|
-
const pid = await findChildPid(newProcess.pid);
|
|
1642
|
-
await sleep2(400);
|
|
1643
|
-
return pid;
|
|
1716
|
+
return await resolveSpawnedProcessPid(newProcess.pid, finalCommand, finalDirectory);
|
|
1644
1717
|
}) ?? 0;
|
|
1718
|
+
if (actualPid <= 0) {
|
|
1719
|
+
throw new Error(`Failed to resolve a live PID for "${name}" after launch. The child process likely exited immediately. Check logs:
|
|
1720
|
+
stdout: ${stdoutPath}
|
|
1721
|
+
stderr: ${stderrPath}`);
|
|
1722
|
+
}
|
|
1645
1723
|
await retryDatabaseOperation(() => insertProcess({
|
|
1646
1724
|
pid: actualPid,
|
|
1647
1725
|
workdir: finalDirectory,
|
package/dist/deploy.js
CHANGED
|
@@ -240,7 +240,7 @@ function ensureDir(dirPath) {
|
|
|
240
240
|
}
|
|
241
241
|
function getShellCommand(command) {
|
|
242
242
|
if (isWindows()) {
|
|
243
|
-
return ["cmd", "/c", command];
|
|
243
|
+
return [process.env.ComSpec || "cmd.exe", "/c", command];
|
|
244
244
|
} else {
|
|
245
245
|
return ["sh", "-c", command];
|
|
246
246
|
}
|
|
@@ -1183,7 +1183,8 @@ async function loadConfigEnv(directory, configPath = ".config.toml") {
|
|
|
1183
1183
|
// src/commands/run.ts
|
|
1184
1184
|
var {$: $2 } = globalThis.Bun;
|
|
1185
1185
|
var {sleep: sleep2 } = globalThis.Bun;
|
|
1186
|
-
import {
|
|
1186
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
1187
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
1187
1188
|
import { createMeasure as createMeasure2 } from "measure-fn";
|
|
1188
1189
|
|
|
1189
1190
|
// src/watcher.ts
|
|
@@ -1426,6 +1427,78 @@ function getRecentGuardEvents(limit = 100) {
|
|
|
1426
1427
|
var homePath2 = getHomeDir();
|
|
1427
1428
|
var run = createMeasure2("run");
|
|
1428
1429
|
var INTERNAL_BUNX_PREFIX = "bunx bgrun";
|
|
1430
|
+
async function resolveSpawnedProcessPid(parentPid, command, workdir) {
|
|
1431
|
+
if (parentPid <= 0)
|
|
1432
|
+
return 0;
|
|
1433
|
+
let candidatePid = parentPid;
|
|
1434
|
+
const spawnedAt = Date.now();
|
|
1435
|
+
for (let attempt = 0;attempt < 6; attempt++) {
|
|
1436
|
+
const descendantPid = await findChildPid(parentPid);
|
|
1437
|
+
if (descendantPid > 0) {
|
|
1438
|
+
candidatePid = descendantPid;
|
|
1439
|
+
}
|
|
1440
|
+
if (candidatePid > 0 && await isProcessRunning(candidatePid, command)) {
|
|
1441
|
+
return candidatePid;
|
|
1442
|
+
}
|
|
1443
|
+
await sleep2(250);
|
|
1444
|
+
}
|
|
1445
|
+
const reconciled = await reconcileProcessPids([{ name: "__spawn__", pid: parentPid, command, workdir }], new Set([parentPid]));
|
|
1446
|
+
const matchedPid = reconciled.get("__spawn__") ?? 0;
|
|
1447
|
+
if (matchedPid > 0 && await isProcessRunning(matchedPid, command)) {
|
|
1448
|
+
return matchedPid;
|
|
1449
|
+
}
|
|
1450
|
+
if (process.platform === "win32") {
|
|
1451
|
+
try {
|
|
1452
|
+
const commandParts = command.toLowerCase().split(/\s+/).map((part) => part.trim()).filter((part) => part.length > 2);
|
|
1453
|
+
const output = await psExec(`Get-CimInstance Win32_Process -Filter "Name='bun.exe'" | ` + `ForEach-Object { Write-Output "$($_.ProcessId)|$($_.ParentProcessId)|$($_.CreationDate)|$($_.CommandLine)" }`, 5000);
|
|
1454
|
+
let bestPid = 0;
|
|
1455
|
+
let bestScore = -1;
|
|
1456
|
+
for (const line of output.split(`
|
|
1457
|
+
`)) {
|
|
1458
|
+
const parts = line.split("|");
|
|
1459
|
+
if (parts.length < 4)
|
|
1460
|
+
continue;
|
|
1461
|
+
const pid = parseInt(parts[0]?.trim(), 10);
|
|
1462
|
+
const candidateParentPid = parseInt(parts[1]?.trim(), 10);
|
|
1463
|
+
const creationDateRaw = parts[2]?.trim() || "";
|
|
1464
|
+
const candidateCommand = parts.slice(3).join("|").trim().toLowerCase();
|
|
1465
|
+
if (isNaN(pid) || pid <= 0 || pid === process.pid)
|
|
1466
|
+
continue;
|
|
1467
|
+
if (!candidateCommand)
|
|
1468
|
+
continue;
|
|
1469
|
+
let score = 0;
|
|
1470
|
+
if (candidateParentPid === parentPid)
|
|
1471
|
+
score += 10;
|
|
1472
|
+
for (const part of commandParts) {
|
|
1473
|
+
if (candidateCommand.includes(part))
|
|
1474
|
+
score += 2;
|
|
1475
|
+
}
|
|
1476
|
+
if (candidateCommand.includes("run server.ts"))
|
|
1477
|
+
score += 2;
|
|
1478
|
+
if (candidateCommand.includes(workdir.toLowerCase().replace(/\\/g, "/")))
|
|
1479
|
+
score += 4;
|
|
1480
|
+
if (candidateCommand.includes(workdir.toLowerCase()))
|
|
1481
|
+
score += 4;
|
|
1482
|
+
const createdAt = creationDateRaw ? Date.parse(creationDateRaw) : NaN;
|
|
1483
|
+
if (!isNaN(createdAt)) {
|
|
1484
|
+
const ageMs = Math.abs(createdAt - spawnedAt);
|
|
1485
|
+
if (ageMs <= 15000)
|
|
1486
|
+
score += 6;
|
|
1487
|
+
else if (ageMs <= 60000)
|
|
1488
|
+
score += 2;
|
|
1489
|
+
}
|
|
1490
|
+
if (score > bestScore && await isProcessRunning(pid, command)) {
|
|
1491
|
+
bestScore = score;
|
|
1492
|
+
bestPid = pid;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
if (bestScore >= 4) {
|
|
1496
|
+
return bestPid;
|
|
1497
|
+
}
|
|
1498
|
+
} catch {}
|
|
1499
|
+
}
|
|
1500
|
+
return 0;
|
|
1501
|
+
}
|
|
1429
1502
|
function resolveInternalBgrunCommand(command) {
|
|
1430
1503
|
const trimmed = command.trim();
|
|
1431
1504
|
if (!trimmed.startsWith("bgrun --_") && !trimmed.startsWith(`${INTERNAL_BUNX_PREFIX} --_`)) {
|
|
@@ -1445,6 +1518,7 @@ async function handleRun(options) {
|
|
|
1445
1518
|
configPath,
|
|
1446
1519
|
force,
|
|
1447
1520
|
fetch,
|
|
1521
|
+
logsDir,
|
|
1448
1522
|
stdout,
|
|
1449
1523
|
stderr
|
|
1450
1524
|
} = options;
|
|
@@ -1624,9 +1698,11 @@ async function handleRun(options) {
|
|
|
1624
1698
|
console.log(`Config file '${finalConfigPath}' not found, continuing without it.`);
|
|
1625
1699
|
}
|
|
1626
1700
|
}
|
|
1627
|
-
const stdoutPath = stdout || existingProcess?.stdout_path || join5(homePath2, ".bgr", `${name}-out.txt`);
|
|
1701
|
+
const stdoutPath = stdout || (logsDir ? join5(logsDir, `${name}-out.txt`) : undefined) || existingProcess?.stdout_path || join5(homePath2, ".bgr", `${name}-out.txt`);
|
|
1702
|
+
mkdirSync3(dirname2(stdoutPath), { recursive: true });
|
|
1628
1703
|
Bun.write(stdoutPath, "");
|
|
1629
|
-
const stderrPath = stderr || existingProcess?.stderr_path || join5(homePath2, ".bgr", `${name}-err.txt`);
|
|
1704
|
+
const stderrPath = stderr || (logsDir ? join5(logsDir, `${name}-err.txt`) : undefined) || existingProcess?.stderr_path || join5(homePath2, ".bgr", `${name}-err.txt`);
|
|
1705
|
+
mkdirSync3(dirname2(stderrPath), { recursive: true });
|
|
1630
1706
|
Bun.write(stderrPath, "");
|
|
1631
1707
|
const actualPid = await run.measure(`Spawn "${name}" \u2192 ${finalCommand}`, async () => {
|
|
1632
1708
|
const newProcess = Bun.spawn(getShellCommand(finalCommand), {
|
|
@@ -1636,11 +1712,13 @@ async function handleRun(options) {
|
|
|
1636
1712
|
stderr: Bun.file(stderrPath)
|
|
1637
1713
|
});
|
|
1638
1714
|
newProcess.unref();
|
|
1639
|
-
await
|
|
1640
|
-
const pid = await findChildPid(newProcess.pid);
|
|
1641
|
-
await sleep2(400);
|
|
1642
|
-
return pid;
|
|
1715
|
+
return await resolveSpawnedProcessPid(newProcess.pid, finalCommand, finalDirectory);
|
|
1643
1716
|
}) ?? 0;
|
|
1717
|
+
if (actualPid <= 0) {
|
|
1718
|
+
throw new Error(`Failed to resolve a live PID for "${name}" after launch. The child process likely exited immediately. Check logs:
|
|
1719
|
+
stdout: ${stdoutPath}
|
|
1720
|
+
stderr: ${stderrPath}`);
|
|
1721
|
+
}
|
|
1644
1722
|
await retryDatabaseOperation(() => insertProcess({
|
|
1645
1723
|
pid: actualPid,
|
|
1646
1724
|
workdir: finalDirectory,
|
package/dist/deps.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -241,7 +241,7 @@ function ensureDir(dirPath) {
|
|
|
241
241
|
}
|
|
242
242
|
function getShellCommand(command) {
|
|
243
243
|
if (isWindows()) {
|
|
244
|
-
return ["cmd", "/c", command];
|
|
244
|
+
return [process.env.ComSpec || "cmd.exe", "/c", command];
|
|
245
245
|
} else {
|
|
246
246
|
return ["sh", "-c", command];
|
|
247
247
|
}
|
|
@@ -1344,7 +1344,8 @@ async function loadConfigEnv(directory, configPath = ".config.toml") {
|
|
|
1344
1344
|
// src/commands/run.ts
|
|
1345
1345
|
var {$: $2 } = globalThis.Bun;
|
|
1346
1346
|
var {sleep: sleep2 } = globalThis.Bun;
|
|
1347
|
-
import {
|
|
1347
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
1348
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
1348
1349
|
import { createMeasure as createMeasure2 } from "measure-fn";
|
|
1349
1350
|
|
|
1350
1351
|
// src/watcher.ts
|
|
@@ -1587,6 +1588,78 @@ function getRecentGuardEvents(limit = 100) {
|
|
|
1587
1588
|
var homePath2 = getHomeDir();
|
|
1588
1589
|
var run = createMeasure2("run");
|
|
1589
1590
|
var INTERNAL_BUNX_PREFIX = "bunx bgrun";
|
|
1591
|
+
async function resolveSpawnedProcessPid(parentPid, command, workdir) {
|
|
1592
|
+
if (parentPid <= 0)
|
|
1593
|
+
return 0;
|
|
1594
|
+
let candidatePid = parentPid;
|
|
1595
|
+
const spawnedAt = Date.now();
|
|
1596
|
+
for (let attempt = 0;attempt < 6; attempt++) {
|
|
1597
|
+
const descendantPid = await findChildPid(parentPid);
|
|
1598
|
+
if (descendantPid > 0) {
|
|
1599
|
+
candidatePid = descendantPid;
|
|
1600
|
+
}
|
|
1601
|
+
if (candidatePid > 0 && await isProcessRunning(candidatePid, command)) {
|
|
1602
|
+
return candidatePid;
|
|
1603
|
+
}
|
|
1604
|
+
await sleep2(250);
|
|
1605
|
+
}
|
|
1606
|
+
const reconciled = await reconcileProcessPids([{ name: "__spawn__", pid: parentPid, command, workdir }], new Set([parentPid]));
|
|
1607
|
+
const matchedPid = reconciled.get("__spawn__") ?? 0;
|
|
1608
|
+
if (matchedPid > 0 && await isProcessRunning(matchedPid, command)) {
|
|
1609
|
+
return matchedPid;
|
|
1610
|
+
}
|
|
1611
|
+
if (process.platform === "win32") {
|
|
1612
|
+
try {
|
|
1613
|
+
const commandParts = command.toLowerCase().split(/\s+/).map((part) => part.trim()).filter((part) => part.length > 2);
|
|
1614
|
+
const output = await psExec(`Get-CimInstance Win32_Process -Filter "Name='bun.exe'" | ` + `ForEach-Object { Write-Output "$($_.ProcessId)|$($_.ParentProcessId)|$($_.CreationDate)|$($_.CommandLine)" }`, 5000);
|
|
1615
|
+
let bestPid = 0;
|
|
1616
|
+
let bestScore = -1;
|
|
1617
|
+
for (const line of output.split(`
|
|
1618
|
+
`)) {
|
|
1619
|
+
const parts = line.split("|");
|
|
1620
|
+
if (parts.length < 4)
|
|
1621
|
+
continue;
|
|
1622
|
+
const pid = parseInt(parts[0]?.trim(), 10);
|
|
1623
|
+
const candidateParentPid = parseInt(parts[1]?.trim(), 10);
|
|
1624
|
+
const creationDateRaw = parts[2]?.trim() || "";
|
|
1625
|
+
const candidateCommand = parts.slice(3).join("|").trim().toLowerCase();
|
|
1626
|
+
if (isNaN(pid) || pid <= 0 || pid === process.pid)
|
|
1627
|
+
continue;
|
|
1628
|
+
if (!candidateCommand)
|
|
1629
|
+
continue;
|
|
1630
|
+
let score = 0;
|
|
1631
|
+
if (candidateParentPid === parentPid)
|
|
1632
|
+
score += 10;
|
|
1633
|
+
for (const part of commandParts) {
|
|
1634
|
+
if (candidateCommand.includes(part))
|
|
1635
|
+
score += 2;
|
|
1636
|
+
}
|
|
1637
|
+
if (candidateCommand.includes("run server.ts"))
|
|
1638
|
+
score += 2;
|
|
1639
|
+
if (candidateCommand.includes(workdir.toLowerCase().replace(/\\/g, "/")))
|
|
1640
|
+
score += 4;
|
|
1641
|
+
if (candidateCommand.includes(workdir.toLowerCase()))
|
|
1642
|
+
score += 4;
|
|
1643
|
+
const createdAt = creationDateRaw ? Date.parse(creationDateRaw) : NaN;
|
|
1644
|
+
if (!isNaN(createdAt)) {
|
|
1645
|
+
const ageMs = Math.abs(createdAt - spawnedAt);
|
|
1646
|
+
if (ageMs <= 15000)
|
|
1647
|
+
score += 6;
|
|
1648
|
+
else if (ageMs <= 60000)
|
|
1649
|
+
score += 2;
|
|
1650
|
+
}
|
|
1651
|
+
if (score > bestScore && await isProcessRunning(pid, command)) {
|
|
1652
|
+
bestScore = score;
|
|
1653
|
+
bestPid = pid;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
if (bestScore >= 4) {
|
|
1657
|
+
return bestPid;
|
|
1658
|
+
}
|
|
1659
|
+
} catch {}
|
|
1660
|
+
}
|
|
1661
|
+
return 0;
|
|
1662
|
+
}
|
|
1590
1663
|
function resolveInternalBgrunCommand(command) {
|
|
1591
1664
|
const trimmed = command.trim();
|
|
1592
1665
|
if (!trimmed.startsWith("bgrun --_") && !trimmed.startsWith(`${INTERNAL_BUNX_PREFIX} --_`)) {
|
|
@@ -1606,6 +1679,7 @@ async function handleRun(options) {
|
|
|
1606
1679
|
configPath,
|
|
1607
1680
|
force,
|
|
1608
1681
|
fetch,
|
|
1682
|
+
logsDir,
|
|
1609
1683
|
stdout,
|
|
1610
1684
|
stderr
|
|
1611
1685
|
} = options;
|
|
@@ -1785,9 +1859,11 @@ async function handleRun(options) {
|
|
|
1785
1859
|
console.log(`Config file '${finalConfigPath}' not found, continuing without it.`);
|
|
1786
1860
|
}
|
|
1787
1861
|
}
|
|
1788
|
-
const stdoutPath = stdout || existingProcess?.stdout_path || join5(homePath2, ".bgr", `${name}-out.txt`);
|
|
1862
|
+
const stdoutPath = stdout || (logsDir ? join5(logsDir, `${name}-out.txt`) : undefined) || existingProcess?.stdout_path || join5(homePath2, ".bgr", `${name}-out.txt`);
|
|
1863
|
+
mkdirSync3(dirname2(stdoutPath), { recursive: true });
|
|
1789
1864
|
Bun.write(stdoutPath, "");
|
|
1790
|
-
const stderrPath = stderr || existingProcess?.stderr_path || join5(homePath2, ".bgr", `${name}-err.txt`);
|
|
1865
|
+
const stderrPath = stderr || (logsDir ? join5(logsDir, `${name}-err.txt`) : undefined) || existingProcess?.stderr_path || join5(homePath2, ".bgr", `${name}-err.txt`);
|
|
1866
|
+
mkdirSync3(dirname2(stderrPath), { recursive: true });
|
|
1791
1867
|
Bun.write(stderrPath, "");
|
|
1792
1868
|
const actualPid = await run.measure(`Spawn "${name}" \u2192 ${finalCommand}`, async () => {
|
|
1793
1869
|
const newProcess = Bun.spawn(getShellCommand(finalCommand), {
|
|
@@ -1797,11 +1873,13 @@ async function handleRun(options) {
|
|
|
1797
1873
|
stderr: Bun.file(stderrPath)
|
|
1798
1874
|
});
|
|
1799
1875
|
newProcess.unref();
|
|
1800
|
-
await
|
|
1801
|
-
const pid = await findChildPid(newProcess.pid);
|
|
1802
|
-
await sleep2(400);
|
|
1803
|
-
return pid;
|
|
1876
|
+
return await resolveSpawnedProcessPid(newProcess.pid, finalCommand, finalDirectory);
|
|
1804
1877
|
}) ?? 0;
|
|
1878
|
+
if (actualPid <= 0) {
|
|
1879
|
+
throw new Error(`Failed to resolve a live PID for "${name}" after launch. The child process likely exited immediately. Check logs:
|
|
1880
|
+
stdout: ${stdoutPath}
|
|
1881
|
+
stderr: ${stderrPath}`);
|
|
1882
|
+
}
|
|
1805
1883
|
await retryDatabaseOperation(() => insertProcess({
|
|
1806
1884
|
pid: actualPid,
|
|
1807
1885
|
workdir: finalDirectory,
|
|
@@ -2870,6 +2948,7 @@ async function showHelp() {
|
|
|
2870
2948
|
bunx bgrun --delete [name] Delete a process
|
|
2871
2949
|
bunx bgrun --clean Remove all stopped processes
|
|
2872
2950
|
bunx bgrun --nuke Delete ALL processes
|
|
2951
|
+
bunx bgrun --kill-port <n> Kill whatever is currently listening on a port
|
|
2873
2952
|
|
|
2874
2953
|
${chalk7.yellow("Options:")}
|
|
2875
2954
|
--name <string> Process name (required for new)
|
|
@@ -2882,6 +2961,7 @@ async function showHelp() {
|
|
|
2882
2961
|
--watch Watch for file changes and auto-restart
|
|
2883
2962
|
--hot Restart the managed process when files change
|
|
2884
2963
|
--force Force restart existing process
|
|
2964
|
+
--logs-dir <path> Directory for derived stdout/stderr logs
|
|
2885
2965
|
--fetch Fetch latest git changes before running
|
|
2886
2966
|
--json Output in JSON format
|
|
2887
2967
|
--filter <group> Filter list by BGR_GROUP
|
|
@@ -2894,36 +2974,41 @@ async function showHelp() {
|
|
|
2894
2974
|
--dashboard Launch web dashboard as bgrun-managed process
|
|
2895
2975
|
--guard Enable per-process crash watcher
|
|
2896
2976
|
--guard-off Disable per-process crash watcher
|
|
2977
|
+
--kill-port <number> Kill the process currently using a port
|
|
2897
2978
|
--port <number> Port for dashboard (default: 3000)
|
|
2898
2979
|
--help Show this help message
|
|
2899
2980
|
|
|
2900
2981
|
${chalk7.yellow("Examples:")}
|
|
2901
2982
|
bunx bgrun -- bun run dev
|
|
2902
2983
|
bunx bgrun --hot -- bun run index.ts
|
|
2984
|
+
bunx bgrun -hl -- bun run server.ts
|
|
2903
2985
|
bunx bgrun --no-config -- bun run script.ts
|
|
2904
2986
|
bunx bgrun --force -- bun run server.ts
|
|
2905
2987
|
bunx bgrun inline -- bun run dev
|
|
2906
2988
|
Invoke-Expression (bunx bgrun --env)
|
|
2907
2989
|
eval "$(bunx bgrun --env --shell sh)"
|
|
2908
2990
|
bunx bgrun --dashboard
|
|
2991
|
+
bunx bgrun --kill-port 3000
|
|
2909
2992
|
bunx bgrun myapp --guard
|
|
2910
2993
|
bunx bgrun myapp --guard-off
|
|
2911
2994
|
bunx bgrun --name myapp --command "bun run dev" --directory . --watch
|
|
2995
|
+
bunx bgrun --name myapp --logs-dir .data --command "bun run dev" --directory .
|
|
2912
2996
|
bunx bgrun myapp --logs --lines 50
|
|
2913
2997
|
`;
|
|
2914
2998
|
console.log(usage);
|
|
2915
2999
|
}
|
|
2916
3000
|
var cliArgOptions = {
|
|
2917
|
-
name: { type: "string" },
|
|
2918
|
-
command: { type: "string" },
|
|
2919
|
-
directory: { type: "string" },
|
|
3001
|
+
name: { type: "string", short: "n" },
|
|
3002
|
+
command: { type: "string", short: "c" },
|
|
3003
|
+
directory: { type: "string", short: "d" },
|
|
2920
3004
|
config: { type: "string" },
|
|
2921
3005
|
"no-config": { type: "boolean" },
|
|
2922
3006
|
env: { type: "boolean" },
|
|
2923
3007
|
shell: { type: "string" },
|
|
2924
|
-
watch: { type: "boolean" },
|
|
2925
|
-
hot: { type: "boolean" },
|
|
2926
|
-
force: { type: "boolean" },
|
|
3008
|
+
watch: { type: "boolean", short: "w" },
|
|
3009
|
+
hot: { type: "boolean", short: "h" },
|
|
3010
|
+
force: { type: "boolean", short: "f" },
|
|
3011
|
+
"logs-dir": { type: "string" },
|
|
2927
3012
|
fetch: { type: "boolean" },
|
|
2928
3013
|
delete: { type: "boolean" },
|
|
2929
3014
|
nuke: { type: "boolean" },
|
|
@@ -2932,13 +3017,13 @@ var cliArgOptions = {
|
|
|
2932
3017
|
stop: { type: "boolean" },
|
|
2933
3018
|
"stop-all": { type: "boolean" },
|
|
2934
3019
|
clean: { type: "boolean" },
|
|
2935
|
-
json: { type: "boolean" },
|
|
2936
|
-
logs: { type: "boolean" },
|
|
3020
|
+
json: { type: "boolean", short: "j" },
|
|
3021
|
+
logs: { type: "boolean", short: "l" },
|
|
2937
3022
|
"log-stdout": { type: "boolean" },
|
|
2938
3023
|
"log-stderr": { type: "boolean" },
|
|
2939
3024
|
lines: { type: "string" },
|
|
2940
3025
|
filter: { type: "string" },
|
|
2941
|
-
version: { type: "boolean" },
|
|
3026
|
+
version: { type: "boolean", short: "v" },
|
|
2942
3027
|
help: { type: "boolean" },
|
|
2943
3028
|
db: { type: "string" },
|
|
2944
3029
|
stdout: { type: "string" },
|
|
@@ -2947,6 +3032,7 @@ var cliArgOptions = {
|
|
|
2947
3032
|
guard: { type: "boolean" },
|
|
2948
3033
|
"guard-off": { type: "boolean" },
|
|
2949
3034
|
debug: { type: "boolean" },
|
|
3035
|
+
"kill-port": { type: "string" },
|
|
2950
3036
|
_serve: { type: "boolean" },
|
|
2951
3037
|
"_watch-process": { type: "string" },
|
|
2952
3038
|
port: { type: "string" }
|
|
@@ -2954,7 +3040,7 @@ var cliArgOptions = {
|
|
|
2954
3040
|
async function run2() {
|
|
2955
3041
|
const rawArgs = Bun.argv.slice(2);
|
|
2956
3042
|
const isActionInvocation = (values2) => {
|
|
2957
|
-
return Boolean(values2.dashboard || values2.guard || values2["guard-off"] || values2.version || values2.help || values2.debug || values2.nuke || values2.clean || values2["restart-all"] || values2["stop-all"] || values2.delete || values2.restart || values2.stop || values2.logs || values2["log-stdout"] || values2["log-stderr"] || values2.watch || values2.hot || values2.json || values2.filter);
|
|
3043
|
+
return Boolean(values2.dashboard || values2.guard || values2["guard-off"] || values2.version || values2.help || values2.debug || values2["kill-port"] || values2.nuke || values2.clean || values2["restart-all"] || values2["stop-all"] || values2.delete || values2.restart || values2.stop || values2.logs || values2["log-stdout"] || values2["log-stderr"] || values2.watch || values2.hot || values2.json || values2.filter);
|
|
2958
3044
|
};
|
|
2959
3045
|
if (rawArgs[0] === "inline") {
|
|
2960
3046
|
const parsed = parseInlineArgs(rawArgs.slice(1));
|
|
@@ -2999,6 +3085,7 @@ async function run2() {
|
|
|
2999
3085
|
configPath: values2["no-config"] ? "" : values2.config,
|
|
3000
3086
|
force: values2.force,
|
|
3001
3087
|
fetch: values2.fetch,
|
|
3088
|
+
logsDir: values2["logs-dir"],
|
|
3002
3089
|
remoteName: "",
|
|
3003
3090
|
dbPath: values2.db,
|
|
3004
3091
|
stdout: values2.stdout,
|
|
@@ -3045,6 +3132,7 @@ async function run2() {
|
|
|
3045
3132
|
configPath: values["no-config"] ? "" : values.config,
|
|
3046
3133
|
force: values.force,
|
|
3047
3134
|
fetch: values.fetch,
|
|
3135
|
+
logsDir: values["logs-dir"],
|
|
3048
3136
|
remoteName: "",
|
|
3049
3137
|
dbPath: values.db,
|
|
3050
3138
|
stdout: values.stdout,
|
|
@@ -3227,6 +3315,24 @@ async function run2() {
|
|
|
3227
3315
|
`);
|
|
3228
3316
|
return;
|
|
3229
3317
|
}
|
|
3318
|
+
if (values["kill-port"]) {
|
|
3319
|
+
const port = parseInt(String(values["kill-port"]), 10);
|
|
3320
|
+
if (isNaN(port) || port <= 0) {
|
|
3321
|
+
error("Please provide a valid port number for --kill-port.");
|
|
3322
|
+
}
|
|
3323
|
+
const wasFree = await isPortFree(port);
|
|
3324
|
+
if (wasFree) {
|
|
3325
|
+
announce(`Port ${port} is already free.`, "Port Free");
|
|
3326
|
+
return;
|
|
3327
|
+
}
|
|
3328
|
+
await killProcessOnPort(port);
|
|
3329
|
+
const freed = await waitForPortFree(port, 5000);
|
|
3330
|
+
if (!freed) {
|
|
3331
|
+
error(`Port ${port} is still busy after attempted cleanup.`);
|
|
3332
|
+
}
|
|
3333
|
+
announce(`Freed port ${port}.`, "Port Cleanup");
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3230
3336
|
if (values.nuke) {
|
|
3231
3337
|
await handleDeleteAll();
|
|
3232
3338
|
return;
|
|
@@ -3335,6 +3441,7 @@ async function run2() {
|
|
|
3335
3441
|
directory: values.directory,
|
|
3336
3442
|
configPath: values.config,
|
|
3337
3443
|
force: values.force,
|
|
3444
|
+
logsDir: values["logs-dir"],
|
|
3338
3445
|
remoteName: "",
|
|
3339
3446
|
dbPath: values.db,
|
|
3340
3447
|
stdout: values.stdout,
|
|
@@ -3365,6 +3472,7 @@ async function run2() {
|
|
|
3365
3472
|
configPath: values["no-config"] ? "" : values.config,
|
|
3366
3473
|
force: values.force,
|
|
3367
3474
|
fetch: values.fetch,
|
|
3475
|
+
logsDir: values["logs-dir"],
|
|
3368
3476
|
remoteName: "",
|
|
3369
3477
|
dbPath: values.db,
|
|
3370
3478
|
stdout: values.stdout,
|
package/dist/server.js
CHANGED