bgrun 3.12.11 → 3.12.12

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.
@@ -2,7 +2,7 @@
2
2
  * GET /api/config/:name — Read .config.toml content
3
3
  * PUT /api/config/:name — Write .config.toml content
4
4
  */
5
- import { getProcess } from '../../../../../src/db';
5
+ import { getProcess } from '../../../../lib/runtime';
6
6
  import { join } from 'path';
7
7
  import { readFile, writeFile } from 'fs/promises';
8
8
 
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Returns DB path, BGR home dir, platform info for diagnostics.
5
5
  */
6
- import { getDbInfo } from '../../../../src/db';
6
+ import { getDbInfo } from '../../../lib/runtime';
7
7
  import { measureSync } from 'measure-fn';
8
8
 
9
9
  export async function GET() {
@@ -1,40 +1,40 @@
1
- import { getDependencyGraph, addDependency, removeDependency, getStartOrder, getAllProcesses } from "../../../../src/db";
2
-
3
- /** GET /api/dependencies — full dependency graph + start order */
4
- export function GET() {
5
- const graph = getDependencyGraph();
6
- const startOrder = getStartOrder();
7
- const processes = getAllProcesses().map(p => ({
8
- name: p.name,
9
- group: p.group || '',
10
- pid: p.pid,
11
- }));
12
-
13
- return Response.json({ graph, startOrder, processes });
14
- }
15
-
16
- /** POST /api/dependencies — add a dependency */
17
- export async function POST(req: Request) {
18
- const body = await req.json() as { process: string; depends_on: string };
19
- if (!body.process || !body.depends_on) {
20
- return Response.json({ error: 'Missing process or depends_on' }, { status: 400 });
21
- }
22
-
23
- const ok = addDependency(body.process, body.depends_on);
24
- if (!ok) {
25
- return Response.json({ error: 'Invalid dependency (duplicate, self-reference, or would create cycle)' }, { status: 400 });
26
- }
27
-
28
- return Response.json({ ok: true });
29
- }
30
-
31
- /** DELETE /api/dependencies — remove a dependency */
32
- export async function DELETE(req: Request) {
33
- const body = await req.json() as { process: string; depends_on: string };
34
- if (!body.process || !body.depends_on) {
35
- return Response.json({ error: 'Missing process or depends_on' }, { status: 400 });
36
- }
37
-
38
- removeDependency(body.process, body.depends_on);
39
- return Response.json({ ok: true });
40
- }
1
+ import { getDependencyGraph, addDependency, removeDependency, getStartOrder, getAllProcesses } from '../../../lib/runtime';
2
+
3
+ /** GET /api/dependencies — full dependency graph + start order */
4
+ export function GET() {
5
+ const graph = getDependencyGraph();
6
+ const startOrder = getStartOrder();
7
+ const processes = getAllProcesses().map(p => ({
8
+ name: p.name,
9
+ group: p.group || '',
10
+ pid: p.pid,
11
+ }));
12
+
13
+ return Response.json({ graph, startOrder, processes });
14
+ }
15
+
16
+ /** POST /api/dependencies — add a dependency */
17
+ export async function POST(req: Request) {
18
+ const body = await req.json() as { process: string; depends_on: string };
19
+ if (!body.process || !body.depends_on) {
20
+ return Response.json({ error: 'Missing process or depends_on' }, { status: 400 });
21
+ }
22
+
23
+ const ok = addDependency(body.process, body.depends_on);
24
+ if (!ok) {
25
+ return Response.json({ error: 'Invalid dependency (duplicate, self-reference, or would create cycle)' }, { status: 400 });
26
+ }
27
+
28
+ return Response.json({ ok: true });
29
+ }
30
+
31
+ /** DELETE /api/dependencies — remove a dependency */
32
+ export async function DELETE(req: Request) {
33
+ const body = await req.json() as { process: string; depends_on: string };
34
+ if (!body.process || !body.depends_on) {
35
+ return Response.json({ error: 'Missing process or depends_on' }, { status: 400 });
36
+ }
37
+
38
+ removeDependency(body.process, body.depends_on);
39
+ return Response.json({ ok: true });
40
+ }
@@ -4,7 +4,7 @@
4
4
  * Only works if the process directory is a git repository.
5
5
  * Steps: git pull → bun install → force restart
6
6
  */
7
- import { deployProcess } from '../../../../../src/deploy';
7
+ import { deployProcess } from '../../../../lib/runtime';
8
8
 
9
9
  export async function POST(req: Request, { params }: { params: { name: string } }) {
10
10
  const name = decodeURIComponent(params.name);
@@ -1,25 +1,25 @@
1
- import { deployAllProcesses } from '../../../../src/deploy';
2
-
3
- export async function POST(req: Request) {
4
- try {
5
- const body = await req.json().catch(() => ({}));
6
- const group = body?.group ? String(body.group) : undefined;
7
- const results = await deployAllProcesses(group);
8
-
9
- const deployed = results.filter(r => r.ok);
10
- const skipped = results.filter(r => r.skipped);
11
- const failed = results.filter(r => !r.ok && !r.skipped);
12
-
13
- return Response.json({
14
- success: failed.length === 0,
15
- group: group || null,
16
- total: results.length,
17
- deployed: deployed.length,
18
- skipped: skipped.length,
19
- failed: failed.length,
20
- results,
21
- }, { status: failed.length > 0 ? 207 : 200 });
22
- } catch (e: any) {
23
- return Response.json({ error: e?.message || String(e) }, { status: 500 });
24
- }
25
- }
1
+ import { deployAllProcesses } from '../../../lib/runtime';
2
+
3
+ export async function POST(req: Request) {
4
+ try {
5
+ const body = await req.json().catch(() => ({}));
6
+ const group = body?.group ? String(body.group) : undefined;
7
+ const results = await deployAllProcesses(group);
8
+
9
+ const deployed = results.filter(r => r.ok);
10
+ const skipped = results.filter(r => r.skipped);
11
+ const failed = results.filter(r => !r.ok && !r.skipped);
12
+
13
+ return Response.json({
14
+ success: failed.length === 0,
15
+ group: group || null,
16
+ total: results.length,
17
+ deployed: deployed.length,
18
+ skipped: skipped.length,
19
+ failed: failed.length,
20
+ results,
21
+ }, { status: failed.length > 0 ? 207 : 200 });
22
+ } catch (e: any) {
23
+ return Response.json({ error: e?.message || String(e) }, { status: 500 });
24
+ }
25
+ }
@@ -4,9 +4,9 @@
4
4
  * Body: { name: string, dependsOn: string[] }
5
5
  */
6
6
 
7
- import { getProcess, updateProcessEnv } from '../../../../src/db'
8
- import { buildDepGraph } from '../../../../src/deps'
9
- import { parseEnvString } from '../../../../src/utils'
7
+ import { getProcess, updateProcessEnv } from '../../../lib/runtime'
8
+ import { buildDepGraph } from '../../../lib/runtime'
9
+ import { parseEnvString } from '../../../lib/runtime'
10
10
 
11
11
  export async function GET() {
12
12
  const graph = await buildDepGraph()
@@ -5,7 +5,7 @@
5
5
  * When enabled=true, the built-in guard will auto-restart this process if it dies.
6
6
  * When enabled=false, the process is left alone.
7
7
  */
8
- import { getProcess, updateProcessEnv, addHistoryEntry } from '../../../../src/db';
8
+ import { getProcess, updateProcessEnv, addHistoryEntry } from '../../../lib/runtime';
9
9
 
10
10
  export async function POST(req: Request) {
11
11
  try {
@@ -5,7 +5,7 @@
5
5
  * When enabled=true, sets BGR_KEEP_ALIVE=true for ALL processes (except bgr-dashboard).
6
6
  * When enabled=false, removes BGR_KEEP_ALIVE from ALL processes.
7
7
  */
8
- import { getAllProcesses, getProcess, updateProcessEnv } from '../../../../src/db';
8
+ import { getAllProcesses, getProcess, updateProcessEnv } from '../../../lib/runtime';
9
9
 
10
10
  const SKIP = new Set(['bgr-dashboard']);
11
11
 
@@ -1,5 +1,5 @@
1
- import { guardEvents } from '../../../../src/server';
2
-
3
- export async function GET() {
4
- return Response.json(guardEvents);
1
+ import { guardEvents } from '../../../lib/runtime';
2
+
3
+ export async function GET() {
4
+ return Response.json(guardEvents);
5
5
  }
@@ -1,105 +1,105 @@
1
- import { getProcessHistory, getRecentHistory, addHistoryEntry } from '../../../../src/db';
2
-
3
- function stringifyMetadata(metadata: unknown) {
4
- try {
5
- return JSON.stringify(metadata ?? {});
6
- } catch {
7
- return '{}';
8
- }
9
- }
10
-
11
- function escapeCsv(value: unknown) {
12
- const text = String(value ?? '');
13
- if (/[",\n\r]/.test(text)) {
14
- return `"${text.replace(/"/g, '""')}"`;
15
- }
16
- return text;
17
- }
18
-
19
- function buildHistoryCsv(rows: Array<{
20
- process_name: string;
21
- event: string;
22
- pid: number | null;
23
- timestamp: string;
24
- metadata: unknown;
25
- }>) {
26
- const header = ['process_name', 'event', 'pid', 'timestamp', 'metadata'];
27
- const lines = rows.map((row) => [
28
- row.process_name,
29
- row.event,
30
- row.pid ?? '',
31
- row.timestamp,
32
- stringifyMetadata(row.metadata),
33
- ].map(escapeCsv).join(','));
34
- return [header.join(','), ...lines].join('\n');
35
- }
36
-
37
- export async function GET(req: Request) {
38
- const url = new URL(req.url);
39
- const name = url.searchParams.get('name');
40
- const event = (url.searchParams.get('event') || '').trim().toLowerCase();
41
- const metadataFilter = (url.searchParams.get('metadata') || '')
42
- .split(',')
43
- .map((value) => value.toLowerCase().trim())
44
- .filter(Boolean);
45
- const limit = parseInt(url.searchParams.get('limit') || '50');
46
- const format = (url.searchParams.get('format') || 'json').toLowerCase();
47
- const download = url.searchParams.get('download') === '1';
48
-
49
- let history;
50
- if (name) {
51
- history = getProcessHistory(name, limit);
52
- } else {
53
- history = getRecentHistory(limit);
54
- }
55
-
56
- let rows = history.map((h: any) => ({
57
- process_name: h.process_name,
58
- event: h.event,
59
- pid: h.pid,
60
- timestamp: h.timestamp,
61
- metadata: h.metadata ? JSON.parse(h.metadata) : {},
62
- }));
63
-
64
- if (event) {
65
- rows = rows.filter((row) => row.event.toLowerCase() === event);
66
- }
67
- if (metadataFilter.length > 0) {
68
- rows = rows.filter((row) => {
69
- const haystack = stringifyMetadata(row.metadata).toLowerCase();
70
- return metadataFilter.every((term) => haystack.includes(term));
71
- });
72
- }
73
-
74
- if (format === 'csv') {
75
- return new Response(buildHistoryCsv(rows), {
76
- headers: {
77
- 'content-type': 'text/csv; charset=utf-8',
78
- ...(download ? { 'content-disposition': `attachment; filename="bgr-history${name ? `-${encodeURIComponent(name)}` : ''}.csv"` } : {}),
79
- },
80
- });
81
- }
82
-
83
- return Response.json(rows, {
84
- headers: download
85
- ? { 'content-disposition': `attachment; filename="bgr-history${name ? `-${encodeURIComponent(name)}` : ''}.json"` }
86
- : undefined,
87
- });
88
- }
89
-
90
- export async function POST(req: Request) {
91
- try {
92
- const body = await req.json();
93
- const { process_name, event, pid, metadata } = body;
94
-
95
- if (!process_name || !event) {
96
- return Response.json({ error: 'process_name and event are required' }, { status: 400 });
97
- }
98
-
99
- addHistoryEntry(process_name, event, pid, metadata);
100
- return Response.json({ success: true });
101
- } catch (err) {
102
- console.error('[api/history] Error adding history:', err);
103
- return Response.json({ error: 'Failed to add history' }, { status: 500 });
104
- }
105
- }
1
+ import { getProcessHistory, getRecentHistory, addHistoryEntry } from '../../../lib/runtime';
2
+
3
+ function stringifyMetadata(metadata: unknown) {
4
+ try {
5
+ return JSON.stringify(metadata ?? {});
6
+ } catch {
7
+ return '{}';
8
+ }
9
+ }
10
+
11
+ function escapeCsv(value: unknown) {
12
+ const text = String(value ?? '');
13
+ if (/[",\n\r]/.test(text)) {
14
+ return `"${text.replace(/"/g, '""')}"`;
15
+ }
16
+ return text;
17
+ }
18
+
19
+ function buildHistoryCsv(rows: Array<{
20
+ process_name: string;
21
+ event: string;
22
+ pid: number | null;
23
+ timestamp: string;
24
+ metadata: unknown;
25
+ }>) {
26
+ const header = ['process_name', 'event', 'pid', 'timestamp', 'metadata'];
27
+ const lines = rows.map((row) => [
28
+ row.process_name,
29
+ row.event,
30
+ row.pid ?? '',
31
+ row.timestamp,
32
+ stringifyMetadata(row.metadata),
33
+ ].map(escapeCsv).join(','));
34
+ return [header.join(','), ...lines].join('\n');
35
+ }
36
+
37
+ export async function GET(req: Request) {
38
+ const url = new URL(req.url);
39
+ const name = url.searchParams.get('name');
40
+ const event = (url.searchParams.get('event') || '').trim().toLowerCase();
41
+ const metadataFilter = (url.searchParams.get('metadata') || '')
42
+ .split(',')
43
+ .map((value) => value.toLowerCase().trim())
44
+ .filter(Boolean);
45
+ const limit = parseInt(url.searchParams.get('limit') || '50');
46
+ const format = (url.searchParams.get('format') || 'json').toLowerCase();
47
+ const download = url.searchParams.get('download') === '1';
48
+
49
+ let history;
50
+ if (name) {
51
+ history = getProcessHistory(name, limit);
52
+ } else {
53
+ history = getRecentHistory(limit);
54
+ }
55
+
56
+ let rows = history.map((h: any) => ({
57
+ process_name: h.process_name,
58
+ event: h.event,
59
+ pid: h.pid,
60
+ timestamp: h.timestamp,
61
+ metadata: h.metadata ? JSON.parse(h.metadata) : {},
62
+ }));
63
+
64
+ if (event) {
65
+ rows = rows.filter((row) => row.event.toLowerCase() === event);
66
+ }
67
+ if (metadataFilter.length > 0) {
68
+ rows = rows.filter((row) => {
69
+ const haystack = stringifyMetadata(row.metadata).toLowerCase();
70
+ return metadataFilter.every((term) => haystack.includes(term));
71
+ });
72
+ }
73
+
74
+ if (format === 'csv') {
75
+ return new Response(buildHistoryCsv(rows), {
76
+ headers: {
77
+ 'content-type': 'text/csv; charset=utf-8',
78
+ ...(download ? { 'content-disposition': `attachment; filename="bgr-history${name ? `-${encodeURIComponent(name)}` : ''}.csv"` } : {}),
79
+ },
80
+ });
81
+ }
82
+
83
+ return Response.json(rows, {
84
+ headers: download
85
+ ? { 'content-disposition': `attachment; filename="bgr-history${name ? `-${encodeURIComponent(name)}` : ''}.json"` }
86
+ : undefined,
87
+ });
88
+ }
89
+
90
+ export async function POST(req: Request) {
91
+ try {
92
+ const body = await req.json();
93
+ const { process_name, event, pid, metadata } = body;
94
+
95
+ if (!process_name || !event) {
96
+ return Response.json({ error: 'process_name and event are required' }, { status: 400 });
97
+ }
98
+
99
+ addHistoryEntry(process_name, event, pid, metadata);
100
+ return Response.json({ success: true });
101
+ } catch (err) {
102
+ console.error('[api/history] Error adding history:', err);
103
+ return Response.json({ error: 'Failed to add history' }, { status: 500 });
104
+ }
105
+ }
@@ -1,100 +1,100 @@
1
- /**
2
- * GET /api/logs/:name — Read process stdout/stderr logs
3
- *
4
- * Supports incremental loading via query params:
5
- * ?tab=stdout|stderr — which log to read (default: stdout)
6
- * ?offset=N — byte offset to start reading from (default: 0 = full file)
7
- * ?format=json|text|csv — response format (default: json)
8
- *
9
- * Returns JSON by default:
10
- * { text, size, mtime, filePath }
11
- */
12
- import { getProcess } from '../../../../../src/db';
13
- import { stat, open } from 'fs/promises';
14
-
15
- interface FileInfo {
16
- text: string;
17
- size: number;
18
- mtime: string | null;
19
- filePath: string;
20
- }
21
-
22
- function escapeCsv(value: unknown) {
23
- const text = String(value ?? '');
24
- if (/[",\n\r]/.test(text)) {
25
- return `"${text.replace(/"/g, '""')}"`;
26
- }
27
- return text;
28
- }
29
-
30
- function buildLogCsv(text: string) {
31
- const header = 'line,text';
32
- const lines = text.split('\n').map((line, index) => `${index + 1},${escapeCsv(line)}`);
33
- return [header, ...lines].join('\n');
34
- }
35
-
36
- async function readLogFile(path: string, offset: number): Promise<FileInfo> {
37
- try {
38
- const s = await stat(path);
39
- const size = s.size;
40
- const mtime = s.mtime.toISOString();
41
-
42
- if (offset >= size) {
43
- return { text: '', size, mtime, filePath: path };
44
- }
45
-
46
- const handle = await open(path, 'r');
47
- try {
48
- const bytesToRead = size - offset;
49
- const buffer = Buffer.alloc(bytesToRead);
50
- await handle.read(buffer, 0, bytesToRead, offset);
51
- return { text: buffer.toString('utf-8'), size, mtime, filePath: path };
52
- } finally {
53
- await handle.close();
54
- }
55
- } catch {
56
- return { text: '', size: 0, mtime: null, filePath: path };
57
- }
58
- }
59
-
60
- export async function GET(req: Request, { params }: { params: { name: string } }) {
61
- const name = decodeURIComponent(params.name);
62
- const proc = getProcess(name);
63
-
64
- if (!proc) {
65
- return Response.json({ error: 'Process not found' }, { status: 404 });
66
- }
67
-
68
- const url = new URL(req.url);
69
- const tab = url.searchParams.get('tab') || 'stdout';
70
- const offset = parseInt(url.searchParams.get('offset') || '0', 10) || 0;
71
- const format = (url.searchParams.get('format') || 'json').toLowerCase();
72
- const download = url.searchParams.get('download') === '1';
73
-
74
- const path = tab === 'stderr' ? proc.stderr_path : proc.stdout_path;
75
- const info = await readLogFile(path, offset);
76
-
77
- if (format === 'text') {
78
- return new Response(info.text, {
79
- headers: {
80
- 'content-type': 'text/plain; charset=utf-8',
81
- ...(download ? { 'content-disposition': `attachment; filename="${encodeURIComponent(name)}-${tab}.log"` } : {}),
82
- },
83
- });
84
- }
85
-
86
- if (format === 'csv') {
87
- return new Response(buildLogCsv(info.text), {
88
- headers: {
89
- 'content-type': 'text/csv; charset=utf-8',
90
- ...(download ? { 'content-disposition': `attachment; filename="${encodeURIComponent(name)}-${tab}.csv"` } : {}),
91
- },
92
- });
93
- }
94
-
95
- return Response.json(info, {
96
- headers: download
97
- ? { 'content-disposition': `attachment; filename="${encodeURIComponent(name)}-${tab}.json"` }
98
- : undefined,
99
- });
100
- }
1
+ /**
2
+ * GET /api/logs/:name — Read process stdout/stderr logs
3
+ *
4
+ * Supports incremental loading via query params:
5
+ * ?tab=stdout|stderr — which log to read (default: stdout)
6
+ * ?offset=N — byte offset to start reading from (default: 0 = full file)
7
+ * ?format=json|text|csv — response format (default: json)
8
+ *
9
+ * Returns JSON by default:
10
+ * { text, size, mtime, filePath }
11
+ */
12
+ import { getProcess } from '../../../../lib/runtime';
13
+ import { stat, open } from 'fs/promises';
14
+
15
+ interface FileInfo {
16
+ text: string;
17
+ size: number;
18
+ mtime: string | null;
19
+ filePath: string;
20
+ }
21
+
22
+ function escapeCsv(value: unknown) {
23
+ const text = String(value ?? '');
24
+ if (/[",\n\r]/.test(text)) {
25
+ return `"${text.replace(/"/g, '""')}"`;
26
+ }
27
+ return text;
28
+ }
29
+
30
+ function buildLogCsv(text: string) {
31
+ const header = 'line,text';
32
+ const lines = text.split('\n').map((line, index) => `${index + 1},${escapeCsv(line)}`);
33
+ return [header, ...lines].join('\n');
34
+ }
35
+
36
+ async function readLogFile(path: string, offset: number): Promise<FileInfo> {
37
+ try {
38
+ const s = await stat(path);
39
+ const size = s.size;
40
+ const mtime = s.mtime.toISOString();
41
+
42
+ if (offset >= size) {
43
+ return { text: '', size, mtime, filePath: path };
44
+ }
45
+
46
+ const handle = await open(path, 'r');
47
+ try {
48
+ const bytesToRead = size - offset;
49
+ const buffer = Buffer.alloc(bytesToRead);
50
+ await handle.read(buffer, 0, bytesToRead, offset);
51
+ return { text: buffer.toString('utf-8'), size, mtime, filePath: path };
52
+ } finally {
53
+ await handle.close();
54
+ }
55
+ } catch {
56
+ return { text: '', size: 0, mtime: null, filePath: path };
57
+ }
58
+ }
59
+
60
+ export async function GET(req: Request, { params }: { params: { name: string } }) {
61
+ const name = decodeURIComponent(params.name);
62
+ const proc = getProcess(name);
63
+
64
+ if (!proc) {
65
+ return Response.json({ error: 'Process not found' }, { status: 404 });
66
+ }
67
+
68
+ const url = new URL(req.url);
69
+ const tab = url.searchParams.get('tab') || 'stdout';
70
+ const offset = parseInt(url.searchParams.get('offset') || '0', 10) || 0;
71
+ const format = (url.searchParams.get('format') || 'json').toLowerCase();
72
+ const download = url.searchParams.get('download') === '1';
73
+
74
+ const path = tab === 'stderr' ? proc.stderr_path : proc.stdout_path;
75
+ const info = await readLogFile(path, offset);
76
+
77
+ if (format === 'text') {
78
+ return new Response(info.text, {
79
+ headers: {
80
+ 'content-type': 'text/plain; charset=utf-8',
81
+ ...(download ? { 'content-disposition': `attachment; filename="${encodeURIComponent(name)}-${tab}.log"` } : {}),
82
+ },
83
+ });
84
+ }
85
+
86
+ if (format === 'csv') {
87
+ return new Response(buildLogCsv(info.text), {
88
+ headers: {
89
+ 'content-type': 'text/csv; charset=utf-8',
90
+ ...(download ? { 'content-disposition': `attachment; filename="${encodeURIComponent(name)}-${tab}.csv"` } : {}),
91
+ },
92
+ });
93
+ }
94
+
95
+ return Response.json(info, {
96
+ headers: download
97
+ ? { 'content-disposition': `attachment; filename="${encodeURIComponent(name)}-${tab}.json"` }
98
+ : undefined,
99
+ });
100
+ }
@@ -3,8 +3,8 @@
3
3
  * GET /api/logs/rotate — Get log file sizes
4
4
  */
5
5
 
6
- import { getAllProcesses } from '../../../../../src/db'
7
- import { rotateAllLogs } from '../../../../../src/log-rotation'
6
+ import { getAllProcesses } from '../../../../lib/runtime'
7
+ import { rotateAllLogs } from '../../../../lib/runtime'
8
8
  import { existsSync, statSync } from 'fs'
9
9
 
10
10
  export function POST() {