| 29 | // cleanly — otherwise the new instance can't bind to PORT and the app ends |
| 30 | // up talking to a stale PGlite with the wrong schema. |
| 31 | function reapStaleDevDb() { |
| 32 | const out = execSync(`lsof -ti tcp:${PORT} -sTCP:LISTEN 2>/dev/null || true`, { |
| 33 | encoding: "utf8", |
| 34 | }); |
| 35 | const pids = out.trim().split("\n").filter(Boolean); |
| 36 | if (pids.length === 0) return false; |
| 37 | |
| 38 | for (const pid of pids) { |
| 39 | const cmd = execSync(`ps -p ${pid} -o args= 2>/dev/null || true`, { |
| 40 | encoding: "utf8", |
| 41 | }).trim(); |
| 42 | if (!cmd.includes("dev-db.ts")) { |
| 43 | console.error(`[dev-db] Port ${PORT} is held by an unexpected process (pid ${pid}): ${cmd}`); |
| 44 | console.error(`[dev-db] Refusing to kill it. Free the port and retry.`); |
| 45 | process.exit(1); |
| 46 | } |
| 47 | console.log(`[dev-db] Reaping stale dev-db (pid ${pid})`); |
| 48 | execSync(`kill -KILL ${pid}`); |
| 49 | } |
| 50 | return true; |
| 51 | } |
| 52 | |
| 53 | if (reapStaleDevDb()) { |
| 54 | // Give the kernel a beat to release the socket before we try to bind. |