bgrun 3.12.1 → 3.12.2

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.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * GET /api/check-port?port=3001 — Check if a port is in use
3
+ *
4
+ * Attempts a TCP connect to localhost:port. Returns { inUse, port }.
5
+ */
6
+
7
+ export async function GET(req: Request) {
8
+ const url = new URL(req.url);
9
+ const port = parseInt(url.searchParams.get('port') || '0');
10
+
11
+ if (!port || port < 1 || port > 65535) {
12
+ return Response.json({ error: 'Invalid port' }, { status: 400 });
13
+ }
14
+
15
+ const inUse = await isPortInUse(port);
16
+ return Response.json({ port, inUse });
17
+ }
18
+
19
+ async function isPortInUse(port: number): Promise<boolean> {
20
+ return new Promise((resolve) => {
21
+ try {
22
+ const server = Bun.serve({
23
+ port,
24
+ hostname: '127.0.0.1',
25
+ fetch() { return new Response(''); },
26
+ });
27
+ // If we successfully bound, it's free
28
+ server.stop(true);
29
+ resolve(false);
30
+ } catch {
31
+ // Port is in use
32
+ resolve(true);
33
+ }
34
+ });
35
+ }
@@ -0,0 +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
+ }
@@ -4,54 +4,19 @@
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 { getProcess } from '../../../../../src/db';
8
- import { handleRun } from '../../../../../src/commands/run';
9
- import { measure } from 'measure-fn';
10
- import { $ } from 'bun';
7
+ import { deployProcess } from '../../../../../src/deploy';
11
8
 
12
9
  export async function POST(req: Request, { params }: { params: { name: string } }) {
13
10
  const name = decodeURIComponent(params.name);
14
11
 
15
12
  try {
16
- const proc = getProcess(name);
17
- if (!proc) {
18
- return Response.json({ error: `Process '${name}' not found` }, { status: 404 });
13
+ const result = await deployProcess(name);
14
+ if (result.ok) {
15
+ return Response.json({ success: true, ...result });
19
16
  }
20
17
 
21
- const dir = proc.workdir;
22
-
23
- // Check if it's a git repo
24
- const isGit = await Bun.file(`${dir}/.git/HEAD`).exists();
25
- if (!isGit) {
26
- return Response.json({ error: `'${dir}' is not a git repository` }, { status: 400 });
27
- }
28
-
29
- const result = await measure(`Deploy "${name}"`, async () => {
30
- // 1. Git pull
31
- $.cwd(dir);
32
- const pullOutput = await $`git pull`.text();
33
-
34
- // 2. Install dependencies (detect package manager)
35
- let installOutput = '';
36
- const hasBunLock = await Bun.file(`${dir}/bun.lock`).exists() || await Bun.file(`${dir}/bun.lockb`).exists();
37
- const hasPackageJson = await Bun.file(`${dir}/package.json`).exists();
38
-
39
- if (hasPackageJson) {
40
- installOutput = await $`bun install`.text();
41
- }
42
-
43
- // 3. Restart the process
44
- await handleRun({
45
- action: 'run',
46
- name,
47
- force: true,
48
- remoteName: '',
49
- });
50
-
51
- return { pullOutput: pullOutput.trim(), installOutput: installOutput.trim() };
52
- });
53
-
54
- return Response.json({ success: true, ...result });
18
+ const status = result.skipped ? 400 : 500;
19
+ return Response.json({ error: result.reason || `Failed to deploy '${name}'`, ...result }, { status });
55
20
  } catch (e: any) {
56
21
  return Response.json({ error: e.message }, { status: 500 });
57
22
  }
@@ -0,0 +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
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * GET /api/next-port — Find the next available port
3
+ *
4
+ * Scans existing processes' env for PORT= values,
5
+ * then returns the next unused port starting from a base (default 3001).
6
+ */
7
+ import { getAllProcesses } from '../../../../src/db';
8
+
9
+ export async function GET(req: Request) {
10
+ const url = new URL(req.url);
11
+ const base = parseInt(url.searchParams.get('base') || '3001') || 3001;
12
+
13
+ const processes = getAllProcesses();
14
+ const usedPorts = new Set<number>();
15
+
16
+ for (const proc of processes) {
17
+ // Parse PORT from env string (comma-separated KEY=VAL)
18
+ const envStr = proc.env || '';
19
+ const portMatch = envStr.match(/(?:^|,)PORT=(\d+)/);
20
+ if (portMatch) {
21
+ usedPorts.add(parseInt(portMatch[1]));
22
+ }
23
+ }
24
+
25
+ // Find next available port
26
+ let nextPort = base;
27
+ while (usedPorts.has(nextPort)) {
28
+ nextPort++;
29
+ }
30
+
31
+ return Response.json({ port: nextPort, usedPorts: Array.from(usedPorts).sort((a, b) => a - b) });
32
+ }
@@ -9,12 +9,18 @@ export async function POST(req: Request) {
9
9
  const body = await req.json();
10
10
 
11
11
  try {
12
+ // Build env from body.env object if provided (e.g. { PORT: "3001" })
13
+ const env = body.env && typeof body.env === 'object'
14
+ ? body.env as Record<string, string>
15
+ : undefined;
16
+
12
17
  await measure(`Start process "${body.name}"`, () => handleRun({
13
18
  action: 'run',
14
19
  name: body.name,
15
20
  command: body.command,
16
21
  directory: body.directory,
17
22
  force: body.force || false,
23
+ env,
18
24
  remoteName: '',
19
25
  }));
20
26