bgrun 3.12.16 → 3.12.22
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 +54 -15
- package/dashboard/app/api/guard/route.ts +5 -13
- package/dashboard/app/api/guard-all/route.ts +6 -14
- package/dashboard/app/api/guard-events/route.ts +3 -3
- package/dashboard/app/api/next-port/route.ts +27 -7
- package/dashboard/app/api/processes/route.ts +31 -5
- package/dashboard/app/page.client.tsx +2 -19
- package/dashboard/app/page.tsx +1 -5
- package/dashboard/lib/runtime.ts +58 -49
- package/dist/api.js +912 -234
- package/dist/deploy.js +691 -231
- package/dist/deps.js +151 -70
- package/dist/index.js +1276 -717
- package/dist/server.js +151 -639
- package/package.json +3 -2
package/dist/api.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,
|
|
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
|
-
|
|
52
|
-
const
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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.
|
|
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`)
|
|
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
|
-
|
|
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
|
-
|
|
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,14 @@ var init_deps = __esm(() => {
|
|
|
996
1077
|
init_utils();
|
|
997
1078
|
});
|
|
998
1079
|
|
|
1080
|
+
// src/api.ts
|
|
1081
|
+
init_db();
|
|
1082
|
+
init_platform();
|
|
1083
|
+
|
|
1084
|
+
// src/commands/run.ts
|
|
1085
|
+
init_db();
|
|
1086
|
+
init_platform();
|
|
1087
|
+
|
|
999
1088
|
// src/logger.ts
|
|
1000
1089
|
import boxen from "boxen";
|
|
1001
1090
|
import chalk from "chalk";
|
|
@@ -1009,6 +1098,13 @@ function announce(message, title) {
|
|
|
1009
1098
|
borderStyle: "round"
|
|
1010
1099
|
}));
|
|
1011
1100
|
}
|
|
1101
|
+
|
|
1102
|
+
class BgrunError extends Error {
|
|
1103
|
+
constructor(message) {
|
|
1104
|
+
super(message);
|
|
1105
|
+
this.name = "BgrunError";
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1012
1108
|
function error(message) {
|
|
1013
1109
|
const text = message instanceof Error ? message.stack || message.message : String(message);
|
|
1014
1110
|
console.error(boxen(chalk.red(text), {
|
|
@@ -1021,15 +1117,9 @@ function error(message) {
|
|
|
1021
1117
|
}));
|
|
1022
1118
|
throw new BgrunError(text);
|
|
1023
1119
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
constructor(message) {
|
|
1028
|
-
super(message);
|
|
1029
|
-
this.name = "BgrunError";
|
|
1030
|
-
}
|
|
1031
|
-
};
|
|
1032
|
-
});
|
|
1120
|
+
|
|
1121
|
+
// src/commands/run.ts
|
|
1122
|
+
init_utils();
|
|
1033
1123
|
|
|
1034
1124
|
// src/config.ts
|
|
1035
1125
|
function formatEnvKey(key) {
|
|
@@ -1061,190 +1151,738 @@ async function parseConfigFile(configPath) {
|
|
|
1061
1151
|
const parsedConfig = await import(importPath).then((m) => m.default);
|
|
1062
1152
|
return flattenConfig(parsedConfig);
|
|
1063
1153
|
}
|
|
1154
|
+
async function loadConfigEnv(directory, configPath = ".config.toml") {
|
|
1155
|
+
const { join: join4 } = await import("path");
|
|
1156
|
+
const fullConfigPath = join4(directory, configPath);
|
|
1157
|
+
if (!await Bun.file(fullConfigPath).exists()) {
|
|
1158
|
+
return {
|
|
1159
|
+
configEnv: {},
|
|
1160
|
+
fullConfigPath,
|
|
1161
|
+
exists: false
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
return {
|
|
1165
|
+
configEnv: await parseConfigFile(fullConfigPath),
|
|
1166
|
+
fullConfigPath,
|
|
1167
|
+
exists: true
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1064
1170
|
|
|
1065
1171
|
// src/commands/run.ts
|
|
1066
1172
|
var {$: $2 } = globalThis.Bun;
|
|
1067
1173
|
var {sleep: sleep2 } = globalThis.Bun;
|
|
1068
|
-
import { join as
|
|
1174
|
+
import { join as join5 } from "path";
|
|
1069
1175
|
import { createMeasure as createMeasure2 } from "measure-fn";
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1176
|
+
|
|
1177
|
+
// src/watcher.ts
|
|
1178
|
+
init_db();
|
|
1179
|
+
init_platform();
|
|
1180
|
+
import { join as join4 } from "path";
|
|
1181
|
+
|
|
1182
|
+
// src/cli-helpers.ts
|
|
1183
|
+
init_db();
|
|
1184
|
+
var MONTH_NAMES = [
|
|
1185
|
+
"january",
|
|
1186
|
+
"february",
|
|
1187
|
+
"march",
|
|
1188
|
+
"april",
|
|
1189
|
+
"may",
|
|
1190
|
+
"june",
|
|
1191
|
+
"july",
|
|
1192
|
+
"august",
|
|
1193
|
+
"september",
|
|
1194
|
+
"october",
|
|
1195
|
+
"november",
|
|
1196
|
+
"december"
|
|
1197
|
+
];
|
|
1198
|
+
var DAY_NAMES = [
|
|
1199
|
+
"",
|
|
1200
|
+
"first",
|
|
1201
|
+
"second",
|
|
1202
|
+
"third",
|
|
1203
|
+
"fourth",
|
|
1204
|
+
"fifth",
|
|
1205
|
+
"sixth",
|
|
1206
|
+
"seventh",
|
|
1207
|
+
"eighth",
|
|
1208
|
+
"ninth",
|
|
1209
|
+
"tenth",
|
|
1210
|
+
"eleventh",
|
|
1211
|
+
"twelfth",
|
|
1212
|
+
"thirteenth",
|
|
1213
|
+
"fourteenth",
|
|
1214
|
+
"fifteenth",
|
|
1215
|
+
"sixteenth",
|
|
1216
|
+
"seventeenth",
|
|
1217
|
+
"eighteenth",
|
|
1218
|
+
"nineteenth",
|
|
1219
|
+
"twentieth",
|
|
1220
|
+
"twenty-first",
|
|
1221
|
+
"twenty-second",
|
|
1222
|
+
"twenty-third",
|
|
1223
|
+
"twenty-fourth",
|
|
1224
|
+
"twenty-fifth",
|
|
1225
|
+
"twenty-sixth",
|
|
1226
|
+
"twenty-seventh",
|
|
1227
|
+
"twenty-eighth",
|
|
1228
|
+
"twenty-ninth",
|
|
1229
|
+
"thirtieth",
|
|
1230
|
+
"thirty-first"
|
|
1231
|
+
];
|
|
1232
|
+
function pad2(value) {
|
|
1233
|
+
return String(value).padStart(2, "0");
|
|
1234
|
+
}
|
|
1235
|
+
function buildDateProcessName(now = new Date) {
|
|
1236
|
+
const month = MONTH_NAMES[now.getMonth()] || "process";
|
|
1237
|
+
const day = DAY_NAMES[now.getDate()] || "day";
|
|
1238
|
+
return `${month}-${day}`;
|
|
1239
|
+
}
|
|
1240
|
+
function generateAutoProcessName(now = new Date) {
|
|
1241
|
+
const baseName = buildDateProcessName(now);
|
|
1242
|
+
if (!getProcess(baseName)) {
|
|
1243
|
+
return baseName;
|
|
1244
|
+
}
|
|
1245
|
+
const timeSuffix = `${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`;
|
|
1246
|
+
const timeName = `${baseName}-${timeSuffix}`;
|
|
1247
|
+
if (!getProcess(timeName)) {
|
|
1248
|
+
return timeName;
|
|
1249
|
+
}
|
|
1250
|
+
for (let i = 1;i < 1000; i++) {
|
|
1251
|
+
const candidate = `${timeName}-${i}`;
|
|
1252
|
+
if (!getProcess(candidate)) {
|
|
1253
|
+
return candidate;
|
|
1086
1254
|
}
|
|
1087
1255
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
if (
|
|
1093
|
-
|
|
1094
|
-
|
|
1256
|
+
return `${timeName}-${Date.now()}`;
|
|
1257
|
+
}
|
|
1258
|
+
function shellQuoteArg(arg) {
|
|
1259
|
+
if (process.platform === "win32") {
|
|
1260
|
+
if (/^[A-Za-z0-9_./:\\-]+$/.test(arg))
|
|
1261
|
+
return arg;
|
|
1262
|
+
return `"${arg.replace(/"/g, "\\\"")}"`;
|
|
1263
|
+
}
|
|
1264
|
+
if (/^[A-Za-z0-9_./:-]+$/.test(arg))
|
|
1265
|
+
return arg;
|
|
1266
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
1267
|
+
}
|
|
1268
|
+
function joinCommandArgs(args) {
|
|
1269
|
+
return args.map(shellQuoteArg).join(" ");
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// src/watcher.ts
|
|
1273
|
+
init_utils();
|
|
1274
|
+
var DEFAULT_INTERVAL_MS = 5000;
|
|
1275
|
+
var CRASH_THRESHOLD = 5;
|
|
1276
|
+
var MAX_BACKOFF_MS = 5 * 60000;
|
|
1277
|
+
var STABILITY_WINDOW_MS = 120000;
|
|
1278
|
+
function getWatcherLogPaths(watcherName) {
|
|
1279
|
+
const homePath2 = getHomeDir();
|
|
1280
|
+
return {
|
|
1281
|
+
stdoutPath: join4(homePath2, ".bgr", `${watcherName}-out.txt`),
|
|
1282
|
+
stderrPath: join4(homePath2, ".bgr", `${watcherName}-err.txt`)
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
async function findDetachedWatcherPid(targetName) {
|
|
1286
|
+
if (process.platform !== "win32")
|
|
1287
|
+
return null;
|
|
1288
|
+
const escaped = targetName.replace(/'/g, "''");
|
|
1289
|
+
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);
|
|
1290
|
+
const pid = parseInt(result.trim(), 10);
|
|
1291
|
+
return !isNaN(pid) && pid > 0 ? pid : null;
|
|
1292
|
+
}
|
|
1293
|
+
function getInternalWatcherCommand(targetName) {
|
|
1294
|
+
const quotedTarget = shellQuoteArg(targetName);
|
|
1295
|
+
return {
|
|
1296
|
+
storedCommand: `bunx bgrun --_watch-process ${quotedTarget}`,
|
|
1297
|
+
spawnCommand: `bunx bgrun --_watch-process ${quotedTarget}`
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
async function ensureProcessWatcher(targetName) {
|
|
1301
|
+
if (!targetName || isInternalProcessName(targetName))
|
|
1302
|
+
return;
|
|
1303
|
+
const proc = getProcess(targetName);
|
|
1304
|
+
if (!proc)
|
|
1305
|
+
return;
|
|
1306
|
+
const env = parseEnvString(proc.env || "");
|
|
1307
|
+
if (env.BGR_KEEP_ALIVE !== "true") {
|
|
1308
|
+
await stopProcessWatcher(targetName);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const watcherName = getWatcherProcessName(targetName);
|
|
1312
|
+
const existingWatcher = getProcess(watcherName);
|
|
1313
|
+
if (existingWatcher && await isProcessRunning(existingWatcher.pid, existingWatcher.command)) {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
if (existingWatcher) {
|
|
1317
|
+
await retryDatabaseOperation(() => removeProcessByName(watcherName));
|
|
1318
|
+
}
|
|
1319
|
+
const { stdoutPath, stderrPath } = getWatcherLogPaths(watcherName);
|
|
1320
|
+
await Bun.write(stdoutPath, "");
|
|
1321
|
+
await Bun.write(stderrPath, "");
|
|
1322
|
+
const { storedCommand, spawnCommand } = getInternalWatcherCommand(targetName);
|
|
1323
|
+
const newProcess = Bun.spawn(getShellCommand(spawnCommand), {
|
|
1324
|
+
env: {
|
|
1325
|
+
...Bun.env,
|
|
1326
|
+
BGR_STDOUT: stdoutPath,
|
|
1327
|
+
BGR_STDERR: stderrPath
|
|
1328
|
+
},
|
|
1329
|
+
cwd: getHomeDir(),
|
|
1330
|
+
stdout: "ignore",
|
|
1331
|
+
stderr: "ignore",
|
|
1332
|
+
detached: true
|
|
1333
|
+
});
|
|
1334
|
+
newProcess.unref();
|
|
1335
|
+
await Bun.sleep(1000);
|
|
1336
|
+
let actualPid = await findChildPid(newProcess.pid);
|
|
1337
|
+
if (!await isProcessRunning(actualPid)) {
|
|
1338
|
+
const detachedPid = await findDetachedWatcherPid(targetName);
|
|
1339
|
+
if (detachedPid)
|
|
1340
|
+
actualPid = detachedPid;
|
|
1341
|
+
}
|
|
1342
|
+
await retryDatabaseOperation(() => insertProcess({
|
|
1343
|
+
pid: actualPid,
|
|
1344
|
+
workdir: getHomeDir(),
|
|
1345
|
+
command: storedCommand,
|
|
1346
|
+
name: watcherName,
|
|
1347
|
+
env: stringifyEnvString({ BGR_KEEP_ALIVE: "false", BGR_WATCH_TARGET: targetName }),
|
|
1348
|
+
configPath: "",
|
|
1349
|
+
stdout_path: stdoutPath,
|
|
1350
|
+
stderr_path: stderrPath
|
|
1351
|
+
}));
|
|
1352
|
+
}
|
|
1353
|
+
async function stopProcessWatcher(targetName) {
|
|
1354
|
+
const watcherName = getWatcherProcessName(targetName);
|
|
1355
|
+
const watcherProc = getProcess(watcherName);
|
|
1356
|
+
if (!watcherProc)
|
|
1357
|
+
return;
|
|
1358
|
+
if (await isProcessRunning(watcherProc.pid, watcherProc.command)) {
|
|
1359
|
+
await terminateProcess(watcherProc.pid, true);
|
|
1360
|
+
}
|
|
1361
|
+
await retryDatabaseOperation(() => removeProcessByName(watcherName));
|
|
1362
|
+
}
|
|
1363
|
+
async function syncProcessWatcher(targetName, env) {
|
|
1364
|
+
if (!targetName || isInternalProcessName(targetName))
|
|
1365
|
+
return;
|
|
1366
|
+
if (env.BGR_KEEP_ALIVE === "true") {
|
|
1367
|
+
await ensureProcessWatcher(targetName);
|
|
1368
|
+
} else {
|
|
1369
|
+
await stopProcessWatcher(targetName);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
function getBackoffMs(restartCount) {
|
|
1373
|
+
if (restartCount <= CRASH_THRESHOLD)
|
|
1374
|
+
return 0;
|
|
1375
|
+
const exponent = restartCount - CRASH_THRESHOLD;
|
|
1376
|
+
return Math.min(30000 * Math.pow(2, exponent - 1), MAX_BACKOFF_MS);
|
|
1377
|
+
}
|
|
1378
|
+
async function cleanupWatcher(targetName) {
|
|
1379
|
+
const watcherName = getWatcherProcessName(targetName);
|
|
1380
|
+
await retryDatabaseOperation(() => removeProcessByName(watcherName));
|
|
1381
|
+
}
|
|
1382
|
+
async function startProcessWatcher(targetName, intervalMs = DEFAULT_INTERVAL_MS) {
|
|
1383
|
+
const watcherName = getWatcherProcessName(targetName);
|
|
1384
|
+
const releaseWatcherLock = acquireProcessOperationLock(watcherName);
|
|
1385
|
+
let restartCount = 0;
|
|
1386
|
+
let nextRestartAt = 0;
|
|
1387
|
+
let lastSeenAliveAt = 0;
|
|
1388
|
+
try {
|
|
1389
|
+
console.log(`[watcher] Watching "${targetName}" every ${Math.round(intervalMs / 1000)}s`);
|
|
1390
|
+
while (true) {
|
|
1391
|
+
const proc = getProcess(targetName);
|
|
1392
|
+
if (!proc) {
|
|
1393
|
+
console.log(`[watcher] Target "${targetName}" removed; exiting watcher`);
|
|
1394
|
+
break;
|
|
1395
|
+
}
|
|
1396
|
+
const env = parseEnvString(proc.env || "");
|
|
1397
|
+
if (env.BGR_KEEP_ALIVE !== "true") {
|
|
1398
|
+
console.log(`[watcher] Guard disabled for "${targetName}"; exiting watcher`);
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
if (proc.pid <= 0 || isProcessOperationLocked(targetName)) {
|
|
1402
|
+
await Bun.sleep(intervalMs);
|
|
1403
|
+
continue;
|
|
1095
1404
|
}
|
|
1096
|
-
|
|
1405
|
+
const alive = await isProcessRunning(proc.pid, proc.command);
|
|
1406
|
+
if (!alive) {
|
|
1407
|
+
const now = Date.now();
|
|
1408
|
+
if (now < nextRestartAt) {
|
|
1409
|
+
await Bun.sleep(intervalMs);
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1097
1412
|
try {
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
}
|
|
1413
|
+
console.log(`[watcher] Restarting "${targetName}" after detected crash`);
|
|
1414
|
+
await handleRun({
|
|
1415
|
+
action: "run",
|
|
1416
|
+
name: targetName,
|
|
1417
|
+
force: true,
|
|
1418
|
+
remoteName: ""
|
|
1419
|
+
});
|
|
1420
|
+
restartCount++;
|
|
1421
|
+
const backoffMs = getBackoffMs(restartCount);
|
|
1422
|
+
nextRestartAt = backoffMs > 0 ? now + backoffMs : 0;
|
|
1423
|
+
lastSeenAliveAt = 0;
|
|
1424
|
+
addHistoryEntry(targetName, "guard_restart", proc.pid, { by: watcherName, count: restartCount, backoffMs });
|
|
1105
1425
|
} catch (err) {
|
|
1106
|
-
|
|
1426
|
+
addHistoryEntry(targetName, "guard_restart_failed", proc.pid, { by: watcherName, error: err?.message || String(err) });
|
|
1427
|
+
console.error(`[watcher] Failed to restart "${targetName}": ${err.message}`);
|
|
1107
1428
|
}
|
|
1108
|
-
})
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1429
|
+
} else if (restartCount > 0) {
|
|
1430
|
+
const now = Date.now();
|
|
1431
|
+
if (!lastSeenAliveAt) {
|
|
1432
|
+
lastSeenAliveAt = now;
|
|
1433
|
+
} else if (now - lastSeenAliveAt >= STABILITY_WINDOW_MS) {
|
|
1434
|
+
restartCount = 0;
|
|
1435
|
+
nextRestartAt = 0;
|
|
1436
|
+
lastSeenAliveAt = 0;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
await Bun.sleep(intervalMs);
|
|
1117
1440
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1441
|
+
} finally {
|
|
1442
|
+
releaseWatcherLock();
|
|
1443
|
+
await cleanupWatcher(targetName);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
function getGuardRestartCounts() {
|
|
1447
|
+
const counts = new Map;
|
|
1448
|
+
const entries = db.history.select().where({ event: "guard_restart" }).all();
|
|
1449
|
+
for (const entry of entries) {
|
|
1450
|
+
counts.set(entry.process_name, (counts.get(entry.process_name) || 0) + 1);
|
|
1451
|
+
}
|
|
1452
|
+
return counts;
|
|
1453
|
+
}
|
|
1454
|
+
function getRecentGuardEvents(limit = 100) {
|
|
1455
|
+
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);
|
|
1456
|
+
return rows.map((row) => ({
|
|
1457
|
+
time: new Date(row.timestamp).getTime(),
|
|
1458
|
+
name: row.process_name,
|
|
1459
|
+
action: "restart",
|
|
1460
|
+
success: row.event === "guard_restart"
|
|
1461
|
+
}));
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
// src/commands/run.ts
|
|
1465
|
+
var homePath2 = getHomeDir();
|
|
1466
|
+
var run = createMeasure2("run");
|
|
1467
|
+
var INTERNAL_BUNX_PREFIX = "bunx bgrun";
|
|
1468
|
+
function resolveInternalBgrunCommand(command) {
|
|
1469
|
+
const trimmed = command.trim();
|
|
1470
|
+
if (!trimmed.startsWith("bgrun --_") && !trimmed.startsWith(`${INTERNAL_BUNX_PREFIX} --_`)) {
|
|
1471
|
+
return command;
|
|
1472
|
+
}
|
|
1473
|
+
if (trimmed.startsWith(`${INTERNAL_BUNX_PREFIX} --_`)) {
|
|
1474
|
+
return trimmed;
|
|
1475
|
+
}
|
|
1476
|
+
return `${INTERNAL_BUNX_PREFIX}${trimmed.slice("bgrun".length)}`;
|
|
1477
|
+
}
|
|
1478
|
+
async function handleRun(options) {
|
|
1479
|
+
const {
|
|
1480
|
+
command,
|
|
1481
|
+
directory,
|
|
1482
|
+
env,
|
|
1483
|
+
name,
|
|
1484
|
+
configPath,
|
|
1485
|
+
force,
|
|
1486
|
+
fetch,
|
|
1487
|
+
stdout,
|
|
1488
|
+
stderr
|
|
1489
|
+
} = options;
|
|
1490
|
+
const releaseOperationLock = name ? acquireProcessOperationLock(name) : () => {};
|
|
1491
|
+
try {
|
|
1492
|
+
const existingProcess = name ? getProcess(name) : null;
|
|
1493
|
+
if (name && existingProcess) {
|
|
1494
|
+
const { getUnmetDeps: getUnmetDeps2 } = await Promise.resolve().then(() => (init_deps(), exports_deps));
|
|
1495
|
+
const unmet = await getUnmetDeps2(name);
|
|
1496
|
+
if (unmet.length > 0) {
|
|
1497
|
+
await run.measure(`Start ${unmet.length} dependencies for "${name}"`, async () => {
|
|
1498
|
+
for (const depName of unmet) {
|
|
1499
|
+
const depProc = getProcess(depName);
|
|
1500
|
+
if (depProc) {
|
|
1501
|
+
announce(`\uD83D\uDCE6 Starting dependency "${depName}" for "${name}"`, "Dependency");
|
|
1502
|
+
await handleRun({
|
|
1503
|
+
action: "run",
|
|
1504
|
+
name: depName,
|
|
1505
|
+
force: true,
|
|
1506
|
+
remoteName: ""
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1123
1512
|
}
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1513
|
+
if (existingProcess) {
|
|
1514
|
+
const finalDirectory2 = directory || existingProcess.workdir;
|
|
1515
|
+
validateDirectory(finalDirectory2);
|
|
1516
|
+
$2.cwd(finalDirectory2);
|
|
1517
|
+
if (fetch) {
|
|
1518
|
+
if (!__require("fs").existsSync(__require("path").join(finalDirectory2, ".git"))) {
|
|
1519
|
+
error(`Cannot --fetch: '${finalDirectory2}' is not a Git repository.`);
|
|
1128
1520
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
await
|
|
1133
|
-
await
|
|
1521
|
+
await run.measure(`Git fetch "${name}"`, async () => {
|
|
1522
|
+
try {
|
|
1523
|
+
await $2`git fetch origin`;
|
|
1524
|
+
const localHash = (await $2`git rev-parse HEAD`.text()).trim();
|
|
1525
|
+
const remoteHash = (await $2`git rev-parse origin/$(git rev-parse --abbrev-ref HEAD)`.text()).trim();
|
|
1526
|
+
if (localHash !== remoteHash) {
|
|
1527
|
+
await $2`git pull origin $(git rev-parse --abbrev-ref HEAD)`;
|
|
1528
|
+
announce("\uD83D\uDCE5 Pulled latest changes", "Git Update");
|
|
1529
|
+
}
|
|
1530
|
+
} catch (err) {
|
|
1531
|
+
error(`Failed to pull latest changes: ${err}`);
|
|
1134
1532
|
}
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
const isRunning = await isProcessRunning(existingProcess.pid);
|
|
1536
|
+
if (isRunning && !force) {
|
|
1537
|
+
error(`Process '${name}' is currently running. Use --force to restart.`);
|
|
1538
|
+
}
|
|
1539
|
+
let actualPid2 = existingProcess.pid;
|
|
1540
|
+
if (!isRunning && !force) {
|
|
1541
|
+
const reconciled = await reconcileProcessPids([
|
|
1542
|
+
{
|
|
1543
|
+
name,
|
|
1544
|
+
pid: existingProcess.pid,
|
|
1545
|
+
command: existingProcess.command,
|
|
1546
|
+
workdir: existingProcess.workdir
|
|
1547
|
+
}
|
|
1548
|
+
], new Set([existingProcess.pid]));
|
|
1549
|
+
const newPid = reconciled.get(name);
|
|
1550
|
+
if (newPid) {
|
|
1551
|
+
console.log(`[run] Reconciled dead PID ${existingProcess.pid} to live PID ${newPid}`);
|
|
1552
|
+
actualPid2 = newPid;
|
|
1553
|
+
updateProcessPid(name, newPid);
|
|
1135
1554
|
}
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1555
|
+
}
|
|
1556
|
+
let detectedPorts = [];
|
|
1557
|
+
const actuallyRunning = await isProcessRunning(actualPid2);
|
|
1558
|
+
if (actuallyRunning) {
|
|
1559
|
+
detectedPorts = await getProcessPorts(actualPid2);
|
|
1560
|
+
}
|
|
1561
|
+
if (actuallyRunning) {
|
|
1562
|
+
await run.measure(`Terminate "${name}" (PID ${actualPid2})`, async () => {
|
|
1563
|
+
await terminateProcess(actualPid2);
|
|
1564
|
+
announce(`\uD83D\uDD25 Terminated existing process '${name}'`, "Process Terminated");
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
if (detectedPorts.length > 0) {
|
|
1568
|
+
await run.measure(`Port cleanup [${detectedPorts.join(", ")}]`, async () => {
|
|
1569
|
+
for (const port of detectedPorts) {
|
|
1570
|
+
await killProcessOnPort(port);
|
|
1146
1571
|
}
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1572
|
+
for (const port of detectedPorts) {
|
|
1573
|
+
const freed = await waitForPortFree(port, 5000);
|
|
1574
|
+
if (!freed) {
|
|
1575
|
+
await killProcessOnPort(port);
|
|
1576
|
+
await waitForPortFree(port, 3000);
|
|
1577
|
+
}
|
|
1153
1578
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
const existingEnv = existingProcess.env ? parseEnvString(existingProcess.env) : {};
|
|
1582
|
+
const declaredPort = getDeclaredPort(existingEnv, existingProcess.command);
|
|
1583
|
+
if (declaredPort && !detectedPorts.includes(declaredPort)) {
|
|
1584
|
+
await run.measure(`Declared port cleanup [${declaredPort}]`, async () => {
|
|
1585
|
+
const portFree = await isPortFree(declaredPort);
|
|
1586
|
+
if (!portFree) {
|
|
1587
|
+
console.log(`[run] Declared port ${declaredPort} is busy (orphaned process), cleaning up...`);
|
|
1588
|
+
await killProcessOnPort(declaredPort);
|
|
1589
|
+
const freed = await waitForPortFree(declaredPort, 5000);
|
|
1590
|
+
if (!freed) {
|
|
1591
|
+
console.warn(`[run] Port ${declaredPort} still busy after cleanup, retrying...`);
|
|
1592
|
+
await killProcessOnPort(declaredPort);
|
|
1593
|
+
await waitForPortFree(declaredPort, 3000);
|
|
1594
|
+
}
|
|
1156
1595
|
}
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
const cmdToMatch = existingProcess.command;
|
|
1599
|
+
if (cmdToMatch) {
|
|
1600
|
+
await run.measure("Zombie sweep", async () => {
|
|
1601
|
+
try {
|
|
1602
|
+
const cmdKeyword = cmdToMatch.split(" ")[1] || cmdToMatch;
|
|
1603
|
+
const GENERIC_KEYWORDS = [
|
|
1604
|
+
"dev",
|
|
1605
|
+
"run",
|
|
1606
|
+
"start",
|
|
1607
|
+
"serve",
|
|
1608
|
+
"build",
|
|
1609
|
+
"test"
|
|
1610
|
+
];
|
|
1611
|
+
if (GENERIC_KEYWORDS.includes(cmdKeyword.toLowerCase())) {
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
const currentPid = process.pid;
|
|
1615
|
+
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);
|
|
1616
|
+
const zombiePids = result.split(`
|
|
1617
|
+
`).map((l) => parseInt(l.trim())).filter((n) => !isNaN(n) && n > 0 && n !== currentPid);
|
|
1618
|
+
for (const zPid of zombiePids) {
|
|
1619
|
+
await $2`taskkill /F /T /PID ${zPid}`.nothrow().quiet();
|
|
1620
|
+
}
|
|
1621
|
+
if (zombiePids.length > 0) {
|
|
1622
|
+
announce(`\uD83E\uDDF9 Swept ${zombiePids.length} zombie process(es)`, "Zombie Cleanup");
|
|
1623
|
+
}
|
|
1624
|
+
} catch {}
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
await retryDatabaseOperation(() => removeProcessByName(name));
|
|
1628
|
+
} else {
|
|
1629
|
+
if (!directory || !name || !command) {
|
|
1630
|
+
error("'directory', 'name', and 'command' parameters are required for new processes.");
|
|
1631
|
+
}
|
|
1632
|
+
validateDirectory(directory);
|
|
1633
|
+
$2.cwd(directory);
|
|
1159
1634
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1635
|
+
const storedCommand = command || existingProcess.command;
|
|
1636
|
+
const finalCommand = resolveInternalBgrunCommand(storedCommand);
|
|
1637
|
+
const finalDirectory = directory || existingProcess?.workdir;
|
|
1638
|
+
let finalEnv = env || (existingProcess ? parseEnvString(existingProcess.env) : {});
|
|
1639
|
+
let finalConfigPath;
|
|
1640
|
+
if (configPath !== undefined) {
|
|
1641
|
+
finalConfigPath = configPath;
|
|
1642
|
+
} else if (existingProcess) {
|
|
1643
|
+
finalConfigPath = existingProcess.configPath;
|
|
1644
|
+
} else {
|
|
1645
|
+
finalConfigPath = ".config.toml";
|
|
1164
1646
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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;
|
|
1647
|
+
if (finalConfigPath) {
|
|
1648
|
+
const fullConfigPath = join5(finalDirectory, finalConfigPath);
|
|
1649
|
+
if (await Bun.file(fullConfigPath).exists()) {
|
|
1650
|
+
const configEnv = await run.measure(`Parse config "${finalConfigPath}"`, async () => {
|
|
1651
|
+
try {
|
|
1652
|
+
return await parseConfigFile(fullConfigPath);
|
|
1653
|
+
} catch (err) {
|
|
1654
|
+
console.warn(`Warning: Failed to parse config file ${finalConfigPath}: ${err.message}`);
|
|
1655
|
+
return null;
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
if (configEnv) {
|
|
1659
|
+
finalEnv = { ...finalEnv, ...configEnv };
|
|
1660
|
+
console.log(`Loaded config from ${finalConfigPath}`);
|
|
1191
1661
|
}
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
finalEnv = { ...finalEnv, ...configEnv };
|
|
1195
|
-
console.log(`Loaded config from ${finalConfigPath}`);
|
|
1662
|
+
} else {
|
|
1663
|
+
console.log(`Config file '${finalConfigPath}' not found, continuing without it.`);
|
|
1196
1664
|
}
|
|
1197
|
-
} else {
|
|
1198
|
-
console.log(`Config file '${finalConfigPath}' not found, continuing without it.`);
|
|
1199
1665
|
}
|
|
1666
|
+
const stdoutPath = stdout || existingProcess?.stdout_path || join5(homePath2, ".bgr", `${name}-out.txt`);
|
|
1667
|
+
Bun.write(stdoutPath, "");
|
|
1668
|
+
const stderrPath = stderr || existingProcess?.stderr_path || join5(homePath2, ".bgr", `${name}-err.txt`);
|
|
1669
|
+
Bun.write(stderrPath, "");
|
|
1670
|
+
const actualPid = await run.measure(`Spawn "${name}" \u2192 ${finalCommand}`, async () => {
|
|
1671
|
+
const newProcess = Bun.spawn(getShellCommand(finalCommand), {
|
|
1672
|
+
env: buildManagedProcessEnv(Bun.env, finalEnv),
|
|
1673
|
+
cwd: finalDirectory,
|
|
1674
|
+
stdout: Bun.file(stdoutPath),
|
|
1675
|
+
stderr: Bun.file(stderrPath)
|
|
1676
|
+
});
|
|
1677
|
+
newProcess.unref();
|
|
1678
|
+
await sleep2(100);
|
|
1679
|
+
const pid = await findChildPid(newProcess.pid);
|
|
1680
|
+
await sleep2(400);
|
|
1681
|
+
return pid;
|
|
1682
|
+
}) ?? 0;
|
|
1683
|
+
await retryDatabaseOperation(() => insertProcess({
|
|
1684
|
+
pid: actualPid,
|
|
1685
|
+
workdir: finalDirectory,
|
|
1686
|
+
command: finalCommand,
|
|
1687
|
+
name,
|
|
1688
|
+
env: stringifyEnvString(finalEnv),
|
|
1689
|
+
configPath: finalConfigPath || "",
|
|
1690
|
+
stdout_path: stdoutPath,
|
|
1691
|
+
stderr_path: stderrPath
|
|
1692
|
+
}));
|
|
1693
|
+
if (!isInternalProcessName(name)) {
|
|
1694
|
+
await syncProcessWatcher(name, finalEnv);
|
|
1695
|
+
}
|
|
1696
|
+
announce(`${existingProcess ? "\uD83D\uDD04 Restarted" : "\uD83D\uDE80 Launched"} process "${name}" with PID ${actualPid}`, "Process Started");
|
|
1697
|
+
} finally {
|
|
1698
|
+
releaseOperationLock();
|
|
1200
1699
|
}
|
|
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
1700
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1701
|
+
// src/commands/envit.ts
|
|
1702
|
+
function parseEnvitArgs(args) {
|
|
1703
|
+
let directory;
|
|
1704
|
+
let configPath;
|
|
1705
|
+
let shell;
|
|
1706
|
+
let help = false;
|
|
1707
|
+
for (let i = 0;i < args.length; i++) {
|
|
1708
|
+
const arg = args[i];
|
|
1709
|
+
if (arg === "--help" || arg === "-h") {
|
|
1710
|
+
help = true;
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
if (arg === "--directory") {
|
|
1714
|
+
directory = args[++i];
|
|
1715
|
+
if (!directory)
|
|
1716
|
+
error("Missing value for --directory.");
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
if (arg.startsWith("--directory=")) {
|
|
1720
|
+
directory = arg.slice("--directory=".length);
|
|
1721
|
+
if (!directory)
|
|
1722
|
+
error("Missing value for --directory.");
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
if (arg === "--config") {
|
|
1726
|
+
configPath = args[++i];
|
|
1727
|
+
if (!configPath)
|
|
1728
|
+
error("Missing value for --config.");
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
if (arg.startsWith("--config=")) {
|
|
1732
|
+
configPath = arg.slice("--config=".length);
|
|
1733
|
+
if (!configPath)
|
|
1734
|
+
error("Missing value for --config.");
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
if (arg === "--shell") {
|
|
1738
|
+
const value = args[++i];
|
|
1739
|
+
if (!value)
|
|
1740
|
+
error("Missing value for --shell.");
|
|
1741
|
+
shell = normalizeEnvitShell(value);
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
if (arg.startsWith("--shell=")) {
|
|
1745
|
+
shell = normalizeEnvitShell(arg.slice("--shell=".length));
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
if (!configPath) {
|
|
1749
|
+
configPath = arg;
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
error(`Unexpected argument '${arg}'. envit prints shell export commands and does not run a child process.`);
|
|
1753
|
+
}
|
|
1754
|
+
return { directory, configPath, shell, help };
|
|
1755
|
+
}
|
|
1756
|
+
function normalizeEnvitShell(value) {
|
|
1757
|
+
const normalized = value.trim().toLowerCase();
|
|
1758
|
+
if (normalized === "powershell" || normalized === "pwsh" || normalized === "ps1")
|
|
1759
|
+
return "powershell";
|
|
1760
|
+
if (normalized === "cmd" || normalized === "bat")
|
|
1761
|
+
return "cmd";
|
|
1762
|
+
if (normalized === "sh" || normalized === "bash" || normalized === "zsh")
|
|
1763
|
+
return "sh";
|
|
1764
|
+
if (normalized === "json")
|
|
1765
|
+
return "json";
|
|
1766
|
+
error(`Unsupported shell '${value}'. Use powershell, cmd, sh, or json.`);
|
|
1767
|
+
}
|
|
1768
|
+
function detectEnvitShell() {
|
|
1769
|
+
if (process.platform === "win32") {
|
|
1770
|
+
return "powershell";
|
|
1771
|
+
}
|
|
1772
|
+
return "sh";
|
|
1773
|
+
}
|
|
1774
|
+
function escapePowerShell(value) {
|
|
1775
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1776
|
+
}
|
|
1777
|
+
function escapeCmd(value) {
|
|
1778
|
+
return value.replace(/"/g, '""');
|
|
1779
|
+
}
|
|
1780
|
+
function escapeSh(value) {
|
|
1781
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1782
|
+
}
|
|
1783
|
+
function renderEnvitOutput(env, shell) {
|
|
1784
|
+
if (shell === "json") {
|
|
1785
|
+
return JSON.stringify(env, null, 2);
|
|
1786
|
+
}
|
|
1787
|
+
const lines = Object.entries(env).map(([key, value]) => {
|
|
1788
|
+
if (shell === "powershell") {
|
|
1789
|
+
return `$env:${key}=${escapePowerShell(value)}`;
|
|
1790
|
+
}
|
|
1791
|
+
if (shell === "cmd") {
|
|
1792
|
+
return `set "${key}=${escapeCmd(value)}"`;
|
|
1793
|
+
}
|
|
1794
|
+
return `export ${key}=${escapeSh(value)}`;
|
|
1795
|
+
});
|
|
1796
|
+
return lines.join(`
|
|
1797
|
+
`);
|
|
1798
|
+
}
|
|
1799
|
+
async function handleEnvit(options) {
|
|
1800
|
+
const cwd = options.directory || process.cwd();
|
|
1801
|
+
const configPath = options.configPath || ".config.toml";
|
|
1802
|
+
const shell = options.shell || detectEnvitShell();
|
|
1803
|
+
const { configEnv, exists } = await loadConfigEnv(cwd, configPath);
|
|
1804
|
+
if (!exists) {
|
|
1805
|
+
error(`Config file '${configPath}' not found.`);
|
|
1806
|
+
}
|
|
1807
|
+
console.log(renderEnvitOutput(configEnv, shell));
|
|
1808
|
+
}
|
|
1809
|
+
// src/commands/inline.ts
|
|
1810
|
+
init_utils();
|
|
1811
|
+
function parseInlineArgs(args) {
|
|
1812
|
+
let directory;
|
|
1813
|
+
let configPath;
|
|
1814
|
+
let help = false;
|
|
1815
|
+
const commandArgs = [];
|
|
1816
|
+
for (let i = 0;i < args.length; i++) {
|
|
1817
|
+
const arg = args[i];
|
|
1818
|
+
if (commandArgs.length > 0) {
|
|
1819
|
+
commandArgs.push(arg);
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
if (arg === "--") {
|
|
1823
|
+
commandArgs.push(...args.slice(i + 1));
|
|
1824
|
+
break;
|
|
1825
|
+
}
|
|
1826
|
+
if (arg === "--help" || arg === "-h") {
|
|
1827
|
+
help = true;
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
if (arg === "--directory") {
|
|
1831
|
+
directory = args[++i];
|
|
1832
|
+
if (!directory)
|
|
1833
|
+
error("Missing value for --directory.");
|
|
1834
|
+
continue;
|
|
1835
|
+
}
|
|
1836
|
+
if (arg.startsWith("--directory=")) {
|
|
1837
|
+
directory = arg.slice("--directory=".length);
|
|
1838
|
+
if (!directory)
|
|
1839
|
+
error("Missing value for --directory.");
|
|
1840
|
+
continue;
|
|
1841
|
+
}
|
|
1842
|
+
if (arg === "--config") {
|
|
1843
|
+
configPath = args[++i];
|
|
1844
|
+
if (!configPath)
|
|
1845
|
+
error("Missing value for --config.");
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
if (arg.startsWith("--config=")) {
|
|
1849
|
+
configPath = arg.slice("--config=".length);
|
|
1850
|
+
if (!configPath)
|
|
1851
|
+
error("Missing value for --config.");
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
commandArgs.push(...args.slice(i));
|
|
1855
|
+
break;
|
|
1856
|
+
}
|
|
1857
|
+
return { directory, configPath, commandArgs, help };
|
|
1858
|
+
}
|
|
1859
|
+
async function handleInline(options) {
|
|
1860
|
+
const cwd = options.directory || process.cwd();
|
|
1861
|
+
const configPath = options.configPath || ".config.toml";
|
|
1862
|
+
const { configEnv, exists } = await loadConfigEnv(cwd, configPath);
|
|
1863
|
+
if (exists) {
|
|
1864
|
+
console.log(`Loaded config from ${configPath}`);
|
|
1865
|
+
} else {
|
|
1866
|
+
console.log(`Config file '${configPath}' not found, continuing without it.`);
|
|
1867
|
+
}
|
|
1868
|
+
if (options.commandArgs.length === 0) {
|
|
1869
|
+
error("Please provide a command to run. Example: bgrun inline -- bun run dev");
|
|
1870
|
+
}
|
|
1871
|
+
const proc = Bun.spawn(options.commandArgs, {
|
|
1872
|
+
cwd,
|
|
1873
|
+
env: buildManagedProcessEnv(Bun.env, configEnv),
|
|
1874
|
+
stdin: "inherit",
|
|
1875
|
+
stdout: "inherit",
|
|
1876
|
+
stderr: "inherit"
|
|
1877
|
+
});
|
|
1878
|
+
const exitCode = await proc.exited;
|
|
1879
|
+
process.exit(exitCode);
|
|
1880
|
+
}
|
|
1239
1881
|
|
|
1240
1882
|
// src/api.ts
|
|
1241
|
-
init_db();
|
|
1242
|
-
init_platform();
|
|
1243
|
-
init_run();
|
|
1244
1883
|
init_utils();
|
|
1245
1884
|
init_db();
|
|
1246
1885
|
init_platform();
|
|
1247
|
-
init_run();
|
|
1248
1886
|
init_utils();
|
|
1249
1887
|
var api_default = {
|
|
1250
1888
|
db,
|
|
@@ -1285,11 +1923,31 @@ var api_default = {
|
|
|
1285
1923
|
getProcessBatchResources,
|
|
1286
1924
|
getProcessMemory,
|
|
1287
1925
|
reconcileProcessPids,
|
|
1926
|
+
resolvePidWithPorts,
|
|
1288
1927
|
handleRun,
|
|
1928
|
+
handleEnvit,
|
|
1929
|
+
parseEnvitArgs,
|
|
1930
|
+
renderEnvitOutput,
|
|
1931
|
+
handleInline,
|
|
1932
|
+
parseInlineArgs,
|
|
1933
|
+
ensureProcessWatcher,
|
|
1934
|
+
stopProcessWatcher,
|
|
1935
|
+
syncProcessWatcher,
|
|
1936
|
+
getGuardRestartCounts,
|
|
1937
|
+
getRecentGuardEvents,
|
|
1289
1938
|
getVersion,
|
|
1290
1939
|
calculateRuntime,
|
|
1291
1940
|
parseEnvString,
|
|
1292
|
-
|
|
1941
|
+
parseCommandEnv,
|
|
1942
|
+
getDeclaredPort,
|
|
1943
|
+
validateDirectory,
|
|
1944
|
+
acquireProcessOperationLock,
|
|
1945
|
+
isProcessOperationLocked,
|
|
1946
|
+
stringifyEnvString,
|
|
1947
|
+
getWatcherProcessName,
|
|
1948
|
+
getWatchedProcessName,
|
|
1949
|
+
isWatcherProcessName,
|
|
1950
|
+
isInternalProcessName
|
|
1293
1951
|
};
|
|
1294
1952
|
export {
|
|
1295
1953
|
waitForPortFree,
|
|
@@ -1297,36 +1955,55 @@ export {
|
|
|
1297
1955
|
updateProcessPid,
|
|
1298
1956
|
updateProcessEnv,
|
|
1299
1957
|
terminateProcess,
|
|
1958
|
+
syncProcessWatcher,
|
|
1959
|
+
stringifyEnvString,
|
|
1960
|
+
stopProcessWatcher,
|
|
1300
1961
|
saveTemplate,
|
|
1301
1962
|
retryDatabaseOperation,
|
|
1963
|
+
resolvePidWithPorts,
|
|
1964
|
+
renderEnvitOutput,
|
|
1302
1965
|
removeProcessByName,
|
|
1303
1966
|
removeProcess,
|
|
1304
1967
|
removeDependency,
|
|
1305
1968
|
removeAllProcesses,
|
|
1306
1969
|
reconcileProcessPids,
|
|
1307
1970
|
readFileTail,
|
|
1971
|
+
parseInlineArgs,
|
|
1972
|
+
parseEnvitArgs,
|
|
1308
1973
|
parseEnvString,
|
|
1974
|
+
parseCommandEnv,
|
|
1309
1975
|
killProcessOnPort,
|
|
1310
1976
|
isWindows,
|
|
1977
|
+
isWatcherProcessName,
|
|
1311
1978
|
isProcessRunning,
|
|
1979
|
+
isProcessOperationLocked,
|
|
1980
|
+
isInternalProcessName,
|
|
1312
1981
|
insertProcess,
|
|
1313
1982
|
handleRun,
|
|
1983
|
+
handleInline,
|
|
1984
|
+
handleEnvit,
|
|
1985
|
+
getWatcherProcessName,
|
|
1986
|
+
getWatchedProcessName,
|
|
1314
1987
|
getVersion,
|
|
1315
1988
|
getStartOrder,
|
|
1316
1989
|
getShellCommand,
|
|
1317
1990
|
getRecentHistory,
|
|
1991
|
+
getRecentGuardEvents,
|
|
1318
1992
|
getProcessPorts,
|
|
1319
1993
|
getProcessMemory,
|
|
1320
1994
|
getProcessHistory,
|
|
1321
1995
|
getProcessBatchResources,
|
|
1322
1996
|
getProcess,
|
|
1323
1997
|
getHomeDir,
|
|
1998
|
+
getGuardRestartCounts,
|
|
1324
1999
|
getDependencyGraph,
|
|
2000
|
+
getDeclaredPort,
|
|
1325
2001
|
getDbInfo,
|
|
1326
2002
|
getAllTemplates,
|
|
1327
2003
|
getAllProcesses,
|
|
1328
2004
|
findPidByPort,
|
|
1329
2005
|
findChildPid,
|
|
2006
|
+
ensureProcessWatcher,
|
|
1330
2007
|
ensureDir,
|
|
1331
2008
|
deleteTemplate,
|
|
1332
2009
|
api_default as default,
|
|
@@ -1335,5 +2012,6 @@ export {
|
|
|
1335
2012
|
calculateRuntime,
|
|
1336
2013
|
bgrHome,
|
|
1337
2014
|
addHistoryEntry,
|
|
1338
|
-
addDependency
|
|
2015
|
+
addDependency,
|
|
2016
|
+
acquireProcessOperationLock
|
|
1339
2017
|
};
|