@@ -27,38 +27,29 @@ public Task<string> StartAsync()
2727
2828 async Task < string > StartCoreAsync ( )
2929 {
30- string filename ;
31- string args ;
32-
33- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
34- {
35- filename = "cmd.exe" ;
36- args = "/c npm.cmd run start" ;
37-
38- }
39- else
40- {
41- filename = "npm" ;
42- args = "run start" ;
43- }
44-
30+ // Spawn the server directly so a stalled npm/cmd wrapper cannot strand startup or cleanup.
4531 var startInfo = new ProcessStartInfo
4632 {
47- FileName = filename ,
33+ FileName = "node" ,
4834 WorkingDirectory = Path . Join ( FindRepoRoot ( ) , "test" , "harness" ) ,
49- Arguments = args ,
35+ Arguments = "--import tsx server.ts" ,
5036 UseShellExecute = false ,
5137 RedirectStandardOutput = true ,
5238 RedirectStandardError = true ,
5339 CreateNoWindow = true ,
5440 } ;
5541
56- _process = new Process { StartInfo = startInfo } ;
42+ var process = new Process { StartInfo = startInfo } ;
5743
5844 var tcs = new TaskCompletionSource < string > ( ) ;
5945 var errorOutput = new StringBuilder ( ) ;
46+ var startupTimer = Stopwatch . StartNew ( ) ;
47+ string CapturedErrors ( )
48+ {
49+ lock ( errorOutput ) return errorOutput . ToString ( ) ;
50+ }
6051
61- _process . OutputDataReceived += ( _ , e ) =>
52+ process . OutputDataReceived += ( _ , e ) =>
6253 {
6354 if ( e . Data == null ) return ;
6455 var match = Regex . Match ( e . Data , @"Listening: (?<url>http://[^\s]+)\s+(?<metadata>\{.*\})$" ) ;
@@ -98,28 +89,39 @@ async Task<string> StartCoreAsync()
9889 tcs . TrySetResult ( match . Groups [ "url" ] . Value ) ;
9990 } ;
10091
101- _process . ErrorDataReceived += ( _ , e ) =>
92+ process . ErrorDataReceived += ( _ , e ) =>
10293 {
10394 if ( e . Data == null ) return ;
104- errorOutput . AppendLine ( e . Data ) ;
95+ lock ( errorOutput ) errorOutput . AppendLine ( e . Data ) ;
10596 Console . Error . WriteLine ( e . Data ) ;
10697 } ;
10798
108- _process . Start ( ) ;
109- _process . BeginOutputReadLine ( ) ;
110- _process . BeginErrorReadLine ( ) ;
111- _ = _process . WaitForExitAsync ( ) . ContinueWith ( _ =>
99+ try
100+ {
101+ process . Start ( ) ;
102+ }
103+ catch
104+ {
105+ process . Dispose ( ) ;
106+ throw ;
107+ }
108+ _process = process ;
109+ process . BeginOutputReadLine ( ) ;
110+ process . BeginErrorReadLine ( ) ;
111+ _ = process . WaitForExitAsync ( ) . ContinueWith ( _ =>
112112 {
113- if ( _process ? . ExitCode is int exitCode && exitCode != 0 )
113+ if ( ! tcs . Task . IsCompleted )
114114 {
115- tcs . TrySetException ( new Exception ( $ "Proxy exited with code { _process . ExitCode } : { errorOutput } ") ) ;
115+ tcs . TrySetException ( new Exception ( $ "Proxy exited before listening with code { process . ExitCode } : { CapturedErrors ( ) } ") ) ;
116116 }
117117 } ) ;
118118
119119 // Use longer timeout on Windows due to slower process startup
120120 var timeoutSeconds = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ? 30 : 10 ;
121121 using var cts = new CancellationTokenSource ( TimeSpan . FromSeconds ( timeoutSeconds ) ) ;
122- cts . Token . Register ( ( ) => tcs . TrySetException ( new TimeoutException ( "Timeout waiting for proxy" ) ) ) ;
122+ cts . Token . Register ( ( ) => tcs . TrySetException ( new TimeoutException (
123+ $ "Timeout waiting for proxy after { startupTimer . ElapsedMilliseconds } ms " +
124+ $ "(exited: { process . HasExited } ; stderr: { CapturedErrors ( ) } )") ) ) ;
123125
124126 return await tcs . Task ;
125127 }
0 commit comments