bgrun 3.12.11 → 3.12.13

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.
Files changed (50) hide show
  1. package/README.md +2 -2
  2. package/dashboard/app/api/config/[name]/route.ts +1 -1
  3. package/dashboard/app/api/debug/route.ts +1 -1
  4. package/dashboard/app/api/dependencies/route.ts +40 -40
  5. package/dashboard/app/api/deploy/[name]/route.ts +1 -1
  6. package/dashboard/app/api/deploy-all/route.ts +25 -25
  7. package/dashboard/app/api/deps/route.ts +3 -3
  8. package/dashboard/app/api/guard/route.ts +1 -1
  9. package/dashboard/app/api/guard-all/route.ts +1 -1
  10. package/dashboard/app/api/guard-events/route.ts +4 -4
  11. package/dashboard/app/api/history/route.ts +105 -105
  12. package/dashboard/app/api/logs/[name]/route.ts +100 -100
  13. package/dashboard/app/api/logs/rotate/route.ts +2 -2
  14. package/dashboard/app/api/next-port/route.ts +32 -32
  15. package/dashboard/app/api/processes/[name]/route.ts +2 -2
  16. package/dashboard/app/api/processes/route.ts +4 -4
  17. package/dashboard/app/api/restart/[name]/route.ts +2 -2
  18. package/dashboard/app/api/start/route.ts +2 -2
  19. package/dashboard/app/api/stop/[name]/route.ts +2 -2
  20. package/dashboard/app/api/templates/route.ts +46 -46
  21. package/dashboard/app/api/version/route.ts +1 -1
  22. package/dashboard/lib/runtime.ts +49 -0
  23. package/dist/api.js +94 -67
  24. package/dist/deploy.js +1373 -0
  25. package/dist/deps.js +1004 -0
  26. package/dist/index.js +224 -224
  27. package/dist/log-rotation.js +95 -0
  28. package/dist/server.js +1488 -0
  29. package/package.json +2 -17
  30. package/src/api.ts +0 -63
  31. package/src/build.ts +0 -24
  32. package/src/commands/cleanup.ts +0 -141
  33. package/src/commands/details.ts +0 -60
  34. package/src/commands/list.ts +0 -133
  35. package/src/commands/logs.ts +0 -49
  36. package/src/commands/run.ts +0 -217
  37. package/src/commands/watch.ts +0 -223
  38. package/src/config.ts +0 -37
  39. package/src/db.ts +0 -422
  40. package/src/deploy.ts +0 -163
  41. package/src/deps.ts +0 -126
  42. package/src/guard.ts +0 -208
  43. package/src/index.ts +0 -623
  44. package/src/log-rotation.ts +0 -93
  45. package/src/logger.ts +0 -40
  46. package/src/platform.ts +0 -665
  47. package/src/server.ts +0 -217
  48. package/src/table.ts +0 -232
  49. package/src/types.ts +0 -14
  50. package/src/utils.ts +0 -96
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bgrun",
3
- "version": "3.12.11",
3
+ "version": "3.12.13",
4
4
  "description": "bgrun — A lightweight process manager for Bun",
5
5
  "type": "module",
6
6
  "main": "./dist/api.js",
@@ -17,23 +17,8 @@
17
17
  },
18
18
  "files": [
19
19
  "dist",
20
- "src/api.ts",
21
- "src/build.ts",
22
- "src/config.ts",
23
- "src/db.ts",
24
- "src/deploy.ts",
25
- "src/deps.ts",
26
- "src/guard.ts",
27
- "src/index.ts",
28
- "src/log-rotation.ts",
29
- "src/logger.ts",
30
- "src/platform.ts",
31
- "src/server.ts",
32
- "src/table.ts",
33
- "src/types.ts",
34
- "src/utils.ts",
35
- "src/commands",
36
20
  "dashboard/app",
21
+ "dashboard/lib",
37
22
  "scripts",
38
23
  "README.md",
39
24
  "image.png",
package/src/api.ts DELETED
@@ -1,63 +0,0 @@
1
- /**
2
- * BGR Public API (package: bgrun)
3
- *
4
- * Import from 'bgrun' to use these functions in your own process-managing apps.
5
- *
6
- * @example
7
- * ```ts
8
- * import { getAllProcesses, isProcessRunning, handleRun } from 'bgrun'
9
- *
10
- * // List all managed processes
11
- * const processes = getAllProcesses()
12
- *
13
- * // Check if a process is running
14
- * const alive = await isProcessRunning(process.pid)
15
- *
16
- * // Start a new managed process
17
- * await handleRun({ name: 'my-app', command: 'bun run dev', directory: './my-app', action: 'run', remoteName: '' })
18
- * ```
19
- */
20
-
21
- // --- Types ---
22
- export type { Process } from './db'
23
- export type { CommandOptions } from './types'
24
-
25
- // --- Database Operations ---
26
- export { db, getAllProcesses, getProcess, insertProcess, removeProcess, removeProcessByName, removeAllProcesses, retryDatabaseOperation, getDbInfo, dbPath, bgrHome } from './db'
27
-
28
- // --- Process Operations ---
29
- export {
30
- isProcessRunning,
31
- terminateProcess,
32
- readFileTail,
33
- getProcessPorts,
34
- findChildPid,
35
- findPidByPort,
36
- getShellCommand,
37
- killProcessOnPort,
38
- waitForPortFree,
39
- ensureDir,
40
- getHomeDir,
41
- isWindows,
42
- getProcessBatchResources,
43
- getProcessMemory
44
- } from './platform'
45
-
46
- // --- High-Level Commands ---
47
- export { handleRun } from './commands/run'
48
-
49
- // --- Utilities ---
50
- export { getVersion, calculateRuntime, parseEnvString, validateDirectory } from './utils'
51
-
52
- // --- Default Export (namespace style) ---
53
- import { getAllProcesses, getProcess, insertProcess, removeProcess, removeProcessByName, removeAllProcesses, retryDatabaseOperation, getDbInfo, dbPath, bgrHome } from './db'
54
- import { isProcessRunning, terminateProcess, readFileTail, getProcessPorts, findChildPid, findPidByPort, getShellCommand, killProcessOnPort, waitForPortFree, ensureDir, getHomeDir, isWindows, getProcessBatchResources, getProcessMemory } from './platform'
55
- import { handleRun } from './commands/run'
56
- import { getVersion, calculateRuntime, parseEnvString, validateDirectory } from './utils'
57
-
58
- export default {
59
- getAllProcesses, getProcess, insertProcess, removeProcess, removeProcessByName, removeAllProcesses, retryDatabaseOperation, getDbInfo, dbPath, bgrHome,
60
- isProcessRunning, terminateProcess, readFileTail, getProcessPorts, findChildPid, findPidByPort, getShellCommand, killProcessOnPort, waitForPortFree, ensureDir, getHomeDir, isWindows, getProcessBatchResources, getProcessMemory,
61
- handleRun,
62
- getVersion, calculateRuntime, parseEnvString, validateDirectory,
63
- }
package/src/build.ts DELETED
@@ -1,24 +0,0 @@
1
- console.log("Starting build process for bgrun...");
2
-
3
- const entrypoints = ['./src/index.ts', './src/api.ts'];
4
- const result = await Bun.build({
5
- entrypoints,
6
- outdir: './dist',
7
- target: 'bun',
8
- format: 'esm',
9
- minify: false,
10
- // Mark all packages as external to rely on node_modules
11
- // This avoids bundling native modules or mismatched React versions
12
- packages: "external",
13
- });
14
-
15
- if (!result.success) {
16
- console.error("Build failed");
17
- for (const message of result.logs) {
18
- console.error(message);
19
- }
20
- process.exit(1);
21
- }
22
-
23
- const builtFiles = result.outputs.map((output) => output.path.split(/[\\/]/).pop()).filter(Boolean);
24
- console.log(`Build successful! Artifacts: ${builtFiles.join(', ')}`);
@@ -1,141 +0,0 @@
1
-
2
- import { getProcess, removeProcessByName, removeProcess, getAllProcesses, removeAllProcesses, updateProcessPid } from "../db";
3
- import { isProcessRunning, terminateProcess, getProcessPorts, killProcessOnPort, waitForPortFree } from "../platform";
4
- import { announce, error } from "../logger";
5
- import * as fs from "fs";
6
-
7
- export async function handleDelete(name: string) {
8
- const process = getProcess(name);
9
-
10
- if (!process) {
11
- error(`No process found named '${name}'`);
12
- return;
13
- }
14
-
15
- const isRunning = await isProcessRunning(process.pid);
16
- if (isRunning) {
17
- await terminateProcess(process.pid);
18
- }
19
-
20
- if (fs.existsSync(process.stdout_path)) {
21
- try { fs.unlinkSync(process.stdout_path); } catch { }
22
- }
23
- if (fs.existsSync(process.stderr_path)) {
24
- try { fs.unlinkSync(process.stderr_path); } catch { }
25
- }
26
-
27
- removeProcessByName(name);
28
- announce(`Process '${name}' has been ${isRunning ? 'stopped and ' : ''}deleted`, "Process Deleted");
29
- }
30
-
31
- export async function handleClean() {
32
- const processes = getAllProcesses();
33
- let cleanedCount = 0;
34
- let deletedLogs = 0;
35
-
36
- for (const proc of processes) {
37
- const running = await isProcessRunning(proc.pid);
38
- if (!running) {
39
- removeProcess(proc.pid);
40
- cleanedCount++;
41
-
42
- if (fs.existsSync(proc.stdout_path)) {
43
- try { fs.unlinkSync(proc.stdout_path); deletedLogs++; } catch { }
44
- }
45
- if (fs.existsSync(proc.stderr_path)) {
46
- try { fs.unlinkSync(proc.stderr_path); deletedLogs++; } catch { }
47
- }
48
- }
49
- }
50
-
51
- if (cleanedCount === 0) {
52
- announce("No stopped processes found to clean.", "Clean Complete");
53
- } else {
54
- announce(
55
- `Cleaned ${cleanedCount} stopped ${cleanedCount === 1 ? 'process' : 'processes'} and removed ${deletedLogs} log ${deletedLogs === 1 ? 'file' : 'files'}.`,
56
- "Clean Complete"
57
- );
58
- }
59
- }
60
-
61
- export async function handleStop(name: string) {
62
- const proc = getProcess(name);
63
-
64
- if (!proc) {
65
- error(`No process found named '${name}'`);
66
- return;
67
- }
68
-
69
- const isRunning = await isProcessRunning(proc.pid);
70
- if (!isRunning) {
71
- announce(`Process '${name}' is already stopped.`, "Process Stop");
72
- return;
73
- }
74
-
75
- // Detect ports the process is using BEFORE killing it
76
- const ports = await getProcessPorts(proc.pid);
77
-
78
- await terminateProcess(proc.pid);
79
-
80
- // Also kill by detected ports as safety net
81
- for (const port of ports) {
82
- await killProcessOnPort(port);
83
- }
84
-
85
- // Mark PID as 0 — prevents reconcileProcessPids from re-attaching
86
- // a random matching process as this one
87
- updateProcessPid(name, 0);
88
-
89
- announce(`Process '${name}' has been stopped (kept in registry).`, "Process Stopped");
90
- }
91
-
92
- export async function handleDeleteAll() {
93
- const processes = getAllProcesses();
94
- if (processes.length === 0) {
95
- announce("There are no processes to delete.", "Delete All");
96
- return;
97
- }
98
-
99
- let killedCount = 0;
100
- let portsFreed = 0;
101
-
102
- for (const proc of processes) {
103
- const running = await isProcessRunning(proc.pid);
104
-
105
- if (running) {
106
- // Detect ports BEFORE killing so we can clean them up
107
- const ports = await getProcessPorts(proc.pid);
108
-
109
- // Force-kill the process tree
110
- await terminateProcess(proc.pid, true);
111
- killedCount++;
112
-
113
- // Kill anything still holding the ports
114
- for (const port of ports) {
115
- await killProcessOnPort(port);
116
- const freed = await waitForPortFree(port, 3000);
117
- if (!freed) {
118
- await killProcessOnPort(port);
119
- await waitForPortFree(port, 2000);
120
- }
121
- portsFreed++;
122
- }
123
- }
124
-
125
- // Clean up log files
126
- if (fs.existsSync(proc.stdout_path)) {
127
- try { fs.unlinkSync(proc.stdout_path); } catch { }
128
- }
129
- if (fs.existsSync(proc.stderr_path)) {
130
- try { fs.unlinkSync(proc.stderr_path); } catch { }
131
- }
132
- }
133
-
134
- removeAllProcesses();
135
-
136
- const parts = [`${processes.length} ${processes.length === 1 ? 'process' : 'processes'} deleted`];
137
- if (killedCount > 0) parts.push(`${killedCount} force-killed`);
138
- if (portsFreed > 0) parts.push(`${portsFreed} ${portsFreed === 1 ? 'port' : 'ports'} freed`);
139
-
140
- announce(parts.join(', ') + '.', "Nuke Complete");
141
- }
@@ -1,60 +0,0 @@
1
-
2
- import { error, announce } from "../logger";
3
- import { getProcess, updateProcessPid } from "../db";
4
- import { isProcessRunning, calculateRuntime, parseEnvString } from "../utils";
5
- import { getProcessPorts, reconcileProcessPids } from "../platform";
6
- import chalk from "chalk";
7
-
8
- export async function showDetails(name: string) {
9
- const proc = getProcess(name);
10
- if (!proc) {
11
- error(`No process found named '${name}'`);
12
- return;
13
- }
14
-
15
- let isRunning = await isProcessRunning(proc.pid, proc.command);
16
-
17
- // Reconcile stale PID: cmd.exe wrapper may have exited while bun.exe child lives
18
- if (!isRunning && proc.pid > 0) {
19
- const reconciled = await reconcileProcessPids(
20
- [{ name: proc.name, pid: proc.pid, command: proc.command, workdir: proc.workdir }],
21
- new Set([proc.pid]),
22
- );
23
- const newPid = reconciled.get(proc.name);
24
- if (newPid) {
25
- updateProcessPid(proc.name, newPid);
26
- (proc as any).pid = newPid;
27
- isRunning = true;
28
- }
29
- }
30
- const runtime = calculateRuntime(proc.timestamp);
31
- const envVars = parseEnvString(proc.env);
32
-
33
- // Detect actual ports via OS
34
- const ports = isRunning ? await getProcessPorts(proc.pid) : [];
35
-
36
- const portDisplay = ports.length > 0
37
- ? ports.map(p => chalk.hex('#FF6B6B')(`:${p}`)).join(', ')
38
- : null;
39
-
40
- const details = `
41
- ${chalk.bold('Process Details:')}
42
- ${chalk.gray('═'.repeat(50))}
43
- ${chalk.cyan.bold('Name:')} ${proc.name}
44
- ${chalk.yellow.bold('PID:')} ${proc.pid}${portDisplay ? `\n${chalk.hex('#FF6B6B').bold('Port:')} ${portDisplay}` : ''}
45
- ${chalk.bold('Status:')} ${isRunning ? chalk.green.bold("● Running") : chalk.red.bold("○ Stopped")}
46
- ${chalk.magenta.bold('Runtime:')} ${runtime}
47
- ${chalk.blue.bold('Working Directory:')} ${proc.workdir}
48
- ${chalk.white.bold('Command:')} ${proc.command}
49
- ${chalk.gray.bold('Config Path:')} ${proc.configPath}
50
- ${chalk.green.bold('Stdout Path:')} ${proc.stdout_path}
51
- ${chalk.red.bold('Stderr Path:')} ${proc.stderr_path}
52
-
53
- ${chalk.bold('🔧 Environment Variables:')}
54
- ${chalk.gray('═'.repeat(50))}
55
- ${Object.entries(envVars)
56
- .map(([key, value]) => `${chalk.cyan.bold(key)} = ${chalk.yellow(value)}`)
57
- .join('\n')}
58
- `;
59
- announce(details, `Process Details: ${name}`);
60
- }
@@ -1,133 +0,0 @@
1
- import chalk from "chalk";
2
- import { renderProcessTable } from "../table";
3
- import type { ProcessTableRow } from "../table";
4
- import { getAllProcesses, updateProcessPid } from "../db";
5
- import { announce } from "../logger";
6
- import { isProcessRunning, calculateRuntime, parseEnvString } from "../utils";
7
- import { getProcessPorts, getProcessBatchResources, reconcileProcessPids } from "../platform";
8
- import { measure } from "measure-fn";
9
-
10
- function formatMemory(bytes: number): string {
11
- if (bytes === 0) return '-';
12
- const mb = bytes / (1024 * 1024);
13
- if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`;
14
- return `${Math.round(mb)} MB`;
15
- }
16
-
17
- export async function showAll(opts?: { json?: boolean; filter?: string }) {
18
- const processes = getAllProcesses();
19
-
20
- // Apply filter by env.BGR_GROUP if provided
21
- const filtered = processes.filter((proc) => {
22
- if (!opts?.filter) return true;
23
- const envVars = parseEnvString(proc.env);
24
- return envVars["BGR_GROUP"] === opts.filter;
25
- });
26
-
27
- // ─── PID Reconciliation ──────────────────────────────────────────
28
- // On Windows, the stored PID may be a dead cmd.exe wrapper while the
29
- // actual bun.exe child is still running. Detect dead PIDs up-front,
30
- // reconcile them in one batch PowerShell call, and patch the DB so
31
- // subsequent invocations are stable (no flicker).
32
- const deadPids = new Set<number>();
33
- const aliveCache = new Map<number, boolean>();
34
-
35
- for (const proc of filtered) {
36
- const alive = await isProcessRunning(proc.pid, proc.command);
37
- aliveCache.set(proc.pid, alive);
38
- if (!alive && proc.pid > 0) deadPids.add(proc.pid);
39
- }
40
-
41
- if (deadPids.size > 0) {
42
- const reconciled = await reconcileProcessPids(
43
- filtered.map(p => ({ name: p.name, pid: p.pid, command: p.command, workdir: p.workdir })),
44
- deadPids,
45
- );
46
-
47
- for (const [name, newPid] of reconciled) {
48
- updateProcessPid(name, newPid);
49
- // Patch the in-memory record so the rest of this function sees
50
- // the corrected PID without re-querying the DB.
51
- const proc = filtered.find(p => p.name === name);
52
- if (proc) {
53
- (proc as any).pid = newPid;
54
- aliveCache.set(newPid, true);
55
- }
56
- }
57
- }
58
- // ─────────────────────────────────────────────────────────────────
59
-
60
- if (opts?.json) {
61
- // JSON output with filtered env variables
62
- const jsonData: any[] = [];
63
-
64
- for (const proc of filtered) {
65
- const isRunning = aliveCache.get(proc.pid) ?? await isProcessRunning(proc.pid, proc.command);
66
- const envVars = parseEnvString(proc.env);
67
-
68
- const ports = isRunning ? await getProcessPorts(proc.pid) : [];
69
- jsonData.push({
70
- pid: proc.pid,
71
- name: proc.name,
72
- ports: ports.length > 0 ? ports : undefined,
73
- status: isRunning ? "running" : "stopped",
74
- env: envVars,
75
- });
76
- }
77
-
78
- console.log(JSON.stringify(jsonData, null, 2));
79
- return;
80
- }
81
-
82
- // Table output
83
- const tableData: ProcessTableRow[] = [];
84
-
85
- // Batch fetch memory for all PIDs
86
- const allPids = filtered.map(p => p.pid);
87
- const resourceMap = await getProcessBatchResources(allPids);
88
-
89
- for (const proc of filtered) {
90
- const isRunning = aliveCache.get(proc.pid) ?? await isProcessRunning(proc.pid, proc.command);
91
- const runtime = calculateRuntime(proc.timestamp);
92
- const mem = isRunning ? (resourceMap.get(proc.pid)?.memory || 0) : 0;
93
-
94
- const ports = isRunning ? await getProcessPorts(proc.pid) : [];
95
- tableData.push({
96
- id: proc.id,
97
- pid: proc.pid,
98
- name: proc.name,
99
- port: ports.length > 0 ? ports.map(p => `:${p}`).join(',') : '-',
100
- memory: formatMemory(mem),
101
- command: proc.command,
102
- workdir: proc.workdir,
103
- status: isRunning
104
- ? chalk.green.bold("● Running")
105
- : chalk.red.bold("○ Stopped"),
106
- runtime: runtime,
107
- });
108
- }
109
-
110
- if (tableData.length === 0) {
111
- if (opts?.filter) {
112
- announce(`No processes matched filter BGR_GROUP='${opts.filter}'.`, "No Matches");
113
- } else {
114
- announce("No processes found.", "Empty");
115
- }
116
- return;
117
- }
118
-
119
- const tableOutput = renderProcessTable(tableData, {
120
- padding: 1,
121
- borderStyle: "rounded",
122
- showHeaders: true,
123
- });
124
- console.log(tableOutput);
125
-
126
- const runningCount = tableData.filter((p) => p.status.includes("Running")).length;
127
- const stoppedCount = tableData.filter((p) => p.status.includes("Stopped")).length;
128
- console.log(
129
- chalk.cyan(
130
- `Total: ${tableData.length} processes (${chalk.green(`${runningCount} running`)}, ${chalk.red(`${stoppedCount} stopped`)})`
131
- )
132
- );
133
- }
@@ -1,49 +0,0 @@
1
- import { getProcess } from "../db";
2
- import { error } from "../logger";
3
- import { readFileTail } from "../platform";
4
- import chalk from "chalk";
5
- import * as fs from "fs";
6
-
7
- export async function showLogs(name: string, logType: 'stdout' | 'stderr' | 'both' = 'both', lines?: number) {
8
- const proc = getProcess(name);
9
- if (!proc) {
10
- error(`No process found named '${name}'`);
11
- return; // TS shut up
12
- }
13
-
14
- if (logType === 'both' || logType === 'stdout') {
15
- console.log(chalk.green.bold(`📄 Stdout logs for ${name}:`));
16
- console.log(chalk.gray('═'.repeat(50)));
17
-
18
- if (fs.existsSync(proc.stdout_path)) {
19
- try {
20
- const output = await readFileTail(proc.stdout_path, lines);
21
- console.log(output || chalk.gray('(no output)'));
22
- } catch (err) {
23
- console.log(chalk.red(`Error reading stdout: ${err}`));
24
- }
25
- } else {
26
- console.log(chalk.gray('(log file not found)'));
27
- }
28
-
29
- if (logType === 'both') {
30
- console.log('\n');
31
- }
32
- }
33
-
34
- if (logType === 'both' || logType === 'stderr') {
35
- console.log(chalk.red.bold(`📄 Stderr logs for ${name}:`));
36
- console.log(chalk.gray('═'.repeat(50)));
37
-
38
- if (fs.existsSync(proc.stderr_path)) {
39
- try {
40
- const output = await readFileTail(proc.stderr_path, lines);
41
- console.log(output || chalk.gray('(no errors)'));
42
- } catch (err) {
43
- console.log(chalk.red(`Error reading stderr: ${err}`));
44
- }
45
- } else {
46
- console.log(chalk.gray('(log file not found)'));
47
- }
48
- }
49
- }