bgrun 3.12.15 → 3.12.21

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/deploy.js CHANGED
@@ -17,48 +17,59 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
  var __require = import.meta.require;
18
18
 
19
19
  // src/platform.ts
20
- var exports_platform = {};
21
- __export(exports_platform, {
22
- waitForPortFree: () => waitForPortFree,
23
- terminateProcess: () => terminateProcess,
24
- reconcileProcessPids: () => reconcileProcessPids,
25
- readFileTail: () => readFileTail,
26
- psExec: () => psExec,
27
- parseUnixListeningPorts: () => parseUnixListeningPorts,
28
- killProcessOnPort: () => killProcessOnPort,
29
- isWindows: () => isWindows,
30
- isProcessRunning: () => isProcessRunning,
31
- isPortFree: () => isPortFree,
32
- getShellCommand: () => getShellCommand,
33
- getProcessPorts: () => getProcessPorts,
34
- getProcessMemory: () => getProcessMemory,
35
- getProcessBatchResources: () => getProcessBatchResources,
36
- getPortInfo: () => getPortInfo,
37
- getHomeDir: () => getHomeDir,
38
- findPidByPort: () => findPidByPort,
39
- findChildPid: () => findChildPid,
40
- ensureDir: () => ensureDir,
41
- copyFile: () => copyFile
42
- });
43
20
  import * as fs from "fs";
44
21
  import * as os from "os";
45
22
  import { join } from "path";
46
23
  var {$ } = globalThis.Bun;
47
24
  import { createMeasure } from "measure-fn";
48
- function psExec(command, _timeoutMs = 3000) {
49
- const tmpFile = join(os.tmpdir(), `bgr-ps-${Date.now()}.ps1`);
25
+ async function psExec(command, timeoutMs = 3000) {
26
+ const tmpFile = join(os.tmpdir(), `bgr-ps-${Date.now()}-${Math.random().toString(36).substr(2, 9)}.ps1`);
50
27
  try {
51
- fs.writeFileSync(tmpFile, command);
52
- const result = Bun.spawnSync(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", tmpFile]);
28
+ await Bun.write(tmpFile, command);
29
+ const proc = Bun.spawn([
30
+ "powershell",
31
+ "-NoProfile",
32
+ "-ExecutionPolicy",
33
+ "Bypass",
34
+ "-File",
35
+ tmpFile
36
+ ], {
37
+ stdout: "pipe",
38
+ stderr: "pipe"
39
+ });
40
+ const timeoutPromise = new Promise((_, reject) => {
41
+ setTimeout(() => reject(new Error("PowerShell command timed out")), timeoutMs);
42
+ });
43
+ const resultPromise = new Promise(async (resolve, reject) => {
44
+ try {
45
+ const stdoutPromise = proc.stdout ? new Response(proc.stdout).text() : Promise.resolve("");
46
+ const stderrPromise = proc.stderr ? new Response(proc.stderr).text() : Promise.resolve("");
47
+ const exitCode = await proc.exited;
48
+ const stdout = await stdoutPromise;
49
+ const stderr = await stderrPromise;
50
+ if (exitCode === 0) {
51
+ resolve(stdout);
52
+ } else {
53
+ resolve(stderr || "");
54
+ }
55
+ } catch (error) {
56
+ reject(error);
57
+ }
58
+ });
53
59
  try {
54
- fs.unlinkSync(tmpFile);
55
- } catch {}
56
- return result.stdout?.toString() || "";
57
- } catch {
60
+ const result = await Promise.race([resultPromise, timeoutPromise]);
61
+ return result.trim();
62
+ } catch (error) {
63
+ return "";
64
+ } finally {
65
+ try {
66
+ await Bun.sleep(100);
67
+ } catch {}
68
+ }
69
+ } finally {
58
70
  try {
59
- fs.unlinkSync(tmpFile);
71
+ fs.rmSync(tmpFile, { force: true });
60
72
  } catch {}
61
- return "";
62
73
  }
63
74
  }
64
75
  function isWindows() {
@@ -80,7 +91,7 @@ async function isProcessRunning(pid, command) {
80
91
  process.kill(pid, 0);
81
92
  return true;
82
93
  } catch {
83
- const output = psExec(`Get-Process -Id ${pid} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id`).trim();
94
+ const output = await psExec(`Get-Process -Id ${pid} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id`);
84
95
  return output === String(pid);
85
96
  }
86
97
  } else {
@@ -173,35 +184,6 @@ async function isPortFree(port) {
173
184
  return true;
174
185
  }
175
186
  }
176
- async function getPortInfo(port) {
177
- try {
178
- if (isWindows()) {
179
- const result = await $`netstat -ano | findstr :${port}`.nothrow().quiet().text();
180
- for (const line of result.split(`
181
- `)) {
182
- const match = line.match(new RegExp(`:(${port})\\s+.*LISTENING\\s+(\\d+)`));
183
- if (match) {
184
- const pid = parseInt(match[2]);
185
- if (pid > 0 && await isProcessRunning(pid)) {
186
- const nameResult = await $`powershell -NoProfile -Command "(Get-Process -Id ${pid} -ErrorAction SilentlyContinue).ProcessName"`.nothrow().quiet().text();
187
- return { inUse: true, pid, processName: nameResult.trim() || "unknown" };
188
- }
189
- }
190
- }
191
- return { inUse: false };
192
- } else {
193
- const result = await $`ss -tln sport = :${port}`.nothrow().quiet().text();
194
- const lines = result.trim().split(`
195
- `).filter((l) => l.trim());
196
- if (lines.length > 1) {
197
- return { inUse: true };
198
- }
199
- return { inUse: false };
200
- }
201
- } catch {
202
- return { inUse: false };
203
- }
204
- }
205
187
  async function waitForPortFree(port, timeoutMs = 5000) {
206
188
  const startTime = Date.now();
207
189
  const pollInterval = 300;
@@ -409,9 +391,6 @@ async function readFileTail(filePath, lines) {
409
391
  }
410
392
  }) ?? "";
411
393
  }
412
- function copyFile(src, dest) {
413
- fs.copyFileSync(src, dest);
414
- }
415
394
  async function getProcessMemory(pid) {
416
395
  const map = await getProcessBatchResources([pid]);
417
396
  return map.get(pid)?.memory || 0;
@@ -424,7 +403,7 @@ async function getProcessBatchResources(pids) {
424
403
  const pidSet = new Set(pids);
425
404
  try {
426
405
  if (isWindows()) {
427
- const output = psExec(`Get-Process -Id ${pids.join(",")} -ErrorAction SilentlyContinue | Select-Object Id, WorkingSet64 | ForEach-Object { Write-Output "$($_.Id)|$($_.WorkingSet64)" }`);
406
+ const output = await psExec(`Get-Process -Id ${pids.join(",")} -ErrorAction SilentlyContinue | Select-Object Id, WorkingSet64 | ForEach-Object { Write-Output "$($_.Id)|$($_.WorkingSet64)" }`);
428
407
  for (const line of output.split(`
429
408
  `)) {
430
409
  const sepIdx = line.indexOf("|");
@@ -504,6 +483,21 @@ async function getProcessPorts(pid) {
504
483
  return [];
505
484
  }
506
485
  }
486
+ async function resolvePidWithPorts(pid) {
487
+ const ports = await getProcessPorts(pid);
488
+ if (ports.length > 0 || !isWindows() || pid <= 0) {
489
+ return { pid, ports };
490
+ }
491
+ const childPid = await findChildPid(pid);
492
+ if (childPid === pid || childPid <= 0) {
493
+ return { pid, ports };
494
+ }
495
+ const childPorts = await getProcessPorts(childPid);
496
+ if (childPorts.length > 0) {
497
+ return { pid: childPid, ports: childPorts };
498
+ }
499
+ return { pid, ports };
500
+ }
507
501
  var plat;
508
502
  var init_platform = __esm(() => {
509
503
  plat = createMeasure("platform");
@@ -580,7 +574,7 @@ function removeProcessByName(name) {
580
574
  function updateProcessPid(name, newPid) {
581
575
  const proc = db.process.select().where({ name }).limit(1).get();
582
576
  if (proc) {
583
- db.process.update(proc.id, { pid: newPid });
577
+ proc.update({ pid: newPid });
584
578
  }
585
579
  }
586
580
  function removeAllProcesses() {
@@ -592,7 +586,7 @@ function removeAllProcesses() {
592
586
  function updateProcessEnv(name, envJson) {
593
587
  const proc = db.process.select().where({ name }).limit(1).get();
594
588
  if (proc) {
595
- db.process.update(proc.id, { env: envJson });
589
+ proc.update({ env: envJson });
596
590
  }
597
591
  }
598
592
  function getAllTemplates() {
@@ -830,6 +824,8 @@ var init_db = __esm(() => {
830
824
 
831
825
  // src/utils.ts
832
826
  import * as fs2 from "fs";
827
+ import * as os2 from "os";
828
+ import { join as join3 } from "path";
833
829
  function parseEnvString(envString) {
834
830
  const env = {};
835
831
  envString.split(",").forEach((pair) => {
@@ -845,9 +841,92 @@ function calculateRuntime(startTime) {
845
841
  const diffInMinutes = Math.floor((now - start) / (1000 * 60));
846
842
  return `${diffInMinutes} minutes`;
847
843
  }
844
+ function parseCommandEnv(command) {
845
+ const env = {};
846
+ const trimmed = command.trim();
847
+ const windowsSegments = trimmed.split(/&&/).map((segment) => segment.trim());
848
+ for (const segment of windowsSegments) {
849
+ const match = segment.match(/^set\s+([A-Za-z_][A-Za-z0-9_]*)=(.*)$/i);
850
+ if (!match)
851
+ break;
852
+ env[match[1]] = match[2].trim();
853
+ }
854
+ const unixPrefixRegex = /^(?:([A-Za-z_][A-Za-z0-9_]*)=([^\s]+)\s+)+/;
855
+ const unixPrefix = trimmed.match(unixPrefixRegex);
856
+ if (unixPrefix) {
857
+ const pairs = unixPrefix[0].trim().split(/\s+/);
858
+ for (const pair of pairs) {
859
+ const eqIdx = pair.indexOf("=");
860
+ if (eqIdx <= 0)
861
+ continue;
862
+ const key = pair.slice(0, eqIdx);
863
+ const value = pair.slice(eqIdx + 1);
864
+ if (key)
865
+ env[key] = value;
866
+ }
867
+ }
868
+ return env;
869
+ }
870
+ function getDeclaredPort(processEnv, command) {
871
+ const mergedEnv = { ...command ? parseCommandEnv(command) : {}, ...processEnv };
872
+ const raw = mergedEnv.PORT || mergedEnv.BUN_PORT || "";
873
+ const parsed = parseInt(raw, 10);
874
+ return !isNaN(parsed) && parsed > 0 ? parsed : null;
875
+ }
876
+ function buildManagedProcessEnv(parentEnv, processEnv = {}) {
877
+ const sanitizedParentEnv = {};
878
+ for (const [key, value] of Object.entries(parentEnv)) {
879
+ if (value === undefined)
880
+ continue;
881
+ if (INTERNAL_MANAGED_ENV_KEYS.includes(key))
882
+ continue;
883
+ sanitizedParentEnv[key] = value;
884
+ }
885
+ return { ...sanitizedParentEnv, ...processEnv };
886
+ }
887
+ function stringifyEnvString(env) {
888
+ return Object.entries(env).map(([key, value]) => `${key}=${value}`).join(",");
889
+ }
890
+ function getWatcherProcessName(targetName) {
891
+ return `${WATCHER_PREFIX}${encodeURIComponent(targetName)}`;
892
+ }
893
+ function getWatchedProcessName(watcherName) {
894
+ if (!watcherName.startsWith(WATCHER_PREFIX))
895
+ return null;
896
+ try {
897
+ return decodeURIComponent(watcherName.slice(WATCHER_PREFIX.length));
898
+ } catch {
899
+ return null;
900
+ }
901
+ }
902
+ function isWatcherProcessName(name) {
903
+ return getWatchedProcessName(name) !== null;
904
+ }
905
+ function isInternalProcessName(name) {
906
+ return name === "bgr-dashboard" || name === "bgr-guard" || isWatcherProcessName(name);
907
+ }
908
+ function getOperationLockPath(name) {
909
+ return join3(os2.homedir(), ".bgr", `${name}.operation.lock`);
910
+ }
911
+ function acquireProcessOperationLock(name) {
912
+ const lockPath = getOperationLockPath(name);
913
+ fs2.mkdirSync(join3(os2.homedir(), ".bgr"), { recursive: true });
914
+ fs2.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, time: Date.now() }));
915
+ let released = false;
916
+ return () => {
917
+ if (released)
918
+ return;
919
+ released = true;
920
+ try {
921
+ fs2.unlinkSync(lockPath);
922
+ } catch {}
923
+ };
924
+ }
925
+ function isProcessOperationLocked(name) {
926
+ return fs2.existsSync(getOperationLockPath(name));
927
+ }
848
928
  async function getVersion() {
849
929
  try {
850
- const { join: join3 } = await import("path");
851
930
  const pkgPath = join3(import.meta.dir, "../package.json");
852
931
  const pkg = await Bun.file(pkgPath).json();
853
932
  return pkg.version || "0.0.0";
@@ -903,8 +982,10 @@ function tailFile(path, prefix, colorFn, lines) {
903
982
  } catch {}
904
983
  };
905
984
  }
985
+ var INTERNAL_MANAGED_ENV_KEYS, WATCHER_PREFIX = "bgr-watch-";
906
986
  var init_utils = __esm(() => {
907
987
  init_platform();
988
+ INTERNAL_MANAGED_ENV_KEYS = ["BUN_PORT", "BGR_STDOUT", "BGR_STDERR"];
908
989
  });
909
990
 
910
991
  // src/deps.ts
@@ -996,6 +1077,13 @@ var init_deps = __esm(() => {
996
1077
  init_utils();
997
1078
  });
998
1079
 
1080
+ // src/deploy.ts
1081
+ init_db();
1082
+
1083
+ // src/commands/run.ts
1084
+ init_db();
1085
+ init_platform();
1086
+
999
1087
  // src/logger.ts
1000
1088
  import boxen from "boxen";
1001
1089
  import chalk from "chalk";
@@ -1009,6 +1097,13 @@ function announce(message, title) {
1009
1097
  borderStyle: "round"
1010
1098
  }));
1011
1099
  }
1100
+
1101
+ class BgrunError extends Error {
1102
+ constructor(message) {
1103
+ super(message);
1104
+ this.name = "BgrunError";
1105
+ }
1106
+ }
1012
1107
  function error(message) {
1013
1108
  const text = message instanceof Error ? message.stack || message.message : String(message);
1014
1109
  console.error(boxen(chalk.red(text), {
@@ -1021,15 +1116,9 @@ function error(message) {
1021
1116
  }));
1022
1117
  throw new BgrunError(text);
1023
1118
  }
1024
- var BgrunError;
1025
- var init_logger = __esm(() => {
1026
- BgrunError = class BgrunError extends Error {
1027
- constructor(message) {
1028
- super(message);
1029
- this.name = "BgrunError";
1030
- }
1031
- };
1032
- });
1119
+
1120
+ // src/commands/run.ts
1121
+ init_utils();
1033
1122
 
1034
1123
  // src/config.ts
1035
1124
  function formatEnvKey(key) {
@@ -1061,185 +1150,556 @@ async function parseConfigFile(configPath) {
1061
1150
  const parsedConfig = await import(importPath).then((m) => m.default);
1062
1151
  return flattenConfig(parsedConfig);
1063
1152
  }
1153
+ async function loadConfigEnv(directory, configPath = ".config.toml") {
1154
+ const { join: join4 } = await import("path");
1155
+ const fullConfigPath = join4(directory, configPath);
1156
+ if (!await Bun.file(fullConfigPath).exists()) {
1157
+ return {
1158
+ configEnv: {},
1159
+ fullConfigPath,
1160
+ exists: false
1161
+ };
1162
+ }
1163
+ return {
1164
+ configEnv: await parseConfigFile(fullConfigPath),
1165
+ fullConfigPath,
1166
+ exists: true
1167
+ };
1168
+ }
1064
1169
 
1065
1170
  // src/commands/run.ts
1066
1171
  var {$: $2 } = globalThis.Bun;
1067
1172
  var {sleep: sleep2 } = globalThis.Bun;
1068
- import { join as join3 } from "path";
1173
+ import { join as join5 } from "path";
1069
1174
  import { createMeasure as createMeasure2 } from "measure-fn";
1070
- async function handleRun(options) {
1071
- const { command, directory, env, name, configPath, force, fetch, stdout, stderr } = options;
1072
- const existingProcess = name ? getProcess(name) : null;
1073
- if (name && existingProcess) {
1074
- const { getUnmetDeps: getUnmetDeps2 } = await Promise.resolve().then(() => (init_deps(), exports_deps));
1075
- const unmet = await getUnmetDeps2(name);
1076
- if (unmet.length > 0) {
1077
- await run.measure(`Start ${unmet.length} dependencies for "${name}"`, async () => {
1078
- for (const depName of unmet) {
1079
- const depProc = getProcess(depName);
1080
- if (depProc) {
1081
- announce(`\uD83D\uDCE6 Starting dependency "${depName}" for "${name}"`, "Dependency");
1082
- await handleRun({ action: "run", name: depName, force: true, remoteName: "" });
1083
- }
1084
- }
1085
- });
1175
+
1176
+ // src/watcher.ts
1177
+ init_db();
1178
+ init_platform();
1179
+ import { join as join4 } from "path";
1180
+
1181
+ // src/cli-helpers.ts
1182
+ init_db();
1183
+ var MONTH_NAMES = [
1184
+ "january",
1185
+ "february",
1186
+ "march",
1187
+ "april",
1188
+ "may",
1189
+ "june",
1190
+ "july",
1191
+ "august",
1192
+ "september",
1193
+ "october",
1194
+ "november",
1195
+ "december"
1196
+ ];
1197
+ var DAY_NAMES = [
1198
+ "",
1199
+ "first",
1200
+ "second",
1201
+ "third",
1202
+ "fourth",
1203
+ "fifth",
1204
+ "sixth",
1205
+ "seventh",
1206
+ "eighth",
1207
+ "ninth",
1208
+ "tenth",
1209
+ "eleventh",
1210
+ "twelfth",
1211
+ "thirteenth",
1212
+ "fourteenth",
1213
+ "fifteenth",
1214
+ "sixteenth",
1215
+ "seventeenth",
1216
+ "eighteenth",
1217
+ "nineteenth",
1218
+ "twentieth",
1219
+ "twenty-first",
1220
+ "twenty-second",
1221
+ "twenty-third",
1222
+ "twenty-fourth",
1223
+ "twenty-fifth",
1224
+ "twenty-sixth",
1225
+ "twenty-seventh",
1226
+ "twenty-eighth",
1227
+ "twenty-ninth",
1228
+ "thirtieth",
1229
+ "thirty-first"
1230
+ ];
1231
+ function pad2(value) {
1232
+ return String(value).padStart(2, "0");
1233
+ }
1234
+ function buildDateProcessName(now = new Date) {
1235
+ const month = MONTH_NAMES[now.getMonth()] || "process";
1236
+ const day = DAY_NAMES[now.getDate()] || "day";
1237
+ return `${month}-${day}`;
1238
+ }
1239
+ function generateAutoProcessName(now = new Date) {
1240
+ const baseName = buildDateProcessName(now);
1241
+ if (!getProcess(baseName)) {
1242
+ return baseName;
1243
+ }
1244
+ const timeSuffix = `${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`;
1245
+ const timeName = `${baseName}-${timeSuffix}`;
1246
+ if (!getProcess(timeName)) {
1247
+ return timeName;
1248
+ }
1249
+ for (let i = 1;i < 1000; i++) {
1250
+ const candidate = `${timeName}-${i}`;
1251
+ if (!getProcess(candidate)) {
1252
+ return candidate;
1086
1253
  }
1087
1254
  }
1088
- if (existingProcess) {
1089
- const finalDirectory2 = directory || existingProcess.workdir;
1090
- validateDirectory(finalDirectory2);
1091
- $2.cwd(finalDirectory2);
1092
- if (fetch) {
1093
- if (!__require("fs").existsSync(__require("path").join(finalDirectory2, ".git"))) {
1094
- error(`Cannot --fetch: '${finalDirectory2}' is not a Git repository.`);
1255
+ return `${timeName}-${Date.now()}`;
1256
+ }
1257
+ function shellQuoteArg(arg) {
1258
+ if (process.platform === "win32") {
1259
+ if (/^[A-Za-z0-9_./:\\-]+$/.test(arg))
1260
+ return arg;
1261
+ return `"${arg.replace(/"/g, "\\\"")}"`;
1262
+ }
1263
+ if (/^[A-Za-z0-9_./:-]+$/.test(arg))
1264
+ return arg;
1265
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
1266
+ }
1267
+ function joinCommandArgs(args) {
1268
+ return args.map(shellQuoteArg).join(" ");
1269
+ }
1270
+
1271
+ // src/watcher.ts
1272
+ init_utils();
1273
+ var DEFAULT_INTERVAL_MS = 5000;
1274
+ var CRASH_THRESHOLD = 5;
1275
+ var MAX_BACKOFF_MS = 5 * 60000;
1276
+ var STABILITY_WINDOW_MS = 120000;
1277
+ function getWatcherLogPaths(watcherName) {
1278
+ const homePath2 = getHomeDir();
1279
+ return {
1280
+ stdoutPath: join4(homePath2, ".bgr", `${watcherName}-out.txt`),
1281
+ stderrPath: join4(homePath2, ".bgr", `${watcherName}-err.txt`)
1282
+ };
1283
+ }
1284
+ async function findDetachedWatcherPid(targetName) {
1285
+ if (process.platform !== "win32")
1286
+ return null;
1287
+ const escaped = targetName.replace(/'/g, "''");
1288
+ const result = await psExec(`Get-CimInstance Win32_Process -Filter "Name='bun.exe'" | Where-Object { $_.CommandLine -like '*--_watch-process*' -and $_.CommandLine -like '*${escaped}*' } | Sort-Object -Property CreationDate -Descending | Select-Object -First 1 -ExpandProperty ProcessId`, 4000);
1289
+ const pid = parseInt(result.trim(), 10);
1290
+ return !isNaN(pid) && pid > 0 ? pid : null;
1291
+ }
1292
+ function getInternalWatcherCommand(targetName) {
1293
+ const quotedTarget = shellQuoteArg(targetName);
1294
+ return {
1295
+ storedCommand: `bunx bgrun --_watch-process ${quotedTarget}`,
1296
+ spawnCommand: `bunx bgrun --_watch-process ${quotedTarget}`
1297
+ };
1298
+ }
1299
+ async function ensureProcessWatcher(targetName) {
1300
+ if (!targetName || isInternalProcessName(targetName))
1301
+ return;
1302
+ const proc = getProcess(targetName);
1303
+ if (!proc)
1304
+ return;
1305
+ const env = parseEnvString(proc.env || "");
1306
+ if (env.BGR_KEEP_ALIVE !== "true") {
1307
+ await stopProcessWatcher(targetName);
1308
+ return;
1309
+ }
1310
+ const watcherName = getWatcherProcessName(targetName);
1311
+ const existingWatcher = getProcess(watcherName);
1312
+ if (existingWatcher && await isProcessRunning(existingWatcher.pid, existingWatcher.command)) {
1313
+ return;
1314
+ }
1315
+ if (existingWatcher) {
1316
+ await retryDatabaseOperation(() => removeProcessByName(watcherName));
1317
+ }
1318
+ const { stdoutPath, stderrPath } = getWatcherLogPaths(watcherName);
1319
+ await Bun.write(stdoutPath, "");
1320
+ await Bun.write(stderrPath, "");
1321
+ const { storedCommand, spawnCommand } = getInternalWatcherCommand(targetName);
1322
+ const newProcess = Bun.spawn(getShellCommand(spawnCommand), {
1323
+ env: {
1324
+ ...Bun.env,
1325
+ BGR_STDOUT: stdoutPath,
1326
+ BGR_STDERR: stderrPath
1327
+ },
1328
+ cwd: getHomeDir(),
1329
+ stdout: "ignore",
1330
+ stderr: "ignore",
1331
+ detached: true
1332
+ });
1333
+ newProcess.unref();
1334
+ await Bun.sleep(1000);
1335
+ let actualPid = await findChildPid(newProcess.pid);
1336
+ if (!await isProcessRunning(actualPid)) {
1337
+ const detachedPid = await findDetachedWatcherPid(targetName);
1338
+ if (detachedPid)
1339
+ actualPid = detachedPid;
1340
+ }
1341
+ await retryDatabaseOperation(() => insertProcess({
1342
+ pid: actualPid,
1343
+ workdir: getHomeDir(),
1344
+ command: storedCommand,
1345
+ name: watcherName,
1346
+ env: stringifyEnvString({ BGR_KEEP_ALIVE: "false", BGR_WATCH_TARGET: targetName }),
1347
+ configPath: "",
1348
+ stdout_path: stdoutPath,
1349
+ stderr_path: stderrPath
1350
+ }));
1351
+ }
1352
+ async function stopProcessWatcher(targetName) {
1353
+ const watcherName = getWatcherProcessName(targetName);
1354
+ const watcherProc = getProcess(watcherName);
1355
+ if (!watcherProc)
1356
+ return;
1357
+ if (await isProcessRunning(watcherProc.pid, watcherProc.command)) {
1358
+ await terminateProcess(watcherProc.pid, true);
1359
+ }
1360
+ await retryDatabaseOperation(() => removeProcessByName(watcherName));
1361
+ }
1362
+ async function syncProcessWatcher(targetName, env) {
1363
+ if (!targetName || isInternalProcessName(targetName))
1364
+ return;
1365
+ if (env.BGR_KEEP_ALIVE === "true") {
1366
+ await ensureProcessWatcher(targetName);
1367
+ } else {
1368
+ await stopProcessWatcher(targetName);
1369
+ }
1370
+ }
1371
+ function getBackoffMs(restartCount) {
1372
+ if (restartCount <= CRASH_THRESHOLD)
1373
+ return 0;
1374
+ const exponent = restartCount - CRASH_THRESHOLD;
1375
+ return Math.min(30000 * Math.pow(2, exponent - 1), MAX_BACKOFF_MS);
1376
+ }
1377
+ async function cleanupWatcher(targetName) {
1378
+ const watcherName = getWatcherProcessName(targetName);
1379
+ await retryDatabaseOperation(() => removeProcessByName(watcherName));
1380
+ }
1381
+ async function startProcessWatcher(targetName, intervalMs = DEFAULT_INTERVAL_MS) {
1382
+ const watcherName = getWatcherProcessName(targetName);
1383
+ const releaseWatcherLock = acquireProcessOperationLock(watcherName);
1384
+ let restartCount = 0;
1385
+ let nextRestartAt = 0;
1386
+ let lastSeenAliveAt = 0;
1387
+ try {
1388
+ console.log(`[watcher] Watching "${targetName}" every ${Math.round(intervalMs / 1000)}s`);
1389
+ while (true) {
1390
+ const proc = getProcess(targetName);
1391
+ if (!proc) {
1392
+ console.log(`[watcher] Target "${targetName}" removed; exiting watcher`);
1393
+ break;
1394
+ }
1395
+ const env = parseEnvString(proc.env || "");
1396
+ if (env.BGR_KEEP_ALIVE !== "true") {
1397
+ console.log(`[watcher] Guard disabled for "${targetName}"; exiting watcher`);
1398
+ break;
1095
1399
  }
1096
- await run.measure(`Git fetch "${name}"`, async () => {
1400
+ if (proc.pid <= 0 || isProcessOperationLocked(targetName)) {
1401
+ await Bun.sleep(intervalMs);
1402
+ continue;
1403
+ }
1404
+ const alive = await isProcessRunning(proc.pid, proc.command);
1405
+ if (!alive) {
1406
+ const now = Date.now();
1407
+ if (now < nextRestartAt) {
1408
+ await Bun.sleep(intervalMs);
1409
+ continue;
1410
+ }
1097
1411
  try {
1098
- await $2`git fetch origin`;
1099
- const localHash = (await $2`git rev-parse HEAD`.text()).trim();
1100
- const remoteHash = (await $2`git rev-parse origin/$(git rev-parse --abbrev-ref HEAD)`.text()).trim();
1101
- if (localHash !== remoteHash) {
1102
- await $2`git pull origin $(git rev-parse --abbrev-ref HEAD)`;
1103
- announce("\uD83D\uDCE5 Pulled latest changes", "Git Update");
1104
- }
1412
+ console.log(`[watcher] Restarting "${targetName}" after detected crash`);
1413
+ await handleRun({
1414
+ action: "run",
1415
+ name: targetName,
1416
+ force: true,
1417
+ remoteName: ""
1418
+ });
1419
+ restartCount++;
1420
+ const backoffMs = getBackoffMs(restartCount);
1421
+ nextRestartAt = backoffMs > 0 ? now + backoffMs : 0;
1422
+ lastSeenAliveAt = 0;
1423
+ addHistoryEntry(targetName, "guard_restart", proc.pid, { by: watcherName, count: restartCount, backoffMs });
1105
1424
  } catch (err) {
1106
- error(`Failed to pull latest changes: ${err}`);
1425
+ addHistoryEntry(targetName, "guard_restart_failed", proc.pid, { by: watcherName, error: err?.message || String(err) });
1426
+ console.error(`[watcher] Failed to restart "${targetName}": ${err.message}`);
1107
1427
  }
1108
- });
1109
- }
1110
- const isRunning = await isProcessRunning(existingProcess.pid);
1111
- if (isRunning && !force) {
1112
- error(`Process '${name}' is currently running. Use --force to restart.`);
1113
- }
1114
- let detectedPorts = [];
1115
- if (isRunning) {
1116
- detectedPorts = await getProcessPorts(existingProcess.pid);
1428
+ } else if (restartCount > 0) {
1429
+ const now = Date.now();
1430
+ if (!lastSeenAliveAt) {
1431
+ lastSeenAliveAt = now;
1432
+ } else if (now - lastSeenAliveAt >= STABILITY_WINDOW_MS) {
1433
+ restartCount = 0;
1434
+ nextRestartAt = 0;
1435
+ lastSeenAliveAt = 0;
1436
+ }
1437
+ }
1438
+ await Bun.sleep(intervalMs);
1117
1439
  }
1118
- if (isRunning) {
1119
- await run.measure(`Terminate "${name}" (PID ${existingProcess.pid})`, async () => {
1120
- await terminateProcess(existingProcess.pid);
1121
- announce(`\uD83D\uDD25 Terminated existing process '${name}'`, "Process Terminated");
1122
- });
1440
+ } finally {
1441
+ releaseWatcherLock();
1442
+ await cleanupWatcher(targetName);
1443
+ }
1444
+ }
1445
+ function getGuardRestartCounts() {
1446
+ const counts = new Map;
1447
+ const entries = db.history.select().where({ event: "guard_restart" }).all();
1448
+ for (const entry of entries) {
1449
+ counts.set(entry.process_name, (counts.get(entry.process_name) || 0) + 1);
1450
+ }
1451
+ return counts;
1452
+ }
1453
+ function getRecentGuardEvents(limit = 100) {
1454
+ const rows = db.history.select().orderBy("timestamp", "desc").limit(limit * 4).all().filter((row) => row.event === "guard_restart" || row.event === "guard_restart_failed").slice(0, limit);
1455
+ return rows.map((row) => ({
1456
+ time: new Date(row.timestamp).getTime(),
1457
+ name: row.process_name,
1458
+ action: "restart",
1459
+ success: row.event === "guard_restart"
1460
+ }));
1461
+ }
1462
+
1463
+ // src/commands/run.ts
1464
+ var homePath2 = getHomeDir();
1465
+ var run = createMeasure2("run");
1466
+ var INTERNAL_BUNX_PREFIX = "bunx bgrun";
1467
+ function resolveInternalBgrunCommand(command) {
1468
+ const trimmed = command.trim();
1469
+ if (!trimmed.startsWith("bgrun --_") && !trimmed.startsWith(`${INTERNAL_BUNX_PREFIX} --_`)) {
1470
+ return command;
1471
+ }
1472
+ if (trimmed.startsWith(`${INTERNAL_BUNX_PREFIX} --_`)) {
1473
+ return trimmed;
1474
+ }
1475
+ return `${INTERNAL_BUNX_PREFIX}${trimmed.slice("bgrun".length)}`;
1476
+ }
1477
+ async function handleRun(options) {
1478
+ const {
1479
+ command,
1480
+ directory,
1481
+ env,
1482
+ name,
1483
+ configPath,
1484
+ force,
1485
+ fetch,
1486
+ stdout,
1487
+ stderr
1488
+ } = options;
1489
+ const releaseOperationLock = name ? acquireProcessOperationLock(name) : () => {};
1490
+ try {
1491
+ const existingProcess = name ? getProcess(name) : null;
1492
+ if (name && existingProcess) {
1493
+ const { getUnmetDeps: getUnmetDeps2 } = await Promise.resolve().then(() => (init_deps(), exports_deps));
1494
+ const unmet = await getUnmetDeps2(name);
1495
+ if (unmet.length > 0) {
1496
+ await run.measure(`Start ${unmet.length} dependencies for "${name}"`, async () => {
1497
+ for (const depName of unmet) {
1498
+ const depProc = getProcess(depName);
1499
+ if (depProc) {
1500
+ announce(`\uD83D\uDCE6 Starting dependency "${depName}" for "${name}"`, "Dependency");
1501
+ await handleRun({
1502
+ action: "run",
1503
+ name: depName,
1504
+ force: true,
1505
+ remoteName: ""
1506
+ });
1507
+ }
1508
+ }
1509
+ });
1510
+ }
1123
1511
  }
1124
- if (detectedPorts.length > 0) {
1125
- await run.measure(`Port cleanup [${detectedPorts.join(", ")}]`, async () => {
1126
- for (const port of detectedPorts) {
1127
- await killProcessOnPort(port);
1512
+ if (existingProcess) {
1513
+ const finalDirectory2 = directory || existingProcess.workdir;
1514
+ validateDirectory(finalDirectory2);
1515
+ $2.cwd(finalDirectory2);
1516
+ if (fetch) {
1517
+ if (!__require("fs").existsSync(__require("path").join(finalDirectory2, ".git"))) {
1518
+ error(`Cannot --fetch: '${finalDirectory2}' is not a Git repository.`);
1128
1519
  }
1129
- for (const port of detectedPorts) {
1130
- const freed = await waitForPortFree(port, 5000);
1131
- if (!freed) {
1132
- await killProcessOnPort(port);
1133
- await waitForPortFree(port, 3000);
1520
+ await run.measure(`Git fetch "${name}"`, async () => {
1521
+ try {
1522
+ await $2`git fetch origin`;
1523
+ const localHash = (await $2`git rev-parse HEAD`.text()).trim();
1524
+ const remoteHash = (await $2`git rev-parse origin/$(git rev-parse --abbrev-ref HEAD)`.text()).trim();
1525
+ if (localHash !== remoteHash) {
1526
+ await $2`git pull origin $(git rev-parse --abbrev-ref HEAD)`;
1527
+ announce("\uD83D\uDCE5 Pulled latest changes", "Git Update");
1528
+ }
1529
+ } catch (err) {
1530
+ error(`Failed to pull latest changes: ${err}`);
1134
1531
  }
1532
+ });
1533
+ }
1534
+ const isRunning = await isProcessRunning(existingProcess.pid);
1535
+ if (isRunning && !force) {
1536
+ error(`Process '${name}' is currently running. Use --force to restart.`);
1537
+ }
1538
+ let actualPid2 = existingProcess.pid;
1539
+ if (!isRunning && !force) {
1540
+ const reconciled = await reconcileProcessPids([
1541
+ {
1542
+ name,
1543
+ pid: existingProcess.pid,
1544
+ command: existingProcess.command,
1545
+ workdir: existingProcess.workdir
1546
+ }
1547
+ ], new Set([existingProcess.pid]));
1548
+ const newPid = reconciled.get(name);
1549
+ if (newPid) {
1550
+ console.log(`[run] Reconciled dead PID ${existingProcess.pid} to live PID ${newPid}`);
1551
+ actualPid2 = newPid;
1552
+ updateProcessPid(name, newPid);
1135
1553
  }
1136
- });
1137
- }
1138
- const cmdToMatch = existingProcess.command;
1139
- if (cmdToMatch) {
1140
- await run.measure("Zombie sweep", async () => {
1141
- try {
1142
- const cmdKeyword = cmdToMatch.split(" ")[1] || cmdToMatch;
1143
- const GENERIC_KEYWORDS = ["dev", "run", "start", "serve", "build", "test"];
1144
- if (GENERIC_KEYWORDS.includes(cmdKeyword.toLowerCase())) {
1145
- return;
1554
+ }
1555
+ let detectedPorts = [];
1556
+ const actuallyRunning = await isProcessRunning(actualPid2);
1557
+ if (actuallyRunning) {
1558
+ detectedPorts = await getProcessPorts(actualPid2);
1559
+ }
1560
+ if (actuallyRunning) {
1561
+ await run.measure(`Terminate "${name}" (PID ${actualPid2})`, async () => {
1562
+ await terminateProcess(actualPid2);
1563
+ announce(`\uD83D\uDD25 Terminated existing process '${name}'`, "Process Terminated");
1564
+ });
1565
+ }
1566
+ if (detectedPorts.length > 0) {
1567
+ await run.measure(`Port cleanup [${detectedPorts.join(", ")}]`, async () => {
1568
+ for (const port of detectedPorts) {
1569
+ await killProcessOnPort(port);
1146
1570
  }
1147
- const currentPid = process.pid;
1148
- const result = await psExec(`Get-CimInstance Win32_Process -Filter "Name='bun.exe'" | Where-Object { $_.CommandLine -match '${cmdKeyword.replace(/'/g, "''")}' -and $_.ProcessId -ne ${currentPid} } | Select-Object -ExpandProperty ProcessId`, 3000);
1149
- const zombiePids = result.split(`
1150
- `).map((l) => parseInt(l.trim())).filter((n) => !isNaN(n) && n > 0 && n !== currentPid);
1151
- for (const zPid of zombiePids) {
1152
- await $2`taskkill /F /T /PID ${zPid}`.nothrow().quiet();
1571
+ for (const port of detectedPorts) {
1572
+ const freed = await waitForPortFree(port, 5000);
1573
+ if (!freed) {
1574
+ await killProcessOnPort(port);
1575
+ await waitForPortFree(port, 3000);
1576
+ }
1153
1577
  }
1154
- if (zombiePids.length > 0) {
1155
- announce(`\uD83E\uDDF9 Swept ${zombiePids.length} zombie process(es)`, "Zombie Cleanup");
1578
+ });
1579
+ }
1580
+ const existingEnv = existingProcess.env ? parseEnvString(existingProcess.env) : {};
1581
+ const declaredPort = getDeclaredPort(existingEnv, existingProcess.command);
1582
+ if (declaredPort && !detectedPorts.includes(declaredPort)) {
1583
+ await run.measure(`Declared port cleanup [${declaredPort}]`, async () => {
1584
+ const portFree = await isPortFree(declaredPort);
1585
+ if (!portFree) {
1586
+ console.log(`[run] Declared port ${declaredPort} is busy (orphaned process), cleaning up...`);
1587
+ await killProcessOnPort(declaredPort);
1588
+ const freed = await waitForPortFree(declaredPort, 5000);
1589
+ if (!freed) {
1590
+ console.warn(`[run] Port ${declaredPort} still busy after cleanup, retrying...`);
1591
+ await killProcessOnPort(declaredPort);
1592
+ await waitForPortFree(declaredPort, 3000);
1593
+ }
1156
1594
  }
1157
- } catch {}
1158
- });
1595
+ });
1596
+ }
1597
+ const cmdToMatch = existingProcess.command;
1598
+ if (cmdToMatch) {
1599
+ await run.measure("Zombie sweep", async () => {
1600
+ try {
1601
+ const cmdKeyword = cmdToMatch.split(" ")[1] || cmdToMatch;
1602
+ const GENERIC_KEYWORDS = [
1603
+ "dev",
1604
+ "run",
1605
+ "start",
1606
+ "serve",
1607
+ "build",
1608
+ "test"
1609
+ ];
1610
+ if (GENERIC_KEYWORDS.includes(cmdKeyword.toLowerCase())) {
1611
+ return;
1612
+ }
1613
+ const currentPid = process.pid;
1614
+ const result = await psExec(`Get-CimInstance Win32_Process -Filter "Name='bun.exe'" | Where-Object { $_.CommandLine -like '*${cmdKeyword.replace(/'/g, "''").replace(/([&[\](){}^$|\\*?+])/g, "$&")}*' -and $_.ProcessId -ne ${currentPid} } | Select-Object -ExpandProperty ProcessId`, 3000);
1615
+ const zombiePids = result.split(`
1616
+ `).map((l) => parseInt(l.trim())).filter((n) => !isNaN(n) && n > 0 && n !== currentPid);
1617
+ for (const zPid of zombiePids) {
1618
+ await $2`taskkill /F /T /PID ${zPid}`.nothrow().quiet();
1619
+ }
1620
+ if (zombiePids.length > 0) {
1621
+ announce(`\uD83E\uDDF9 Swept ${zombiePids.length} zombie process(es)`, "Zombie Cleanup");
1622
+ }
1623
+ } catch {}
1624
+ });
1625
+ }
1626
+ await retryDatabaseOperation(() => removeProcessByName(name));
1627
+ } else {
1628
+ if (!directory || !name || !command) {
1629
+ error("'directory', 'name', and 'command' parameters are required for new processes.");
1630
+ }
1631
+ validateDirectory(directory);
1632
+ $2.cwd(directory);
1159
1633
  }
1160
- await retryDatabaseOperation(() => removeProcessByName(name));
1161
- } else {
1162
- if (!directory || !name || !command) {
1163
- error("'directory', 'name', and 'command' parameters are required for new processes.");
1634
+ const storedCommand = command || existingProcess.command;
1635
+ const finalCommand = resolveInternalBgrunCommand(storedCommand);
1636
+ const finalDirectory = directory || existingProcess?.workdir;
1637
+ let finalEnv = env || (existingProcess ? parseEnvString(existingProcess.env) : {});
1638
+ let finalConfigPath;
1639
+ if (configPath !== undefined) {
1640
+ finalConfigPath = configPath;
1641
+ } else if (existingProcess) {
1642
+ finalConfigPath = existingProcess.configPath;
1643
+ } else {
1644
+ finalConfigPath = ".config.toml";
1164
1645
  }
1165
- validateDirectory(directory);
1166
- $2.cwd(directory);
1167
- }
1168
- const finalCommand = command || existingProcess.command;
1169
- const finalDirectory = directory || existingProcess?.workdir;
1170
- let finalEnv = env || (existingProcess ? parseEnvString(existingProcess.env) : {});
1171
- if (!("BGR_KEEP_ALIVE" in finalEnv)) {
1172
- finalEnv.BGR_KEEP_ALIVE = "true";
1173
- }
1174
- let finalConfigPath;
1175
- if (configPath !== undefined) {
1176
- finalConfigPath = configPath;
1177
- } else if (existingProcess) {
1178
- finalConfigPath = existingProcess.configPath;
1179
- } else {
1180
- finalConfigPath = ".config.toml";
1181
- }
1182
- if (finalConfigPath) {
1183
- const fullConfigPath = join3(finalDirectory, finalConfigPath);
1184
- if (await Bun.file(fullConfigPath).exists()) {
1185
- const configEnv = await run.measure(`Parse config "${finalConfigPath}"`, async () => {
1186
- try {
1187
- return await parseConfigFile(fullConfigPath);
1188
- } catch (err) {
1189
- console.warn(`Warning: Failed to parse config file ${finalConfigPath}: ${err.message}`);
1190
- return null;
1646
+ if (finalConfigPath) {
1647
+ const fullConfigPath = join5(finalDirectory, finalConfigPath);
1648
+ if (await Bun.file(fullConfigPath).exists()) {
1649
+ const configEnv = await run.measure(`Parse config "${finalConfigPath}"`, async () => {
1650
+ try {
1651
+ return await parseConfigFile(fullConfigPath);
1652
+ } catch (err) {
1653
+ console.warn(`Warning: Failed to parse config file ${finalConfigPath}: ${err.message}`);
1654
+ return null;
1655
+ }
1656
+ });
1657
+ if (configEnv) {
1658
+ finalEnv = { ...finalEnv, ...configEnv };
1659
+ console.log(`Loaded config from ${finalConfigPath}`);
1191
1660
  }
1192
- });
1193
- if (configEnv) {
1194
- finalEnv = { ...finalEnv, ...configEnv };
1195
- console.log(`Loaded config from ${finalConfigPath}`);
1661
+ } else {
1662
+ console.log(`Config file '${finalConfigPath}' not found, continuing without it.`);
1196
1663
  }
1197
- } else {
1198
- console.log(`Config file '${finalConfigPath}' not found, continuing without it.`);
1199
1664
  }
1665
+ const stdoutPath = stdout || existingProcess?.stdout_path || join5(homePath2, ".bgr", `${name}-out.txt`);
1666
+ Bun.write(stdoutPath, "");
1667
+ const stderrPath = stderr || existingProcess?.stderr_path || join5(homePath2, ".bgr", `${name}-err.txt`);
1668
+ Bun.write(stderrPath, "");
1669
+ const actualPid = await run.measure(`Spawn "${name}" \u2192 ${finalCommand}`, async () => {
1670
+ const newProcess = Bun.spawn(getShellCommand(finalCommand), {
1671
+ env: buildManagedProcessEnv(Bun.env, finalEnv),
1672
+ cwd: finalDirectory,
1673
+ stdout: Bun.file(stdoutPath),
1674
+ stderr: Bun.file(stderrPath)
1675
+ });
1676
+ newProcess.unref();
1677
+ await sleep2(100);
1678
+ const pid = await findChildPid(newProcess.pid);
1679
+ await sleep2(400);
1680
+ return pid;
1681
+ }) ?? 0;
1682
+ await retryDatabaseOperation(() => insertProcess({
1683
+ pid: actualPid,
1684
+ workdir: finalDirectory,
1685
+ command: finalCommand,
1686
+ name,
1687
+ env: stringifyEnvString(finalEnv),
1688
+ configPath: finalConfigPath || "",
1689
+ stdout_path: stdoutPath,
1690
+ stderr_path: stderrPath
1691
+ }));
1692
+ if (!isInternalProcessName(name)) {
1693
+ await syncProcessWatcher(name, finalEnv);
1694
+ }
1695
+ announce(`${existingProcess ? "\uD83D\uDD04 Restarted" : "\uD83D\uDE80 Launched"} process "${name}" with PID ${actualPid}`, "Process Started");
1696
+ } finally {
1697
+ releaseOperationLock();
1200
1698
  }
1201
- const stdoutPath = stdout || existingProcess?.stdout_path || join3(homePath2, ".bgr", `${name}-out.txt`);
1202
- Bun.write(stdoutPath, "");
1203
- const stderrPath = stderr || existingProcess?.stderr_path || join3(homePath2, ".bgr", `${name}-err.txt`);
1204
- Bun.write(stderrPath, "");
1205
- const actualPid = await run.measure(`Spawn "${name}" \u2192 ${finalCommand}`, async () => {
1206
- const newProcess = Bun.spawn(getShellCommand(finalCommand), {
1207
- env: { ...Bun.env, ...finalEnv },
1208
- cwd: finalDirectory,
1209
- stdout: Bun.file(stdoutPath),
1210
- stderr: Bun.file(stderrPath)
1211
- });
1212
- newProcess.unref();
1213
- await sleep2(100);
1214
- const pid = await findChildPid(newProcess.pid);
1215
- await sleep2(400);
1216
- return pid;
1217
- }) ?? 0;
1218
- await retryDatabaseOperation(() => insertProcess({
1219
- pid: actualPid,
1220
- workdir: finalDirectory,
1221
- command: finalCommand,
1222
- name,
1223
- env: Object.entries(finalEnv).map(([k, v]) => `${k}=${v}`).join(","),
1224
- configPath: finalConfigPath || "",
1225
- stdout_path: stdoutPath,
1226
- stderr_path: stderrPath
1227
- }));
1228
- announce(`${existingProcess ? "\uD83D\uDD04 Restarted" : "\uD83D\uDE80 Launched"} process "${name}" with PID ${actualPid}`, "Process Started");
1229
1699
  }
1230
- var homePath2, run;
1231
- var init_run = __esm(() => {
1232
- init_db();
1233
- init_platform();
1234
- init_logger();
1235
- init_utils();
1236
- homePath2 = getHomeDir();
1237
- run = createMeasure2("run");
1238
- });
1239
1700
 
1240
1701
  // src/deploy.ts
1241
- init_db();
1242
- init_run();
1702
+ init_utils();
1243
1703
  var {$: $3 } = globalThis.Bun;
1244
1704
  function formatDeployToolError(manager, error2) {
1245
1705
  const raw = error2 instanceof Error ? error2.message : String(error2 ?? "Unknown error");
@@ -1250,7 +1710,7 @@ function formatDeployToolError(manager, error2) {
1250
1710
  return `Dependency install failed with ${manager}: ${raw}`;
1251
1711
  }
1252
1712
  function isInternalProcess(name) {
1253
- return name === "bgr-dashboard" || name === "bgr-guard";
1713
+ return isInternalProcessName(name);
1254
1714
  }
1255
1715
  async function pathExists(path) {
1256
1716
  return await Bun.file(path).exists();