fix CLI relay server startup and extension reconnection

- detect source vs compiled via __filename.endsWith('.ts') instead of env var
- add waitForExtension() to poll for extension connection after server start
- CLI waits up to 10s for extension to reconnect before executing commands
This commit is contained in:
Tommy D. Rossi
2026-01-24 14:23:50 +01:00
parent c8e0022790
commit 1d744e7995
2 changed files with 58 additions and 5 deletions
+6 -1
View File
@@ -2,7 +2,7 @@
import { cac } from '@xmorse/cac'
import { VERSION, LOG_FILE_PATH } from './utils.js'
import { ensureRelayServer, RELAY_PORT } from './relay-client.js'
import { ensureRelayServer, RELAY_PORT, waitForExtension } from './relay-client.js'
const cliRelayEnv = { PLAYWRITER_AUTO_ENABLE: '1' }
@@ -57,6 +57,11 @@ async function executeCode(options: {
// Ensure relay server is running (only for local)
if (!host && !process.env.PLAYWRITER_HOST) {
await ensureRelayServer({ logger: console, env: cliRelayEnv })
// Wait for extension to reconnect after server (re)start
const connected = await waitForExtension({ logger: console, timeoutMs: 10000 })
if (!connected) {
console.error('Warning: Extension not connected. Commands may fail.')
}
}
// Session is required
+52 -4
View File
@@ -29,6 +29,47 @@ export async function getRelayServerVersion(port: number = RELAY_PORT): Promise<
}
}
export async function getExtensionStatus(port: number = RELAY_PORT): Promise<{ connected: boolean; activeTargets: number } | null> {
try {
const response = await fetch(`http://127.0.0.1:${port}/extension/status`, {
signal: AbortSignal.timeout(500),
})
if (!response.ok) {
return null
}
return await response.json() as { connected: boolean; activeTargets: number }
} catch {
return null
}
}
/**
* Wait for the extension to connect to the relay server.
* Returns true if connected within timeout, false otherwise.
*/
export async function waitForExtension(options: {
port?: number
timeoutMs?: number
logger?: { log: (...args: any[]) => void }
} = {}): Promise<boolean> {
const { port = RELAY_PORT, timeoutMs = 5000, logger } = options
const startTime = Date.now()
logger?.log('Waiting for extension to connect...')
while (Date.now() - startTime < timeoutMs) {
const status = await getExtensionStatus(port)
if (status?.connected) {
logger?.log('Extension connected')
return true
}
await sleep(200)
}
logger?.log('Extension did not connect within timeout')
return false
}
async function killRelayServer(port: number): Promise<void> {
try {
await killPortProcess(port)
@@ -95,12 +136,14 @@ export async function ensureRelayServer(options: EnsureRelayServerOptions = {}):
logger?.log('CDP relay server not running, starting it...')
}
const dev = process.env.PLAYWRITER_NODE_ENV === 'development'
const scriptPath = dev
? path.resolve(__dirname, '../src/start-relay-server.ts')
// Detect if we're running from source (.ts) or compiled (.js)
// This handles: tsx, vite-node, ts-node, or direct node on compiled output
const isRunningFromSource = __filename.endsWith('.ts')
const scriptPath = isRunningFromSource
? path.resolve(__dirname, './start-relay-server.ts')
: path.resolve(__dirname, './start-relay-server.js')
const serverProcess = spawn(dev ? 'tsx' : process.execPath, [scriptPath], {
const serverProcess = spawn(isRunningFromSource ? 'tsx' : process.execPath, [scriptPath], {
detached: true,
stdio: 'ignore',
env: { ...process.env, ...additionalEnv },
@@ -108,14 +151,19 @@ export async function ensureRelayServer(options: EnsureRelayServerOptions = {}):
serverProcess.unref()
for (let i = 0; i < 10; i++) {
await sleep(500)
const newVersion = await getRelayServerVersion(RELAY_PORT)
if (newVersion) {
logger?.log('CDP relay server started successfully')
await sleep(1000)
return
}
}
throw new Error(`Failed to start CDP relay server after 5 seconds. Check logs at: ${LOG_FILE_PATH}`)
}