@@ -3,6 +3,7 @@ import type { File, Task, TaskEventPack, TaskResultPack, TaskState } from '@vite
33import type { ParsedStack } from '@vitest/utils'
44import type { EachMapping } from '@vitest/utils/source-map'
55import type { ChildProcess } from 'node:child_process'
6+ import type { Result } from 'tinyexec'
67import type { Vitest } from '../node/core'
78import type { TestProject } from '../node/project'
89import type { Awaitable } from '../types/general'
@@ -277,11 +278,7 @@ export class Typechecker {
277278 return this . _output
278279 }
279280
280- public async start ( ) : Promise < void > {
281- if ( this . process ) {
282- return
283- }
284-
281+ private async spawn ( ) {
285282 const { root, watch, typecheck } = this . project . config
286283
287284 const args = [
@@ -314,31 +311,88 @@ export class Typechecker {
314311 } ,
315312 throwOnError : false ,
316313 } )
314+
317315 this . process = child . process
318- await this . _onParseStart ?. ( )
316+
319317 let rerunTriggered = false
320- child . process ?. stdout ?. on ( 'data' , ( chunk ) => {
321- this . _output += chunk
322- if ( ! watch ) {
318+ let dataReceived = false
319+
320+ return new Promise < { result : Result } > ( ( resolve , reject ) => {
321+ if ( ! child . process || ! child . process . stdout ) {
322+ reject ( new Error ( `Failed to initialize ${ typecheck . checker } . This is a bug in Vitest - please, open an issue with reproduction.` ) )
323323 return
324324 }
325- if ( this . _output . includes ( 'File change detected' ) && ! rerunTriggered ) {
326- this . _onWatcherRerun ?.( )
327- this . _startTime = performance . now ( )
328- this . _result . sourceErrors = [ ]
329- this . _result . files = [ ]
330- this . _tests = null // test structure might've changed
331- rerunTriggered = true
325+
326+ child . process . stdout . on ( 'data' , ( chunk ) => {
327+ dataReceived = true
328+ this . _output += chunk
329+ if ( ! watch ) {
330+ return
331+ }
332+ if ( this . _output . includes ( 'File change detected' ) && ! rerunTriggered ) {
333+ this . _onWatcherRerun ?.( )
334+ this . _startTime = performance . now ( )
335+ this . _result . sourceErrors = [ ]
336+ this . _result . files = [ ]
337+ this . _tests = null // test structure might've changed
338+ rerunTriggered = true
339+ }
340+ if ( / F o u n d \w + e r r o r s * . W a t c h i n g f o r / . test ( this . _output ) ) {
341+ rerunTriggered = false
342+ this . prepareResults ( this . _output ) . then ( ( result ) => {
343+ this . _result = result
344+ this . _onParseEnd ?.( result )
345+ } )
346+ this . _output = ''
347+ }
348+ } )
349+
350+ const timeout = setTimeout (
351+ ( ) => reject ( new Error ( `${ typecheck . checker } spawn timed out` ) ) ,
352+ this . project . config . typecheck . spawnTimeout ,
353+ )
354+
355+ function onError ( cause : Error ) {
356+ clearTimeout ( timeout )
357+ reject ( new Error ( 'Spawning typechecker failed - is typescript installed?' , { cause } ) )
332358 }
333- if ( / F o u n d \w + e r r o r s * . W a t c h i n g f o r / . test ( this . _output ) ) {
334- rerunTriggered = false
335- this . prepareResults ( this . _output ) . then ( ( result ) => {
336- this . _result = result
337- this . _onParseEnd ?.( result )
359+
360+ child . process . once ( 'spawn' , ( ) => {
361+ this . _onParseStart ?.( )
362+ child . process ?. off ( 'error' , onError )
363+ clearTimeout ( timeout )
364+ if ( process . platform === 'win32' ) {
365+ // on Windows, the process might be spawned but fail to start
366+ // we wait for a potential error here. if "close" event didn't trigger,
367+ // we resolve the promise
368+ setTimeout ( ( ) => {
369+ resolve ( { result : child } )
370+ } , 200 )
371+ }
372+ else {
373+ resolve ( { result : child } )
374+ }
375+ } )
376+
377+ if ( process . platform === 'win32' ) {
378+ child . process . once ( 'close' , ( code ) => {
379+ if ( code != null && code !== 0 && ! dataReceived ) {
380+ onError ( new Error ( `The ${ typecheck . checker } command exited with code ${ code } .` ) )
381+ }
338382 } )
339- this . _output = ''
340383 }
384+ child . process . once ( 'error' , onError )
341385 } )
386+ }
387+
388+ public async start ( ) : Promise < void > {
389+ if ( this . process ) {
390+ return
391+ }
392+
393+ const { watch } = this . project . config
394+ const { result : child } = await this . spawn ( )
395+
342396 if ( ! watch ) {
343397 await child
344398 this . _result = await this . prepareResults ( this . _output )
0 commit comments