1- import { spawn } from "node:child_process "
1+ import launch from "cross-spawn "
22import { type Config } from "./gen/types.gen.js"
3+ import { stop , bindAbort } from "../process.js"
34
45export type ServerOptions = {
56 hostname ?: string
@@ -31,29 +32,38 @@ export async function createOpencodeServer(options?: ServerOptions) {
3132 const args = [ `serve` , `--hostname=${ options . hostname } ` , `--port=${ options . port } ` ]
3233 if ( options . config ?. logLevel ) args . push ( `--log-level=${ options . config . logLevel } ` )
3334
34- const proc = spawn ( `opencode` , args , {
35- signal : options . signal ,
35+ const proc = launch ( `opencode` , args , {
3636 env : {
3737 ...process . env ,
3838 OPENCODE_CONFIG_CONTENT : JSON . stringify ( options . config ?? { } ) ,
3939 } ,
4040 } )
41+ let clear = ( ) => { }
4142
4243 const url = await new Promise < string > ( ( resolve , reject ) => {
4344 const id = setTimeout ( ( ) => {
45+ clear ( )
46+ stop ( proc )
4447 reject ( new Error ( `Timeout waiting for server to start after ${ options . timeout } ms` ) )
4548 } , options . timeout )
4649 let output = ""
50+ let resolved = false
4751 proc . stdout ?. on ( "data" , ( chunk ) => {
52+ if ( resolved ) return
4853 output += chunk . toString ( )
4954 const lines = output . split ( "\n" )
5055 for ( const line of lines ) {
5156 if ( line . startsWith ( "opencode server listening" ) ) {
5257 const match = line . match ( / o n \s + ( h t t p s ? : \/ \/ [ ^ \s ] + ) / )
5358 if ( ! match ) {
54- throw new Error ( `Failed to parse server url from output: ${ line } ` )
59+ clear ( )
60+ stop ( proc )
61+ clearTimeout ( id )
62+ reject ( new Error ( `Failed to parse server url from output: ${ line } ` ) )
63+ return
5564 }
5665 clearTimeout ( id )
66+ resolved = true
5767 resolve ( match [ 1 ] ! )
5868 return
5969 }
@@ -74,18 +84,17 @@ export async function createOpencodeServer(options?: ServerOptions) {
7484 clearTimeout ( id )
7585 reject ( error )
7686 } )
77- if ( options . signal ) {
78- options . signal . addEventListener ( "abort" , ( ) => {
79- clearTimeout ( id )
80- reject ( new Error ( "Aborted" ) )
81- } )
82- }
87+ clear = bindAbort ( proc , options . signal , ( ) => {
88+ clearTimeout ( id )
89+ reject ( options . signal ?. reason )
90+ } )
8391 } )
8492
8593 return {
8694 url,
8795 close ( ) {
88- proc . kill ( )
96+ clear ( )
97+ stop ( proc )
8998 } ,
9099 }
91100}
@@ -106,18 +115,20 @@ export function createOpencodeTui(options?: TuiOptions) {
106115 args . push ( `--agent=${ options . agent } ` )
107116 }
108117
109- const proc = spawn ( `opencode` , args , {
110- signal : options ?. signal ,
118+ const proc = launch ( `opencode` , args , {
111119 stdio : "inherit" ,
112120 env : {
113121 ...process . env ,
114122 OPENCODE_CONFIG_CONTENT : JSON . stringify ( options ?. config ?? { } ) ,
115123 } ,
116124 } )
117125
126+ const clear = bindAbort ( proc , options ?. signal )
127+
118128 return {
119129 close ( ) {
120- proc . kill ( )
130+ clear ( )
131+ stop ( proc )
121132 } ,
122133 }
123134}
0 commit comments