bgrun 3.12.2 → 3.12.3

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/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(exports_platform, {
25
25
  reconcileProcessPids: () => reconcileProcessPids,
26
26
  readFileTail: () => readFileTail,
27
27
  psExec: () => psExec,
28
+ parseUnixListeningPorts: () => parseUnixListeningPorts,
28
29
  killProcessOnPort: () => killProcessOnPort,
29
30
  isWindows: () => isWindows,
30
31
  isProcessRunning: () => isProcessRunning,
@@ -457,6 +458,17 @@ async function getProcessBatchResources(pids) {
457
458
  return resourceMap;
458
459
  }) ?? new Map;
459
460
  }
461
+ function parseUnixListeningPorts(output) {
462
+ const ports = new Set;
463
+ for (const line of output.split(`
464
+ `)) {
465
+ const portMatch = line.match(/:(\d+)\s+\(LISTEN\)/);
466
+ if (portMatch) {
467
+ ports.add(parseInt(portMatch[1]));
468
+ }
469
+ }
470
+ return Array.from(ports);
471
+ }
460
472
  async function getProcessPorts(pid) {
461
473
  try {
462
474
  if (isWindows()) {
@@ -473,29 +485,21 @@ async function getProcessPorts(pid) {
473
485
  } else {
474
486
  try {
475
487
  const result2 = await $`ss -tlnp`.nothrow().quiet().text();
476
- const ports2 = new Set;
488
+ const ports = new Set;
477
489
  for (const line of result2.split(`
478
490
  `)) {
479
491
  if (line.includes(`pid=${pid}`)) {
480
492
  const portMatch = line.match(/:(\d+)\s/);
481
493
  if (portMatch) {
482
- ports2.add(parseInt(portMatch[1]));
494
+ ports.add(parseInt(portMatch[1]));
483
495
  }
484
496
  }
485
497
  }
486
- if (ports2.size > 0)
487
- return Array.from(ports2);
498
+ if (ports.size > 0)
499
+ return Array.from(ports);
488
500
  } catch {}
489
501
  const result = await $`lsof -Pan -p ${pid} -iTCP -sTCP:LISTEN`.nothrow().quiet().text();
490
- const ports = new Set;
491
- for (const line of result.split(`
492
- `)) {
493
- const portMatch = line.match(/:(\d+)\s+\(LISTEN\)/);
494
- if (portMatch) {
495
- ports.add(parseInt(portMatch[1]));
496
- }
497
- }
498
- return Array.from(ports);
502
+ return parseUnixListeningPorts(result);
499
503
  }
500
504
  } catch {
501
505
  return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bgrun",
3
- "version": "3.12.2",
3
+ "version": "3.12.3",
4
4
  "description": "bgrun — A lightweight process manager for Bun",
5
5
  "type": "module",
6
6
  "main": "./src/api.ts",
package/src/bgrun.test.ts CHANGED
@@ -10,7 +10,7 @@ import { describe, expect, test } from 'bun:test'
10
10
  import { parseEnvString, calculateRuntime } from './utils'
11
11
  import { stripAnsi, truncateString, truncatePath } from './table'
12
12
  import { detectPackageManager, formatDeployToolError } from './deploy'
13
- import { isProcessRunning } from './platform'
13
+ import { isProcessRunning, parseUnixListeningPorts } from './platform'
14
14
  import { mkdirSync, rmSync } from 'fs'
15
15
 
16
16
  // Use a test-specific database to avoid polluting real data
@@ -141,6 +141,39 @@ describe('isProcessRunning', () => {
141
141
  })
142
142
  })
143
143
 
144
+ describe('parseUnixListeningPorts', () => {
145
+ test('extracts only LISTEN ports from lsof output', () => {
146
+ const output = [
147
+ 'COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME',
148
+ 'bun 12345 root 21u IPv4 123456 0t0 TCP *:3400 (LISTEN)',
149
+ 'bun 12345 root 22u IPv4 123457 0t0 TCP 127.0.0.1:9222 (LISTEN)',
150
+ ].join('\n')
151
+
152
+ expect(parseUnixListeningPorts(output)).toEqual([3400, 9222])
153
+ })
154
+
155
+ test('ignores non-LISTEN sockets from broad lsof output', () => {
156
+ const output = [
157
+ 'COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME',
158
+ 'bun 12345 root 18u IPv4 111111 0t0 TCP 127.0.0.1:49440->127.0.0.1:3000 (ESTABLISHED)',
159
+ 'bun 12345 root 19u IPv4 111112 0t0 TCP 127.0.0.1:49441->127.0.0.1:3737 (ESTABLISHED)',
160
+ 'bun 12345 root 20u IPv4 111113 0t0 TCP *:3400 (LISTEN)',
161
+ ].join('\n')
162
+
163
+ expect(parseUnixListeningPorts(output)).toEqual([3400])
164
+ })
165
+
166
+ test('returns empty array for no-port worker output', () => {
167
+ const output = [
168
+ 'COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME',
169
+ 'bun 12345 root 18u unix 0xffff 0t0 /tmp/bun.sock',
170
+ 'bun 12345 root 19u IPv4 111111 0t0 TCP 127.0.0.1:49440->127.0.0.1:3000 (ESTABLISHED)',
171
+ ].join('\n')
172
+
173
+ expect(parseUnixListeningPorts(output)).toEqual([])
174
+ })
175
+ })
176
+
144
177
  // ─── detectPackageManager ───────────────────────────────
145
178
 
146
179
  describe('formatDeployToolError', () => {
@@ -217,64 +250,64 @@ describe('detectPackageManager', () => {
217
250
  }
218
251
  })
219
252
  })
220
-
221
- // ─── Dependencies ───────────────────────────────────────
222
-
223
- describe('addDependency', () => {
224
- test('adds a valid dependency', () => {
225
- removeAllDependencies('web-server');
226
- removeAllDependencies('database');
227
- const ok = addDependency('web-server', 'database');
228
- expect(ok).toBe(true);
229
- expect(getDependencies('web-server')).toContain('database');
230
- })
231
-
232
- test('prevents self-dependency', () => {
233
- expect(addDependency('api', 'api')).toBe(false);
234
- })
235
-
236
- test('prevents duplicate dependency', () => {
237
- removeAllDependencies('app');
238
- addDependency('app', 'db');
239
- expect(addDependency('app', 'db')).toBe(false);
240
- })
241
-
242
- test('prevents circular dependency', () => {
243
- removeAllDependencies('a');
244
- removeAllDependencies('b');
245
- removeAllDependencies('c');
246
- addDependency('a', 'b');
247
- addDependency('b', 'c');
248
- // c -> a would create a cycle
249
- expect(addDependency('c', 'a')).toBe(false);
250
- })
251
- })
252
-
253
- describe('getDependencyGraph', () => {
254
- test('returns full graph', () => {
255
- removeAllDependencies('svc-a');
256
- removeAllDependencies('svc-b');
257
- addDependency('svc-a', 'svc-b');
258
- const graph = getDependencyGraph();
259
- expect(graph['svc-a']).toContain('svc-b');
260
- })
261
- })
262
-
263
- describe('getDependents', () => {
264
- test('finds processes that depend on a target', () => {
265
- removeAllDependencies('frontend');
266
- removeAllDependencies('backend');
267
- addDependency('frontend', 'backend');
268
- expect(getDependents('backend')).toContain('frontend');
269
- })
270
- })
271
-
272
- describe('removeDependency', () => {
273
- test('removes an existing dependency', () => {
274
- removeAllDependencies('x');
275
- addDependency('x', 'y');
276
- expect(getDependencies('x')).toContain('y');
277
- removeDependency('x', 'y');
278
- expect(getDependencies('x')).not.toContain('y');
279
- })
280
- })
253
+
254
+ // ─── Dependencies ───────────────────────────────────────
255
+
256
+ describe('addDependency', () => {
257
+ test('adds a valid dependency', () => {
258
+ removeAllDependencies('web-server');
259
+ removeAllDependencies('database');
260
+ const ok = addDependency('web-server', 'database');
261
+ expect(ok).toBe(true);
262
+ expect(getDependencies('web-server')).toContain('database');
263
+ })
264
+
265
+ test('prevents self-dependency', () => {
266
+ expect(addDependency('api', 'api')).toBe(false);
267
+ })
268
+
269
+ test('prevents duplicate dependency', () => {
270
+ removeAllDependencies('app');
271
+ addDependency('app', 'db');
272
+ expect(addDependency('app', 'db')).toBe(false);
273
+ })
274
+
275
+ test('prevents circular dependency', () => {
276
+ removeAllDependencies('a');
277
+ removeAllDependencies('b');
278
+ removeAllDependencies('c');
279
+ addDependency('a', 'b');
280
+ addDependency('b', 'c');
281
+ // c -> a would create a cycle
282
+ expect(addDependency('c', 'a')).toBe(false);
283
+ })
284
+ })
285
+
286
+ describe('getDependencyGraph', () => {
287
+ test('returns full graph', () => {
288
+ removeAllDependencies('svc-a');
289
+ removeAllDependencies('svc-b');
290
+ addDependency('svc-a', 'svc-b');
291
+ const graph = getDependencyGraph();
292
+ expect(graph['svc-a']).toContain('svc-b');
293
+ })
294
+ })
295
+
296
+ describe('getDependents', () => {
297
+ test('finds processes that depend on a target', () => {
298
+ removeAllDependencies('frontend');
299
+ removeAllDependencies('backend');
300
+ addDependency('frontend', 'backend');
301
+ expect(getDependents('backend')).toContain('frontend');
302
+ })
303
+ })
304
+
305
+ describe('removeDependency', () => {
306
+ test('removes an existing dependency', () => {
307
+ removeAllDependencies('x');
308
+ addDependency('x', 'y');
309
+ expect(getDependencies('x')).toContain('y');
310
+ removeDependency('x', 'y');
311
+ expect(getDependencies('x')).not.toContain('y');
312
+ })
313
+ })
package/src/platform.ts CHANGED
@@ -608,6 +608,20 @@ export async function getProcessBatchResources(pids: number[]): Promise<Map<numb
608
608
  }) ?? new Map();
609
609
  }
610
610
 
611
+ /**
612
+ * Parse Unix lsof LISTEN output and return only true listening TCP ports.
613
+ */
614
+ export function parseUnixListeningPorts(output: string): number[] {
615
+ const ports = new Set<number>();
616
+ for (const line of output.split('\n')) {
617
+ const portMatch = line.match(/:(\d+)\s+\(LISTEN\)/);
618
+ if (portMatch) {
619
+ ports.add(parseInt(portMatch[1]));
620
+ }
621
+ }
622
+ return Array.from(ports);
623
+ }
624
+
611
625
  /**
612
626
  * Get the TCP ports a process is currently listening on by querying the OS.
613
627
  * Returns an array of port numbers (empty if none or process not found).
@@ -643,14 +657,7 @@ export async function getProcessPorts(pid: number): Promise<number[]> {
643
657
  } catch { /* ss not available, try lsof */ }
644
658
 
645
659
  const result = await $`lsof -Pan -p ${pid} -iTCP -sTCP:LISTEN`.nothrow().quiet().text();
646
- const ports = new Set<number>();
647
- for (const line of result.split('\n')) {
648
- const portMatch = line.match(/:(\d+)\s+\(LISTEN\)/);
649
- if (portMatch) {
650
- ports.add(parseInt(portMatch[1]));
651
- }
652
- }
653
- return Array.from(ports);
660
+ return parseUnixListeningPorts(result);
654
661
  }
655
662
  } catch {
656
663
  return [];