bgrun 3.12.24 → 3.12.25

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/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
  }
@@ -1427,6 +1427,78 @@ function getRecentGuardEvents(limit = 100) {
1427
1427
  var homePath2 = getHomeDir();
1428
1428
  var run = createMeasure2("run");
1429
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
+ }
1430
1502
  function resolveInternalBgrunCommand(command) {
1431
1503
  const trimmed = command.trim();
1432
1504
  if (!trimmed.startsWith("bgrun --_") && !trimmed.startsWith(`${INTERNAL_BUNX_PREFIX} --_`)) {
@@ -1637,11 +1709,13 @@ async function handleRun(options) {
1637
1709
  stderr: Bun.file(stderrPath)
1638
1710
  });
1639
1711
  newProcess.unref();
1640
- await sleep2(100);
1641
- const pid = await findChildPid(newProcess.pid);
1642
- await sleep2(400);
1643
- return pid;
1712
+ return await resolveSpawnedProcessPid(newProcess.pid, finalCommand, finalDirectory);
1644
1713
  }) ?? 0;
1714
+ if (actualPid <= 0) {
1715
+ throw new Error(`Failed to resolve a live PID for "${name}" after launch. The child process likely exited immediately. Check logs:
1716
+ stdout: ${stdoutPath}
1717
+ stderr: ${stderrPath}`);
1718
+ }
1645
1719
  await retryDatabaseOperation(() => insertProcess({
1646
1720
  pid: actualPid,
1647
1721
  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
  }
@@ -1426,6 +1426,78 @@ function getRecentGuardEvents(limit = 100) {
1426
1426
  var homePath2 = getHomeDir();
1427
1427
  var run = createMeasure2("run");
1428
1428
  var INTERNAL_BUNX_PREFIX = "bunx bgrun";
1429
+ async function resolveSpawnedProcessPid(parentPid, command, workdir) {
1430
+ if (parentPid <= 0)
1431
+ return 0;
1432
+ let candidatePid = parentPid;
1433
+ const spawnedAt = Date.now();
1434
+ for (let attempt = 0;attempt < 6; attempt++) {
1435
+ const descendantPid = await findChildPid(parentPid);
1436
+ if (descendantPid > 0) {
1437
+ candidatePid = descendantPid;
1438
+ }
1439
+ if (candidatePid > 0 && await isProcessRunning(candidatePid, command)) {
1440
+ return candidatePid;
1441
+ }
1442
+ await sleep2(250);
1443
+ }
1444
+ const reconciled = await reconcileProcessPids([{ name: "__spawn__", pid: parentPid, command, workdir }], new Set([parentPid]));
1445
+ const matchedPid = reconciled.get("__spawn__") ?? 0;
1446
+ if (matchedPid > 0 && await isProcessRunning(matchedPid, command)) {
1447
+ return matchedPid;
1448
+ }
1449
+ if (process.platform === "win32") {
1450
+ try {
1451
+ const commandParts = command.toLowerCase().split(/\s+/).map((part) => part.trim()).filter((part) => part.length > 2);
1452
+ const output = await psExec(`Get-CimInstance Win32_Process -Filter "Name='bun.exe'" | ` + `ForEach-Object { Write-Output "$($_.ProcessId)|$($_.ParentProcessId)|$($_.CreationDate)|$($_.CommandLine)" }`, 5000);
1453
+ let bestPid = 0;
1454
+ let bestScore = -1;
1455
+ for (const line of output.split(`
1456
+ `)) {
1457
+ const parts = line.split("|");
1458
+ if (parts.length < 4)
1459
+ continue;
1460
+ const pid = parseInt(parts[0]?.trim(), 10);
1461
+ const candidateParentPid = parseInt(parts[1]?.trim(), 10);
1462
+ const creationDateRaw = parts[2]?.trim() || "";
1463
+ const candidateCommand = parts.slice(3).join("|").trim().toLowerCase();
1464
+ if (isNaN(pid) || pid <= 0 || pid === process.pid)
1465
+ continue;
1466
+ if (!candidateCommand)
1467
+ continue;
1468
+ let score = 0;
1469
+ if (candidateParentPid === parentPid)
1470
+ score += 10;
1471
+ for (const part of commandParts) {
1472
+ if (candidateCommand.includes(part))
1473
+ score += 2;
1474
+ }
1475
+ if (candidateCommand.includes("run server.ts"))
1476
+ score += 2;
1477
+ if (candidateCommand.includes(workdir.toLowerCase().replace(/\\/g, "/")))
1478
+ score += 4;
1479
+ if (candidateCommand.includes(workdir.toLowerCase()))
1480
+ score += 4;
1481
+ const createdAt = creationDateRaw ? Date.parse(creationDateRaw) : NaN;
1482
+ if (!isNaN(createdAt)) {
1483
+ const ageMs = Math.abs(createdAt - spawnedAt);
1484
+ if (ageMs <= 15000)
1485
+ score += 6;
1486
+ else if (ageMs <= 60000)
1487
+ score += 2;
1488
+ }
1489
+ if (score > bestScore && await isProcessRunning(pid, command)) {
1490
+ bestScore = score;
1491
+ bestPid = pid;
1492
+ }
1493
+ }
1494
+ if (bestScore >= 4) {
1495
+ return bestPid;
1496
+ }
1497
+ } catch {}
1498
+ }
1499
+ return 0;
1500
+ }
1429
1501
  function resolveInternalBgrunCommand(command) {
1430
1502
  const trimmed = command.trim();
1431
1503
  if (!trimmed.startsWith("bgrun --_") && !trimmed.startsWith(`${INTERNAL_BUNX_PREFIX} --_`)) {
@@ -1636,11 +1708,13 @@ async function handleRun(options) {
1636
1708
  stderr: Bun.file(stderrPath)
1637
1709
  });
1638
1710
  newProcess.unref();
1639
- await sleep2(100);
1640
- const pid = await findChildPid(newProcess.pid);
1641
- await sleep2(400);
1642
- return pid;
1711
+ return await resolveSpawnedProcessPid(newProcess.pid, finalCommand, finalDirectory);
1643
1712
  }) ?? 0;
1713
+ if (actualPid <= 0) {
1714
+ throw new Error(`Failed to resolve a live PID for "${name}" after launch. The child process likely exited immediately. Check logs:
1715
+ stdout: ${stdoutPath}
1716
+ stderr: ${stderrPath}`);
1717
+ }
1644
1718
  await retryDatabaseOperation(() => insertProcess({
1645
1719
  pid: actualPid,
1646
1720
  workdir: finalDirectory,
package/dist/deps.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
  }
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
  }
@@ -1587,6 +1587,78 @@ function getRecentGuardEvents(limit = 100) {
1587
1587
  var homePath2 = getHomeDir();
1588
1588
  var run = createMeasure2("run");
1589
1589
  var INTERNAL_BUNX_PREFIX = "bunx bgrun";
1590
+ async function resolveSpawnedProcessPid(parentPid, command, workdir) {
1591
+ if (parentPid <= 0)
1592
+ return 0;
1593
+ let candidatePid = parentPid;
1594
+ const spawnedAt = Date.now();
1595
+ for (let attempt = 0;attempt < 6; attempt++) {
1596
+ const descendantPid = await findChildPid(parentPid);
1597
+ if (descendantPid > 0) {
1598
+ candidatePid = descendantPid;
1599
+ }
1600
+ if (candidatePid > 0 && await isProcessRunning(candidatePid, command)) {
1601
+ return candidatePid;
1602
+ }
1603
+ await sleep2(250);
1604
+ }
1605
+ const reconciled = await reconcileProcessPids([{ name: "__spawn__", pid: parentPid, command, workdir }], new Set([parentPid]));
1606
+ const matchedPid = reconciled.get("__spawn__") ?? 0;
1607
+ if (matchedPid > 0 && await isProcessRunning(matchedPid, command)) {
1608
+ return matchedPid;
1609
+ }
1610
+ if (process.platform === "win32") {
1611
+ try {
1612
+ const commandParts = command.toLowerCase().split(/\s+/).map((part) => part.trim()).filter((part) => part.length > 2);
1613
+ const output = await psExec(`Get-CimInstance Win32_Process -Filter "Name='bun.exe'" | ` + `ForEach-Object { Write-Output "$($_.ProcessId)|$($_.ParentProcessId)|$($_.CreationDate)|$($_.CommandLine)" }`, 5000);
1614
+ let bestPid = 0;
1615
+ let bestScore = -1;
1616
+ for (const line of output.split(`
1617
+ `)) {
1618
+ const parts = line.split("|");
1619
+ if (parts.length < 4)
1620
+ continue;
1621
+ const pid = parseInt(parts[0]?.trim(), 10);
1622
+ const candidateParentPid = parseInt(parts[1]?.trim(), 10);
1623
+ const creationDateRaw = parts[2]?.trim() || "";
1624
+ const candidateCommand = parts.slice(3).join("|").trim().toLowerCase();
1625
+ if (isNaN(pid) || pid <= 0 || pid === process.pid)
1626
+ continue;
1627
+ if (!candidateCommand)
1628
+ continue;
1629
+ let score = 0;
1630
+ if (candidateParentPid === parentPid)
1631
+ score += 10;
1632
+ for (const part of commandParts) {
1633
+ if (candidateCommand.includes(part))
1634
+ score += 2;
1635
+ }
1636
+ if (candidateCommand.includes("run server.ts"))
1637
+ score += 2;
1638
+ if (candidateCommand.includes(workdir.toLowerCase().replace(/\\/g, "/")))
1639
+ score += 4;
1640
+ if (candidateCommand.includes(workdir.toLowerCase()))
1641
+ score += 4;
1642
+ const createdAt = creationDateRaw ? Date.parse(creationDateRaw) : NaN;
1643
+ if (!isNaN(createdAt)) {
1644
+ const ageMs = Math.abs(createdAt - spawnedAt);
1645
+ if (ageMs <= 15000)
1646
+ score += 6;
1647
+ else if (ageMs <= 60000)
1648
+ score += 2;
1649
+ }
1650
+ if (score > bestScore && await isProcessRunning(pid, command)) {
1651
+ bestScore = score;
1652
+ bestPid = pid;
1653
+ }
1654
+ }
1655
+ if (bestScore >= 4) {
1656
+ return bestPid;
1657
+ }
1658
+ } catch {}
1659
+ }
1660
+ return 0;
1661
+ }
1590
1662
  function resolveInternalBgrunCommand(command) {
1591
1663
  const trimmed = command.trim();
1592
1664
  if (!trimmed.startsWith("bgrun --_") && !trimmed.startsWith(`${INTERNAL_BUNX_PREFIX} --_`)) {
@@ -1797,11 +1869,13 @@ async function handleRun(options) {
1797
1869
  stderr: Bun.file(stderrPath)
1798
1870
  });
1799
1871
  newProcess.unref();
1800
- await sleep2(100);
1801
- const pid = await findChildPid(newProcess.pid);
1802
- await sleep2(400);
1803
- return pid;
1872
+ return await resolveSpawnedProcessPid(newProcess.pid, finalCommand, finalDirectory);
1804
1873
  }) ?? 0;
1874
+ if (actualPid <= 0) {
1875
+ throw new Error(`Failed to resolve a live PID for "${name}" after launch. The child process likely exited immediately. Check logs:
1876
+ stdout: ${stdoutPath}
1877
+ stderr: ${stderrPath}`);
1878
+ }
1805
1879
  await retryDatabaseOperation(() => insertProcess({
1806
1880
  pid: actualPid,
1807
1881
  workdir: finalDirectory,
@@ -2870,6 +2944,7 @@ async function showHelp() {
2870
2944
  bunx bgrun --delete [name] Delete a process
2871
2945
  bunx bgrun --clean Remove all stopped processes
2872
2946
  bunx bgrun --nuke Delete ALL processes
2947
+ bunx bgrun --kill-port <n> Kill whatever is currently listening on a port
2873
2948
 
2874
2949
  ${chalk7.yellow("Options:")}
2875
2950
  --name <string> Process name (required for new)
@@ -2894,18 +2969,21 @@ async function showHelp() {
2894
2969
  --dashboard Launch web dashboard as bgrun-managed process
2895
2970
  --guard Enable per-process crash watcher
2896
2971
  --guard-off Disable per-process crash watcher
2972
+ --kill-port <number> Kill the process currently using a port
2897
2973
  --port <number> Port for dashboard (default: 3000)
2898
2974
  --help Show this help message
2899
2975
 
2900
2976
  ${chalk7.yellow("Examples:")}
2901
2977
  bunx bgrun -- bun run dev
2902
2978
  bunx bgrun --hot -- bun run index.ts
2979
+ bunx bgrun -hl -- bun run server.ts
2903
2980
  bunx bgrun --no-config -- bun run script.ts
2904
2981
  bunx bgrun --force -- bun run server.ts
2905
2982
  bunx bgrun inline -- bun run dev
2906
2983
  Invoke-Expression (bunx bgrun --env)
2907
2984
  eval "$(bunx bgrun --env --shell sh)"
2908
2985
  bunx bgrun --dashboard
2986
+ bunx bgrun --kill-port 3000
2909
2987
  bunx bgrun myapp --guard
2910
2988
  bunx bgrun myapp --guard-off
2911
2989
  bunx bgrun --name myapp --command "bun run dev" --directory . --watch
@@ -2914,16 +2992,16 @@ async function showHelp() {
2914
2992
  console.log(usage);
2915
2993
  }
2916
2994
  var cliArgOptions = {
2917
- name: { type: "string" },
2918
- command: { type: "string" },
2919
- directory: { type: "string" },
2995
+ name: { type: "string", short: "n" },
2996
+ command: { type: "string", short: "c" },
2997
+ directory: { type: "string", short: "d" },
2920
2998
  config: { type: "string" },
2921
2999
  "no-config": { type: "boolean" },
2922
3000
  env: { type: "boolean" },
2923
3001
  shell: { type: "string" },
2924
- watch: { type: "boolean" },
2925
- hot: { type: "boolean" },
2926
- force: { type: "boolean" },
3002
+ watch: { type: "boolean", short: "w" },
3003
+ hot: { type: "boolean", short: "h" },
3004
+ force: { type: "boolean", short: "f" },
2927
3005
  fetch: { type: "boolean" },
2928
3006
  delete: { type: "boolean" },
2929
3007
  nuke: { type: "boolean" },
@@ -2932,13 +3010,13 @@ var cliArgOptions = {
2932
3010
  stop: { type: "boolean" },
2933
3011
  "stop-all": { type: "boolean" },
2934
3012
  clean: { type: "boolean" },
2935
- json: { type: "boolean" },
2936
- logs: { type: "boolean" },
3013
+ json: { type: "boolean", short: "j" },
3014
+ logs: { type: "boolean", short: "l" },
2937
3015
  "log-stdout": { type: "boolean" },
2938
3016
  "log-stderr": { type: "boolean" },
2939
3017
  lines: { type: "string" },
2940
3018
  filter: { type: "string" },
2941
- version: { type: "boolean" },
3019
+ version: { type: "boolean", short: "v" },
2942
3020
  help: { type: "boolean" },
2943
3021
  db: { type: "string" },
2944
3022
  stdout: { type: "string" },
@@ -2947,6 +3025,7 @@ var cliArgOptions = {
2947
3025
  guard: { type: "boolean" },
2948
3026
  "guard-off": { type: "boolean" },
2949
3027
  debug: { type: "boolean" },
3028
+ "kill-port": { type: "string" },
2950
3029
  _serve: { type: "boolean" },
2951
3030
  "_watch-process": { type: "string" },
2952
3031
  port: { type: "string" }
@@ -2954,7 +3033,7 @@ var cliArgOptions = {
2954
3033
  async function run2() {
2955
3034
  const rawArgs = Bun.argv.slice(2);
2956
3035
  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);
3036
+ 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
3037
  };
2959
3038
  if (rawArgs[0] === "inline") {
2960
3039
  const parsed = parseInlineArgs(rawArgs.slice(1));
@@ -3227,6 +3306,24 @@ async function run2() {
3227
3306
  `);
3228
3307
  return;
3229
3308
  }
3309
+ if (values["kill-port"]) {
3310
+ const port = parseInt(String(values["kill-port"]), 10);
3311
+ if (isNaN(port) || port <= 0) {
3312
+ error("Please provide a valid port number for --kill-port.");
3313
+ }
3314
+ const wasFree = await isPortFree(port);
3315
+ if (wasFree) {
3316
+ announce(`Port ${port} is already free.`, "Port Free");
3317
+ return;
3318
+ }
3319
+ await killProcessOnPort(port);
3320
+ const freed = await waitForPortFree(port, 5000);
3321
+ if (!freed) {
3322
+ error(`Port ${port} is still busy after attempted cleanup.`);
3323
+ }
3324
+ announce(`Freed port ${port}.`, "Port Cleanup");
3325
+ return;
3326
+ }
3230
3327
  if (values.nuke) {
3231
3328
  await handleDeleteAll();
3232
3329
  return;
package/dist/server.js CHANGED
@@ -311,7 +311,7 @@ function ensureDir(dirPath) {
311
311
  }
312
312
  function getShellCommand(command) {
313
313
  if (isWindows()) {
314
- return ["cmd", "/c", command];
314
+ return [process.env.ComSpec || "cmd.exe", "/c", command];
315
315
  } else {
316
316
  return ["sh", "-c", command];
317
317
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bgrun",
3
- "version": "3.12.24",
3
+ "version": "3.12.25",
4
4
  "description": "bgrun — A lightweight process manager for Bun",
5
5
  "type": "module",
6
6
  "main": "./dist/api.js",