@@ -78,6 +78,7 @@ async function runScenario(name) {
7878
7979 const serve = spawn ( scenario . serveCmd [ 0 ] , scenario . serveCmd . slice ( 1 ) , {
8080 stdio : 'inherit' ,
81+ detached : true ,
8182 } ) ;
8283
8384 let serveExitInfo ;
@@ -150,13 +151,9 @@ async function runScenario(name) {
150151 } finally {
151152 shutdownRequested = true ;
152153
153- if ( serve . exitCode === null && serve . signalCode === null ) {
154- serve . kill ( 'SIGINT' ) ;
155- }
156-
157154 let serveExitError = null ;
158155 try {
159- await serveExitPromise ;
156+ await shutdownServe ( serve , serveExitPromise ) ;
160157 } catch ( error ) {
161158 console . error ( '[manifest-e2e] Serve command emitted error:' , error ) ;
162159 serveExitError = error ;
@@ -214,6 +211,94 @@ function spawnWithPromise(cmd, args, options = {}) {
214211 return { child, promise } ;
215212}
216213
214+ async function shutdownServe ( proc , exitPromise ) {
215+ if ( proc . exitCode !== null || proc . signalCode !== null ) {
216+ return exitPromise ;
217+ }
218+
219+ const sequence = [
220+ { signal : 'SIGINT' , timeoutMs : 8000 } ,
221+ { signal : 'SIGTERM' , timeoutMs : 5000 } ,
222+ { signal : 'SIGKILL' , timeoutMs : 3000 } ,
223+ ] ;
224+
225+ for ( const { signal, timeoutMs } of sequence ) {
226+ if ( proc . exitCode !== null || proc . signalCode !== null ) {
227+ break ;
228+ }
229+
230+ sendSignal ( proc , signal ) ;
231+
232+ try {
233+ await waitWithTimeout ( exitPromise , timeoutMs ) ;
234+ break ;
235+ } catch ( error ) {
236+ if ( error ?. name !== 'TimeoutError' ) {
237+ throw error ;
238+ }
239+ // escalate to next signal on timeout
240+ }
241+ }
242+
243+ return exitPromise ;
244+ }
245+
246+ function sendSignal ( proc , signal ) {
247+ if ( proc . exitCode !== null || proc . signalCode !== null ) {
248+ return ;
249+ }
250+
251+ try {
252+ process . kill ( - proc . pid , signal ) ;
253+ } catch ( error ) {
254+ if ( error . code !== 'ESRCH' && error . code !== 'EPERM' ) {
255+ throw error ;
256+ }
257+ try {
258+ proc . kill ( signal ) ;
259+ } catch ( innerError ) {
260+ if ( innerError . code !== 'ESRCH' ) {
261+ throw innerError ;
262+ }
263+ }
264+ }
265+ }
266+
267+ function waitWithTimeout ( promise , timeoutMs ) {
268+ return new Promise ( ( resolve , reject ) => {
269+ let settled = false ;
270+
271+ const timer = setTimeout ( ( ) => {
272+ if ( settled ) {
273+ return ;
274+ }
275+ settled = true ;
276+ const timeoutError = new Error ( `Timed out after ${ timeoutMs } ms` ) ;
277+ timeoutError . name = 'TimeoutError' ;
278+ reject ( timeoutError ) ;
279+ } , timeoutMs ) ;
280+
281+ promise . then (
282+ ( value ) => {
283+ if ( settled ) {
284+ return ;
285+ }
286+ settled = true ;
287+ clearTimeout ( timer ) ;
288+ resolve ( value ) ;
289+ } ,
290+ ( error ) => {
291+ if ( settled ) {
292+ return ;
293+ }
294+ settled = true ;
295+ clearTimeout ( timer ) ;
296+ reject ( error ) ;
297+ } ,
298+ ) ;
299+ } ) ;
300+ }
301+
217302function isExpectedServeExit ( info ) {
218303 if ( ! info ) {
219304 return false ;
0 commit comments