@take-out/scripts 0.0.34 → 0.0.36

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 (2) hide show
  1. package/package.json +2 -2
  2. package/src/ensure-port.ts +97 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@take-out/scripts",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "sideEffects": false,
@@ -24,7 +24,7 @@
24
24
  "access": "public"
25
25
  },
26
26
  "dependencies": {
27
- "@take-out/helpers": "0.0.34"
27
+ "@take-out/helpers": "0.0.36"
28
28
  },
29
29
  "devDependencies": {
30
30
  "vxrn": "*"
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * @description Ensure a port is available, exit with error if in use
5
+ * @args --auto-kill <prefix>
6
+ */
7
+
8
+ import { execSync } from 'node:child_process'
9
+ import { parseArgs } from 'node:util'
10
+
11
+ const { values, positionals } = parseArgs({
12
+ args: process.argv.slice(2),
13
+ options: {
14
+ 'auto-kill': {
15
+ type: 'string',
16
+ short: 'k',
17
+ },
18
+ },
19
+ allowPositionals: true,
20
+ })
21
+
22
+ const port = positionals[0]
23
+
24
+ if (!port) {
25
+ console.error('usage: bun tko ensure-port <port> [--auto-kill <prefix>]')
26
+ process.exit(1)
27
+ }
28
+
29
+ const portNum = Number.parseInt(port, 10)
30
+
31
+ if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
32
+ console.error(`invalid port: ${port}`)
33
+ process.exit(1)
34
+ }
35
+
36
+ function getListeningProcess(p: number): { pid?: number; command?: string } {
37
+ try {
38
+ // use -sTCP:LISTEN to only find processes LISTENING on the port (servers)
39
+ // not clients connected to it
40
+ const output = execSync(`lsof -i :${p} -sTCP:LISTEN -t`, {
41
+ encoding: 'utf-8',
42
+ stdio: ['pipe', 'pipe', 'ignore'],
43
+ }).trim()
44
+
45
+ if (!output) return {}
46
+
47
+ const pid = Number.parseInt(output.split('\n')[0] || '', 10)
48
+ if (Number.isNaN(pid)) return {}
49
+
50
+ // get command name
51
+ const ps = execSync(`ps -p ${pid} -o comm=`, {
52
+ encoding: 'utf-8',
53
+ stdio: ['pipe', 'pipe', 'ignore'],
54
+ }).trim()
55
+
56
+ return { pid, command: ps }
57
+ } catch {
58
+ return {}
59
+ }
60
+ }
61
+
62
+ function killProcess(pid: number): boolean {
63
+ try {
64
+ execSync(`kill ${pid}`, { stdio: 'ignore' })
65
+ return true
66
+ } catch {
67
+ return false
68
+ }
69
+ }
70
+
71
+ const { pid, command } = getListeningProcess(portNum)
72
+
73
+ if (pid) {
74
+ const autoKillPrefix = values['auto-kill']
75
+
76
+ // check if we should auto-kill this process
77
+ if (autoKillPrefix && command?.startsWith(autoKillPrefix)) {
78
+ console.info(`killing ${command} (pid ${pid}) on port ${portNum}`)
79
+ if (killProcess(pid)) {
80
+ // give it a moment to release the port
81
+ Bun.sleepSync(100)
82
+ // verify it's gone
83
+ const check = getListeningProcess(portNum)
84
+ if (!check.pid) {
85
+ process.exit(0)
86
+ }
87
+ console.error(`failed to free port ${portNum}`)
88
+ process.exit(1)
89
+ }
90
+ console.error(`failed to kill pid ${pid}`)
91
+ process.exit(1)
92
+ }
93
+
94
+ console.error(`port ${portNum} in use by ${command || 'unknown'} (pid ${pid})`)
95
+ console.error(`run: kill ${pid}`)
96
+ process.exit(1)
97
+ }