Skip to content

Commit 50123b2

Browse files
committed
fix: add error to log
1 parent d4a32d4 commit 50123b2

3 files changed

Lines changed: 62 additions & 8 deletions

File tree

src/cli/commands/do.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,17 @@ export const doCommand = new Command('do')
3434
process.exit(1)
3535
}
3636

37+
let conn: Awaited<ReturnType<typeof connectToAgent>> | null = null
38+
39+
const cleanup = () => {
40+
conn?.close()
41+
process.exit(0)
42+
}
43+
process.on('SIGINT', cleanup)
44+
process.on('SIGTERM', cleanup)
45+
3746
try {
38-
const conn = await connectToAgent({
47+
conn = await connectToAgent({
3948
port: session.port,
4049
agent: session.agent,
4150
env: session.env,

src/cli/commands/run.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { loadConfig } from '../config.js'
88
import { connectToAgent, type AgentConnection } from '../connection.js'
99
import { getRunningSession } from '../session-file.js'
1010
import { replayTask } from '../task-runner.js'
11-
import { startWrangler } from '../wrangler.js'
11+
import { killProcessTree, startWrangler } from '../wrangler.js'
1212

1313
function resolveDependencies(projectDir: string, task: TaskFile, seen = new Set<string>()): TaskFile[] {
1414
if (!task.depends?.length) return []
@@ -73,7 +73,7 @@ export const runCommand = new Command('run')
7373
const cleanup = async () => {
7474
await browserSession?.cleanup()
7575
conn?.close()
76-
wrangler?.kill()
76+
if (wrangler) killProcessTree(wrangler)
7777
}
7878

7979
process.on('SIGINT', () => void cleanup().then(() => process.exit(0)))

src/cli/wrangler.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { spawn, type ChildProcess } from 'node:child_process'
1+
import { spawn, execSync, type ChildProcess } from 'node:child_process'
22
import { existsSync } from 'node:fs'
3+
import { createServer } from 'node:net'
34
import { resolve } from 'node:path'
45

56
/**
@@ -69,6 +70,41 @@ export function resolveWranglerConfig(cwd: string, configPath?: string): string
6970
)
7071
}
7172

73+
/**
74+
* Check if a port is free. If not, kill whatever is holding it
75+
* (likely a stale wrangler from a previous run).
76+
*/
77+
async function ensurePortFree(port: number): Promise<void> {
78+
const isFree = await new Promise<boolean>((resolve_) => {
79+
const server = createServer()
80+
server.once('error', () => resolve_(false))
81+
server.once('listening', () => {
82+
server.close()
83+
resolve_(true)
84+
})
85+
server.listen(port)
86+
})
87+
88+
if (isFree) return
89+
90+
// Port is taken — find and kill the process holding it
91+
try {
92+
const output = execSync(`lsof -ti tcp:${port}`, { encoding: 'utf-8' })
93+
const pids = output.trim().split('\n').filter(Boolean)
94+
for (const pid of pids) {
95+
try {
96+
process.kill(parseInt(pid, 10), 'SIGTERM')
97+
} catch { /* already dead */ }
98+
}
99+
// Wait briefly for port to be released
100+
await new Promise((r) => setTimeout(r, 500))
101+
} catch {
102+
throw new Error(
103+
`Port ${port} is in use and could not be freed. Kill the process manually or use a different port.`,
104+
)
105+
}
106+
}
107+
72108
/**
73109
* Spawn `npx wrangler dev` and wait for "Ready on" output.
74110
* Returns the child process.
@@ -81,7 +117,7 @@ export function startWrangler(opts: {
81117
const cwd = opts.cwd ?? process.cwd()
82118
const configPath = resolveWranglerConfig(cwd, opts.wrangler)
83119

84-
return new Promise((resolve_, reject) => {
120+
return ensurePortFree(opts.port).then(() => new Promise((resolve_, reject) => {
85121
const wranglerArgs = [
86122
'wrangler',
87123
'dev',
@@ -102,14 +138,19 @@ export function startWrangler(opts: {
102138
30000,
103139
)
104140

141+
const stderrChunks: string[] = []
142+
105143
const onReady = (data: Buffer) => {
106144
if (data.toString().includes('Ready on')) {
107145
clearTimeout(timeout)
108146
resolve_(wrangler)
109147
}
110148
}
111149

112-
wrangler.stderr?.on('data', onReady)
150+
wrangler.stderr?.on('data', (data: Buffer) => {
151+
stderrChunks.push(data.toString())
152+
onReady(data)
153+
})
113154
wrangler.stdout?.on('data', onReady)
114155

115156
wrangler.on('error', (err) => {
@@ -119,7 +160,11 @@ export function startWrangler(opts: {
119160

120161
wrangler.on('exit', (code) => {
121162
clearTimeout(timeout)
122-
if (code !== 0) reject(new Error(`Wrangler exited with code ${code}`))
163+
if (code !== 0) {
164+
const stderr = stderrChunks.join('').trim()
165+
const detail = stderr ? `:\n${stderr}` : ''
166+
reject(new Error(`Wrangler exited with code ${code}${detail}`))
167+
}
123168
})
124-
})
169+
}))
125170
}

0 commit comments

Comments
 (0)