@@ -121,6 +121,9 @@ export default async function setup(project: TestProject) {
121121 async function startServers ( ) {
122122 console . log ( 'Starting servers...' )
123123
124+ // Start app server if necessary
125+ await startAppServerIfNecessary ( )
126+
124127 // Start the MCP server from the exercise directory
125128 console . log ( `Starting MCP server on port ${ mcpServerPort } ...` )
126129 mcpServerProcess = execa (
@@ -166,21 +169,68 @@ export default async function setup(project: TestProject) {
166169 async function cleanup ( ) {
167170 console . log ( 'Cleaning up servers...' )
168171
172+ const cleanupPromises : Array < Promise < void > > = [ ]
173+
169174 if ( mcpServerProcess && ! mcpServerProcess . killed ) {
170- mcpServerProcess . kill ( 'SIGTERM' )
171- try {
172- await mcpServerProcess
173- } catch {
174- // Process was killed, which is expected
175- }
175+ cleanupPromises . push (
176+ ( async ( ) => {
177+ mcpServerProcess . kill ( 'SIGTERM' )
178+ // Give it 2 seconds to gracefully shutdown, then force kill
179+ const timeout = setTimeout ( ( ) => {
180+ if ( ! mcpServerProcess . killed ) {
181+ mcpServerProcess . kill ( 'SIGKILL' )
182+ }
183+ } , 2000 )
184+
185+ try {
186+ await mcpServerProcess
187+ } catch {
188+ // Process was killed, which is expected
189+ } finally {
190+ clearTimeout ( timeout )
191+ }
192+ } ) ( )
193+ )
176194 }
177195
178196 if ( appServerProcess && ! appServerProcess . killed ) {
179- appServerProcess . kill ( 'SIGTERM' )
180- try {
181- await appServerProcess
182- } catch {
183- // Process was killed, which is expected
197+ cleanupPromises . push (
198+ ( async ( ) => {
199+ appServerProcess . kill ( 'SIGTERM' )
200+ // Give it 2 seconds to gracefully shutdown, then force kill
201+ const timeout = setTimeout ( ( ) => {
202+ if ( ! appServerProcess . killed ) {
203+ appServerProcess . kill ( 'SIGKILL' )
204+ }
205+ } , 2000 )
206+
207+ try {
208+ await appServerProcess
209+ } catch {
210+ // Process was killed, which is expected
211+ } finally {
212+ clearTimeout ( timeout )
213+ }
214+ } ) ( )
215+ )
216+ }
217+
218+ // Wait for all cleanup to complete, but with an overall timeout
219+ try {
220+ await Promise . race ( [
221+ Promise . all ( cleanupPromises ) ,
222+ new Promise ( ( _ , reject ) =>
223+ setTimeout ( ( ) => reject ( new Error ( 'Cleanup timeout' ) ) , 5000 )
224+ )
225+ ] )
226+ } catch ( error ) {
227+ console . warn ( 'Cleanup warning:' , error . message )
228+ // Force kill any remaining processes
229+ if ( mcpServerProcess && ! mcpServerProcess . killed ) {
230+ mcpServerProcess . kill ( 'SIGKILL' )
231+ }
232+ if ( appServerProcess && ! appServerProcess . killed ) {
233+ appServerProcess . kill ( 'SIGKILL' )
184234 }
185235 }
186236
0 commit comments