@@ -42,7 +42,7 @@ import Foreign.Object (Object)
42
42
import Foreign.Object as Object
43
43
import Node.Buffer (Buffer )
44
44
import Node.Buffer as Buffer
45
- import Node.ChildProcess (ChildProcess , kill' , pid )
45
+ import Node.ChildProcess (ChildProcess )
46
46
import Node.ChildProcess as CP
47
47
import Node.ChildProcess.Aff (waitSpawned )
48
48
import Node.ChildProcess.Types (Exit (..), KillSignal , StdIO , customShell , fromKillSignal , fromKillSignal' , stringSignal )
@@ -62,6 +62,7 @@ import Node.Process as Process
62
62
import Node.Stream (Readable , Writable , destroy )
63
63
import Node.Stream as Stream
64
64
import Node.UnsafeChildProcess.Unsafe as Unsafe
65
+ import Partial.Unsafe (unsafeCrashWith )
65
66
import Record as Record
66
67
import Type.Proxy (Proxy (..))
67
68
import Unsafe.Coerce (unsafeCoerce )
@@ -321,12 +322,7 @@ execa file args buildOptions = do
321
322
}
322
323
)
323
324
stdinErrRef <- liftEffect $ Ref .new Nothing
324
- timeoutRef <- liftEffect $ Ref .new Nothing
325
325
canceledRef <- liftEffect $ Ref .new false
326
- clearKillOnTimeoutRef <- liftEffect $ Ref .new (mempty :: Effect Unit )
327
- let
328
- clearKillOnTimeout :: Effect Unit
329
- clearKillOnTimeout = join $ Ref .read clearKillOnTimeoutRef
330
326
spawnedFiber <- suspendAff $ waitSpawned spawned
331
327
332
328
processSpawnedFiber <- do
@@ -338,7 +334,7 @@ execa file args buildOptions = do
338
334
( do
339
335
liftEffect do
340
336
removal <- SignalExit .onExit \_ _ -> do
341
- void $ execaKill ( Just $ stringSignal " SIGTERM" ) Nothing spawned
337
+ void $ CP .kill' ( stringSignal " SIGTERM" ) spawned
342
338
Ref .write (Just removal) removeHandlerRef
343
339
joinFiber spawnedFiber
344
340
)
@@ -350,70 +346,61 @@ execa file args buildOptions = do
350
346
let
351
347
cancel :: Aff Unit
352
348
cancel = liftEffect do
353
- killSucceeded <- execaKill ( Just $ stringSignal " SIGTERM" ) Nothing spawned
349
+ killSucceeded <- CP .kill' ( stringSignal " SIGTERM" ) spawned
354
350
when killSucceeded do
355
351
Ref .write true canceledRef
356
352
357
- let
358
- processFinishedFiber :: Aff Exit
359
- processFinishedFiber = makeAff \done -> do
360
- spawned # once_ CP .exitH \exitResult -> do
361
- clearKillOnTimeout
362
- done $ Right exitResult
363
- pure nonCanceler
364
-
365
- let
366
- mkStdIoFiber
367
- :: Readable ()
368
- -> Aff (Fiber { text :: String , error :: Maybe Error } )
369
- mkStdIoFiber stream = forkAff do
370
- streamResult <- getStreamBuffer stream { maxBuffer: Just parsed.options.maxBuffer }
371
- text <- liftEffect do
372
- buf <- handleOutput { stripFinalNewline: parsed.options.stripFinalNewline } streamResult.buffer
373
- text <- Buffer .toString parsed.options.encoding buf
374
- when (isJust streamResult.inputError) do
375
- destroy stream
376
- pure text
377
- pure { text, error: streamResult.inputError }
378
-
379
353
let
380
354
mainFiber
381
355
:: Maybe (Pid -> Aff Unit )
382
- -> Aff _
356
+ -> Aff ExecaResult
383
357
mainFiber postSpawn = do
384
358
res <- joinFiber processSpawnedFiber
385
359
case res of
386
- Left err -> do
387
- exit <- processFinishedFiber
360
+ Left err -> liftEffect do
361
+ -- If the process fails to spawn, an `exit` event will not be emitted.
362
+ -- So, get that information via `exitCode`/`signalCode` and combine here.
363
+ let gotENOENT = SystemError .code err == " ENOENT"
364
+ unfixedExitCode' <- CP .exitCode spawned
365
+ signalCode' <- CP .signalCode spawned
388
366
let
389
- exitResult :: { exit :: Maybe Int , signal :: Maybe KillSignal }
390
- exitResult = case exit of
391
- Normally i -> { exit: Just i, signal: Nothing }
392
- BySignal s -> { exit: Nothing , signal: Just s }
393
- liftEffect do
394
- canceled <- Ref .read canceledRef
395
- killed' <- CP .killed spawned
396
- timeout <- Ref .read timeoutRef
397
- pure $
398
- mkExecaResult
399
- { spawnError: Just err
400
- , pid: Nothing
401
- , stdinErr: Nothing
402
- , stdoutErr: Nothing
403
- , stderrErr: Nothing
404
- , exitStatus: exit
405
- , exitCode: exitResult.exit
406
- , signal: exitResult.signal <|> timeout
407
- , stdout: " "
408
- , stderr: " "
409
- , command
410
- , escapedCommand
411
- , execaOptions: parsed.options
412
- , timedOut: false
413
- , canceled
414
- , killed: killed'
415
- }
367
+ exitCode' = case unfixedExitCode' of
368
+ Just _ | gotENOENT -> Just 127
369
+ x -> x
370
+
371
+ exitStatus :: Exit
372
+ exitStatus = case exitCode', signalCode' of
373
+ Just i, _ -> Normally i
374
+ _, Just s -> BySignal $ stringSignal s
375
+ _, _ -> unsafeCrashWith $ " Impossible: either exit or signal should be non-null"
376
+ canceled <- Ref .read canceledRef
377
+ killed' <- CP .killed spawned
378
+ pure $
379
+ mkExecaResult
380
+ { spawnError: Just err
381
+ , pid: Nothing
382
+ , stdinErr: Nothing
383
+ , stdoutErr: Nothing
384
+ , stderrErr: Nothing
385
+ , exitStatus
386
+ , exitCode: exitCode'
387
+ , signal: map stringSignal signalCode'
388
+ , stdout: " "
389
+ , stderr: " "
390
+ , command
391
+ , escapedCommand
392
+ , execaOptions: parsed.options
393
+ , timedOut: false
394
+ , canceled
395
+ , killed: killed'
396
+ }
416
397
Right pid -> do
398
+ timeoutRef <- liftEffect $ Ref .new Nothing
399
+ clearKillOnTimeoutRef <- liftEffect $ Ref .new (mempty :: Effect Unit )
400
+ let
401
+ clearKillOnTimeout :: Effect Unit
402
+ clearKillOnTimeout = join $ Ref .read clearKillOnTimeoutRef
403
+
417
404
-- Setup a timeout if there is one.
418
405
-- It'll be cleared when the process finishes.
419
406
void $ forkAff do
@@ -423,7 +410,7 @@ execa file args buildOptions = do
423
410
tid <- setTimeout ((unsafeCoerce :: Milliseconds -> Int ) milliseconds) do
424
411
killed' <- CP .killed spawned
425
412
unless killed' do
426
- void $ execaKill ( Just signal) Nothing spawned
413
+ void $ CP .kill' signal spawned
427
414
mbPid <- CP .pid spawned
428
415
for_ mbPid \_ -> do
429
416
-- stdin/out/err only exist if child process has spawned
@@ -445,13 +432,33 @@ execa file args buildOptions = do
445
432
-- allow end-user to use child process before code is finished.
446
433
for_ postSpawn \callback -> callback pid
447
434
435
+ processFinishedFiber :: Fiber Exit <- forkAff $ makeAff \done -> do
436
+ spawned # once_ CP .exitH \exitResult -> do
437
+ clearKillOnTimeout
438
+ done $ Right exitResult
439
+ pure nonCanceler
440
+
441
+ let
442
+ mkStdIoFiber
443
+ :: Readable ()
444
+ -> Aff (Fiber { text :: String , error :: Maybe Error } )
445
+ mkStdIoFiber stream = forkAff do
446
+ streamResult <- getStreamBuffer stream { maxBuffer: Just parsed.options.maxBuffer }
447
+ text <- liftEffect do
448
+ buf <- handleOutput { stripFinalNewline: parsed.options.stripFinalNewline } streamResult.buffer
449
+ text <- Buffer .toString parsed.options.encoding buf
450
+ when (isJust streamResult.inputError) do
451
+ destroy stream
452
+ pure text
453
+ pure { text, error: streamResult.inputError }
454
+
448
455
-- Setup fibers to get stdout/stderr
449
456
stdoutFiber <- mkStdIoFiber (CP .stdout spawned)
450
457
stderrFiber <- mkStdIoFiber (CP .stderr spawned)
451
458
452
459
-- now wait for the result
453
460
result <- sequential $ { exit: _, stdout: _, stderr: _ }
454
- <$> (parallel $ processFinishedFiber)
461
+ <$> (parallel $ joinFiber processFinishedFiber)
455
462
<*> (parallel $ joinFiber stdoutFiber)
456
463
<*> (parallel $ joinFiber stderrFiber)
457
464
@@ -529,7 +536,7 @@ execa file args buildOptions = do
529
536
void $ Stream .pipe (CP .stderr spawned) Process .stderr
530
537
}
531
538
, waitSpawned: do
532
- mbPid <- liftEffect $ pid spawned
539
+ mbPid <- liftEffect $ CP . pid spawned
533
540
case mbPid of
534
541
Just p -> pure $ Right p
535
542
Nothing -> waitSpawned spawned
@@ -714,15 +721,15 @@ execaKill
714
721
execaKill mbKillSignal forceKillAfterTimeout cp = do
715
722
let
716
723
killSignal = fromMaybe (stringSignal " SIGTERM" ) mbKillSignal
717
- killSignalSucceeded <- kill' killSignal cp
724
+ killSignalSucceeded <- CP . kill' killSignal cp
718
725
let
719
726
mbTimeout = do
720
727
guard $ isSigTerm killSignal
721
728
guard killSignalSucceeded
722
729
forceKillAfterTimeout
723
730
for_ mbTimeout \(Milliseconds timeout) -> do
724
731
t <- runEffectFn2 setTimeoutImpl (floor timeout) do
725
- void $ kill' (stringSignal " SIGKILL" ) cp
732
+ void $ CP . kill' (stringSignal " SIGKILL" ) cp
726
733
t.unref
727
734
pure killSignalSucceeded
728
735
where
0 commit comments