bgrun 3.12.12 → 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.
package/README.md CHANGED
@@ -409,8 +409,8 @@ If no `--config` is specified, bgrun looks for `.config.toml` in the working dir
409
409
 
410
410
  bgrun exposes its internals as importable TypeScript functions:
411
411
 
412
- > **Packaging note:** the CLI ships from `dist/index.js`, and the Bun programmatic API now resolves through the built `dist/api.js` artifact.
413
- > The runtime `src/` subset is still temporarily published for compatibility during the migration window, but package consumers should treat `dist/api.js` as the canonical API entry.
412
+ > **Packaging note:** the CLI ships from `dist/index.js`, the Bun programmatic API resolves through `dist/api.js`, and the dashboard backend uses built `dist/*` runtime artifacts via `dashboard/lib/runtime.ts`.
413
+ > Published packages are now `dist`-first; repository `src/` files remain the development/build source of truth and are not part of the runtime package surface.
414
414
 
415
415
  ```bash
416
416
  bun add bgrun
@@ -0,0 +1,49 @@
1
+ export {
2
+ db,
3
+ getAllProcesses,
4
+ getProcess,
5
+ insertProcess,
6
+ removeProcess,
7
+ removeProcessByName,
8
+ removeAllProcesses,
9
+ updateProcessPid,
10
+ updateProcessEnv,
11
+ getAllTemplates,
12
+ saveTemplate,
13
+ deleteTemplate,
14
+ getProcessHistory,
15
+ getRecentHistory,
16
+ addHistoryEntry,
17
+ getDependencyGraph,
18
+ addDependency,
19
+ removeDependency,
20
+ getStartOrder,
21
+ getDbInfo,
22
+ dbPath,
23
+ bgrHome,
24
+ isProcessRunning,
25
+ terminateProcess,
26
+ readFileTail,
27
+ getProcessPorts,
28
+ findChildPid,
29
+ findPidByPort,
30
+ getShellCommand,
31
+ killProcessOnPort,
32
+ waitForPortFree,
33
+ ensureDir,
34
+ getHomeDir,
35
+ isWindows,
36
+ getProcessBatchResources,
37
+ getProcessMemory,
38
+ reconcileProcessPids,
39
+ handleRun,
40
+ getVersion,
41
+ calculateRuntime,
42
+ parseEnvString,
43
+ validateDirectory,
44
+ } from '../../dist/api.js'
45
+
46
+ export { deployProcess, deployAllProcesses } from '../../dist/deploy.js'
47
+ export { buildDepGraph } from '../../dist/deps.js'
48
+ export { rotateAllLogs } from '../../dist/log-rotation.js'
49
+ export { guardEvents, guardRestartCounts } from '../../dist/server.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bgrun",
3
- "version": "3.12.12",
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,116 +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 {
27
- db,
28
- getAllProcesses,
29
- getProcess,
30
- insertProcess,
31
- removeProcess,
32
- removeProcessByName,
33
- removeAllProcesses,
34
- updateProcessPid,
35
- updateProcessEnv,
36
- getAllTemplates,
37
- saveTemplate,
38
- deleteTemplate,
39
- getProcessHistory,
40
- getRecentHistory,
41
- addHistoryEntry,
42
- getDependencyGraph,
43
- addDependency,
44
- removeDependency,
45
- getStartOrder,
46
- retryDatabaseOperation,
47
- getDbInfo,
48
- dbPath,
49
- bgrHome,
50
- } from './db'
51
-
52
- // --- Process Operations ---
53
- export {
54
- isProcessRunning,
55
- terminateProcess,
56
- readFileTail,
57
- getProcessPorts,
58
- findChildPid,
59
- findPidByPort,
60
- getShellCommand,
61
- killProcessOnPort,
62
- waitForPortFree,
63
- ensureDir,
64
- getHomeDir,
65
- isWindows,
66
- getProcessBatchResources,
67
- getProcessMemory,
68
- reconcileProcessPids,
69
- } from './platform'
70
-
71
- // --- High-Level Commands ---
72
- export { handleRun } from './commands/run'
73
-
74
- // --- Utilities ---
75
- export { getVersion, calculateRuntime, parseEnvString, validateDirectory } from './utils'
76
-
77
- // --- Default Export (namespace style) ---
78
- import {
79
- db,
80
- getAllProcesses,
81
- getProcess,
82
- insertProcess,
83
- removeProcess,
84
- removeProcessByName,
85
- removeAllProcesses,
86
- updateProcessPid,
87
- updateProcessEnv,
88
- getAllTemplates,
89
- saveTemplate,
90
- deleteTemplate,
91
- getProcessHistory,
92
- getRecentHistory,
93
- addHistoryEntry,
94
- getDependencyGraph,
95
- addDependency,
96
- removeDependency,
97
- getStartOrder,
98
- retryDatabaseOperation,
99
- getDbInfo,
100
- dbPath,
101
- bgrHome,
102
- } from './db'
103
- import { isProcessRunning, terminateProcess, readFileTail, getProcessPorts, findChildPid, findPidByPort, getShellCommand, killProcessOnPort, waitForPortFree, ensureDir, getHomeDir, isWindows, getProcessBatchResources, getProcessMemory, reconcileProcessPids } from './platform'
104
- import { handleRun } from './commands/run'
105
- import { getVersion, calculateRuntime, parseEnvString, validateDirectory } from './utils'
106
-
107
- export default {
108
- db, getAllProcesses, getProcess, insertProcess, removeProcess, removeProcessByName, removeAllProcesses,
109
- updateProcessPid, updateProcessEnv, getAllTemplates, saveTemplate, deleteTemplate,
110
- getProcessHistory, getRecentHistory, addHistoryEntry,
111
- getDependencyGraph, addDependency, removeDependency, getStartOrder,
112
- retryDatabaseOperation, getDbInfo, dbPath, bgrHome,
113
- isProcessRunning, terminateProcess, readFileTail, getProcessPorts, findChildPid, findPidByPort, getShellCommand, killProcessOnPort, waitForPortFree, ensureDir, getHomeDir, isWindows, getProcessBatchResources, getProcessMemory, reconcileProcessPids,
114
- handleRun,
115
- getVersion, calculateRuntime, parseEnvString, validateDirectory,
116
- }
package/src/build.ts DELETED
@@ -1,31 +0,0 @@
1
- console.log("Starting build process for bgrun...");
2
-
3
- const entrypoints = [
4
- './src/index.ts',
5
- './src/api.ts',
6
- './src/server.ts',
7
- './src/deploy.ts',
8
- './src/deps.ts',
9
- './src/log-rotation.ts',
10
- ];
11
- const result = await Bun.build({
12
- entrypoints,
13
- outdir: './dist',
14
- target: 'bun',
15
- format: 'esm',
16
- minify: false,
17
- // Mark all packages as external to rely on node_modules
18
- // This avoids bundling native modules or mismatched React versions
19
- packages: "external",
20
- });
21
-
22
- if (!result.success) {
23
- console.error("Build failed");
24
- for (const message of result.logs) {
25
- console.error(message);
26
- }
27
- process.exit(1);
28
- }
29
-
30
- const builtFiles = result.outputs.map((output) => output.path.split(/[\\/]/).pop()).filter(Boolean);
31
- 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
- }