refactor(port-utils): drop Bun.serve in favor of node:net probe

isPortAvailable() previously bound a one-shot Bun.serve and stopped it.
That call was reachable from the plugin bundle through
src/shared/index.ts barrel re-export and crashed on Electron.

Switch to node:net.createServer().listen(port, host), which Bun fully
implements as well. Adds a 2s safety timeout and removes both
"error" and "listening" handlers on resolution to prevent listener
leaks. Behavior is bit-equivalent: returns true iff a server can bind
to (host, port) right now.

Test file is fully rewritten away from stale Bun.serve mocking. New
tests exercise: free-port detection via port 0, EADDRINUSE handling
via a real net.createServer blocker, findAvailablePort range
exhaustion, getAvailableServerPort auto-selection, 127.0.0.1 default
hostname binding, and probe-server resource cleanup.
This commit is contained in:
YeonGyu-Kim
2026-05-12 12:46:31 +09:00
parent 0aafe20a85
commit 2386cbd9b9
2 changed files with 421 additions and 284 deletions
+45 -10
View File
@@ -1,18 +1,53 @@
import { createServer } from "node:net"
const DEFAULT_SERVER_PORT = 4096
const MAX_PORT_ATTEMPTS = 20
const PORT_CHECK_TIMEOUT_MS = 2000
export async function isPortAvailable(port: number, hostname: string = "127.0.0.1"): Promise<boolean> {
try {
const server = Bun.serve({
port,
hostname,
fetch: () => new Response(),
return new Promise<boolean>((resolve) => {
const server = createServer()
let timeoutId: ReturnType<typeof setTimeout> | undefined
let resolved = false
const finish = (isAvailable: boolean): void => {
if (resolved) {
return
}
resolved = true
if (timeoutId) {
clearTimeout(timeoutId)
}
server.removeAllListeners("error")
server.removeAllListeners("listening")
resolve(isAvailable)
}
const closeThenFinish = (isAvailable: boolean): void => {
try {
server.close(() => finish(isAvailable))
} catch {
finish(isAvailable)
}
}
timeoutId = setTimeout(() => {
closeThenFinish(false)
}, PORT_CHECK_TIMEOUT_MS)
server.once("error", () => {
finish(false)
})
server.stop(true)
return true
} catch {
return false
}
server.once("listening", () => {
closeThenFinish(true)
})
try {
server.listen(port, hostname)
} catch {
finish(false)
}
})
}
export async function findAvailablePort(