Skip to content

Commit

Permalink
Update node-event-emitter to v3.0.0; expose ChildProcess events as …
Browse files Browse the repository at this point in the history
…`EventHandle`s (#10)

* Update event-emitter to v3.0.0

* Re-expose child process events via EventHandle
  • Loading branch information
JordanMartinez authored Jun 26, 2023
1 parent f82f394 commit a4c7520
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 115 deletions.
10 changes: 10 additions & 0 deletions packages.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,13 @@ let upstream =
sha256:7364c3a98d654844f0256256f6cc22a03f6a57f571cb757b452aeea5489695fe

in upstream
with node-event-emitter.version = "v3.0.0"
with node-event-emitter.dependencies =
[ "effect"
, "either"
, "functions"
, "maybe"
, "nullable"
, "prelude"
, "unsafe-coerce"
]
24 changes: 0 additions & 24 deletions src/Node/Library/Execa.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,30 +93,6 @@ export function stdout(cp) {
return cp.stdout;
}

export function onCloseImpl(cp, cb) {
return cp.on("close", cb);
}

export function onDisconnectImpl(cp, cb) {
return cp.on("disconnect", cb);
}

export function onErrorImpl(cp, cb) {
return cp.on("error", cb);
}

export function onExitImpl(cp, cb) {
return cp.on("exit", cb);
}

export function onMessageImpl(cp, cb) {
return cp.on("message", cb);
}

export function onSpawnImpl(cp, cb) {
return cp.on("spawn", cb);
}

const _undefined = undefined;
export { _undefined as undefined };

Expand Down
95 changes: 44 additions & 51 deletions src/Node/Library/Execa.purs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ module Node.Library.Execa
, ExecaOptions
, ExecaProcess
, ExecaSuccess
, ExecaChildProcess
, closeH
, disconnectH
, errorH
, exitH
, messageH
, spawnH
, KillSignal
, execa
, ExecaSyncOptions
, ExecaSyncResult
Expand Down Expand Up @@ -56,6 +64,8 @@ import Node.Buffer.Immutable (ImmutableBuffer)
import Node.Buffer.Immutable as ImmutableBuffer
import Node.Buffer.Internal as Buffer
import Node.Encoding (Encoding(..))
import Node.EventEmitter (EventHandle(..), once_)
import Node.EventEmitter.UtilTypes (EventHandle1, EventHandle0)
import Node.Library.Execa.CrossSpawn (CrossSpawnConfig)
import Node.Library.Execa.CrossSpawn as CrossSpawn
import Node.Library.Execa.GetStream (getStreamBuffer)
Expand Down Expand Up @@ -261,11 +271,7 @@ type ExecaProcess =
, killForcedWithSignal :: Either Int String -> Milliseconds -> Aff Boolean
, killWithSignal :: Either Int String -> Aff Boolean
, killed :: Aff Boolean
, onClose :: (Maybe Int -> Maybe String -> Effect Unit) -> Aff Unit
, onDisconnect :: Effect Unit -> Aff Unit
, onError :: (ChildProcessError -> Effect Unit) -> Aff Unit
, onMessage :: (Foreign -> Maybe Handle -> Effect Unit) -> Aff Unit
, onSpawn :: Effect Unit -> Aff Unit
, childProcess :: ExecaChildProcess
, pid :: Aff (Maybe Pid)
, pidExists :: Aff Boolean
, ref :: Aff Unit
Expand Down Expand Up @@ -303,6 +309,34 @@ type ExecaSuccess =
, stdout :: String
}

newtype ExecaChildProcess = ExecaChildProcess ChildProcess

closeH :: EventHandle ExecaChildProcess (Either Int String -> Effect Unit) (EffectFn2 (Nullable Int) (Nullable String) Unit)
closeH = EventHandle "close" \cb -> mkEffectFn2 \code sig -> do
case toMaybe code, toMaybe sig of
Just c, _ -> cb $ Left c
_, Just s -> cb $ Right s
_, _ -> unsafeCrashWith $ "Impossible. Got both an exit code and error signal. Code=" <> show code <> "; Signal=" <> show sig

disconnectH :: EventHandle0 ExecaChildProcess
disconnectH = EventHandle "disconnect" identity

errorH :: EventHandle1 ExecaChildProcess ChildProcessError
errorH = EventHandle "error" mkEffectFn1

exitH :: EventHandle ExecaChildProcess (Either Int (Either Int String) -> Effect Unit) (EffectFn2 (Nullable Int) (Nullable KillSignal) Unit)
exitH = EventHandle "exit" \cb -> mkEffectFn2 \code sig -> do
case toMaybe code, map fromKillSignal $ toMaybe sig of
Just c, _ -> cb $ Left c
_, Just s -> cb $ Right s
_, _ -> unsafeCrashWith $ "Impossible. Got neither or both an exit code and error signal. Code=" <> show code <> "; Signal=" <> (unsafeCoerce sig :: String)

messageH :: EventHandle ExecaChildProcess (Foreign -> Maybe Handle -> Effect Unit) (EffectFn2 Foreign (Nullable Handle) Unit)
messageH = EventHandle "message" \cb -> mkEffectFn2 \a b -> cb a (toMaybe b)

spawnH :: EventHandle0 ExecaChildProcess
spawnH = EventHandle "spawn" identity

-- | Replacement for `childProcess.spawn`. Since this is asynchronous,
-- | the returned value will not provide any results until one calls `spawned.result`:
-- | `execa ... >>= \spawned -> spawned.result`.
Expand Down Expand Up @@ -339,13 +373,11 @@ execa file args buildOptions = do
, windowsHide: options.windowsHide
}
spawnedFiber <- suspendAff $ makeAff \cb -> do
onExit spawned \e s -> do
case e, s of
Just i, _ -> cb $ Right $ ExitCode i
_, Just sig -> cb $ Right $ Killed $ fromKillSignal sig
_, _ -> unsafeCrashWith "Impossible: either exit code or signal code must be non-null"
(ExecaChildProcess spawned) # once_ exitH case _ of
Left i -> cb $ Right $ ExitCode i
Right sig -> cb $ Right $ Killed sig

onError spawned \error -> do
(ExecaChildProcess spawned) # once_ errorH \error -> do
cb $ Right $ SpawnError error

Streams.onError (stdin spawned) \error -> do
Expand Down Expand Up @@ -474,11 +506,7 @@ execa file args buildOptions = do
, signalCode: liftEffect $ signalCode spawned
, spawnArgs: spawnArgs spawned
, spawnFile: spawnFile spawned
, onClose: \cb -> liftEffect $ onClose spawned cb
, onDisconnect: \cb -> liftEffect $ onDisconnect spawned cb
, onError: \cb -> liftEffect $ onError spawned cb
, onMessage: \cb -> liftEffect $ onMessage spawned cb
, onSpawn: \cb -> liftEffect $ onSpawn spawned cb
, childProcess: ExecaChildProcess spawned
, stdin:
{ stream: stdin spawned
, writeUtf8: \string -> liftEffect do
Expand Down Expand Up @@ -1040,41 +1068,6 @@ foreign import stdout :: ChildProcess -> Readable ()
-- | The standard error stream of a child process.
foreign import stderr :: ChildProcess -> Readable ()

-- | Handle the `"close"` signal.
onClose :: ChildProcess -> (Maybe Int -> Maybe String -> Effect Unit) -> Effect Unit
onClose cp cb = runEffectFn2 onCloseImpl cp $ mkEffectFn2 \a b -> cb (toMaybe a) (toMaybe b)

foreign import onCloseImpl :: EffectFn2 (ChildProcess) (EffectFn2 (Nullable Int) (Nullable String) Unit) (Unit)

-- | Handle the `"disconnect"` signal.
onDisconnect :: ChildProcess -> Effect Unit -> Effect Unit
onDisconnect cp cb = runEffectFn2 onDisconnectImpl cp cb

foreign import onDisconnectImpl :: EffectFn2 (ChildProcess) (Effect Unit) (Unit)

-- | Handle the `"error"` signal.
onError :: ChildProcess -> (ChildProcessError -> Effect Unit) -> Effect Unit
onError cp cb = runEffectFn2 onErrorImpl cp $ mkEffectFn1 cb

foreign import onErrorImpl :: EffectFn2 (ChildProcess) (EffectFn1 ChildProcessError Unit) (Unit)

onExit :: ChildProcess -> (Maybe Int -> Maybe KillSignal -> Effect Unit) -> Effect Unit
onExit cp cb = runEffectFn2 onExitImpl cp $ mkEffectFn2 \e s ->
cb (toMaybe e) (toMaybe s)

foreign import onExitImpl :: EffectFn2 (ChildProcess) (EffectFn2 (Nullable Int) (Nullable KillSignal) Unit) (Unit)

-- | Handle the `"message"` signal.
onMessage :: ChildProcess -> (Foreign -> Maybe Handle -> Effect Unit) -> Effect Unit
onMessage cp cb = runEffectFn2 onMessageImpl cp $ mkEffectFn2 \a b -> cb a (toMaybe b)

foreign import onMessageImpl :: EffectFn2 (ChildProcess) (EffectFn2 Foreign (Nullable Handle) Unit) (Unit)

onSpawn :: ChildProcess -> Effect Unit -> Effect Unit
onSpawn cp cb = runEffectFn2 onSpawnImpl cp cb

foreign import onSpawnImpl :: EffectFn2 (ChildProcess) (Effect Unit) Unit

-- | either Int or String
foreign import data KillSignal :: Type

Expand Down
87 changes: 47 additions & 40 deletions src/Node/Library/Execa/SignalExit.purs
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,20 @@ import Data.Either (hush)
import Data.Foldable (traverse_)
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Monoid (guard)
import Data.Nullable (Nullable, notNull, null, toMaybe)
import Data.Nullable (Nullable, notNull, null, toMaybe, toNullable)
import Data.Posix (Pid)
import Data.Set (Set)
import Data.Set as Set
import Data.Symbol (reflectSymbol)
import Data.Traversable (for)
import Effect (Effect)
import Effect.Exception (try)
import Effect.Ref (Ref)
import Effect.Ref as Ref
import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, mkEffectFn1, mkEffectFn3, runEffectFn1, runEffectFn2, runEffectFn3)
import Node.EventEmitter (CanEmit, CanHandle, EventEmitter, unsafeEmit)
import Node.EventEmitter.TypedEmitter (TypedEmitter, emit, subscribe, withEmit)
import Node.EventEmitter.TypedEmitter as TypedEmitter
import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, mkEffectFn1, mkEffectFn2, mkEffectFn3, runEffectFn1, runEffectFn2, runEffectFn3)
import Node.EventEmitter (EventEmitter, EventHandle(..), on, unsafeEmitFn)
import Node.EventEmitter as EventEmitter
import Node.Platform (Platform(..))
import Node.Process as Process
import Type.Proxy (Proxy(..))
import Unsafe.Coerce (unsafeCoerce)

foreign import unsafeProcessHasProp :: EffectFn1 String Boolean
foreign import unsafeReadProcessProp :: forall a. EffectFn1 String a
Expand All @@ -54,8 +50,29 @@ type Options =
{ alwaysLast :: Boolean
}

_exit = Proxy :: Proxy "exit"
_afterexit = Proxy :: Proxy "afterexit"
newtype ExitEmitter = ExitEmitter EventEmitter

exitEvent :: String
exitEvent = "exit"

afterexitEvent :: String
afterexitEvent = "afterexit"

exitE :: Maybe Int -> Maybe String -> ExitEmitter -> Effect Unit
exitE code err (ExitEmitter emitter) =
void $ runEffectFn3 (unsafeEmitFn emitter) exitEvent (toNullable code) (toNullable err)

exitH :: EventHandle ExitEmitter (Maybe Int -> Maybe String -> Effect Unit) (EffectFn2 (Nullable Int) (Nullable String) Unit)
exitH = EventHandle exitEvent \cb -> mkEffectFn2 \code err ->
cb (toMaybe code) (toMaybe err)

afterexitE :: Maybe Int -> Maybe String -> ExitEmitter -> Effect Unit
afterexitE code err (ExitEmitter emitter) =
void $ runEffectFn3 (unsafeEmitFn emitter) afterexitEvent (toNullable code) (toNullable err)

afterexitH :: EventHandle ExitEmitter (Maybe Int -> Maybe String -> Effect Unit) (EffectFn2 (Nullable Int) (Nullable String) Unit)
afterexitH = EventHandle afterexitEvent \cb -> mkEffectFn2 \code err ->
cb (toMaybe code) (toMaybe err)

onExit :: (Maybe Int -> Maybe String -> Effect Unit) -> Effect (Effect Unit)
onExit cb = onExit' cb { alwaysLast: false }
Expand All @@ -69,25 +86,20 @@ onAfterExit cb = onExit' cb { alwaysLast: true }
-- in the same project.
onExit' :: (Maybe Int -> Maybe String -> Effect Unit) -> Options -> Effect (Effect Unit)
onExit' cb options = do
{ emitter } <- getGlobalRecOnProcessObject
{ emitter: exitEmitter@(ExitEmitter emitter) } <- getGlobalRecOnProcessObject
load
unSubscribe <-
if options.alwaysLast then do
emitter # subscribe _afterexit \exitCode sig ->
cb (toMaybe exitCode) (toMaybe sig)
exitEmitter # on afterexitH cb
else do
emitter # subscribe _exit \exitCode sig ->
cb (toMaybe exitCode) (toMaybe sig)
exitEmitter # on exitH cb
pure do
unSubscribe
exitLen <- TypedEmitter.listenersLength _exit emitter
afterExitLen <- TypedEmitter.listenersLength _afterexit emitter
exitLen <- EventEmitter.listenerCount emitter exitEvent
afterExitLen <- EventEmitter.listenerCount emitter afterexitEvent
when (exitLen == 0 && afterExitLen == 0) do
unload
where
toEmitter :: forall e h r. TypedEmitter e h r -> EventEmitter e h
toEmitter = unsafeCoerce

unload = do
{ loadedRef
, countRef
Expand All @@ -103,13 +115,13 @@ onExit' cb options = do
Ref.modify_ (_ - 1) countRef

emitFn = mkEffectFn3 \event code signal -> do
{ emitter
{ emitter: ExitEmitter emitter
, emittedEventsRef
} <- getGlobalRecOnProcessObject
eventsAlreadyEmitted <- Ref.read emittedEventsRef
unless (Set.member event eventsAlreadyEmitted) do
Ref.modify_ (Set.insert event) emittedEventsRef
map (\(_ :: Boolean) -> unit) $ unsafeEmit (toEmitter emitter) event code signal
map (\(_ :: Boolean) -> unit) $ (runEffectFn3 (unsafeEmitFn emitter) event code signal)

load = do
{ loadedRef
Expand Down Expand Up @@ -150,8 +162,8 @@ onExit' cb options = do
count <- Ref.read countRef
when (listenersLen == count) do
unload
runEffectFn3 emitFn (reflectSymbol _exit) null (notNull sig)
runEffectFn3 emitFn (reflectSymbol _afterexit) null (notNull sig)
runEffectFn3 emitFn exitEvent null (notNull sig)
runEffectFn3 emitFn afterexitEvent null (notNull sig)
-- "SIGHUP" throws an `ENOSYS` error on Windows,
-- so use a supported signal instead
let sig' = if isWin && sig == "SIGHUP" then "SIGINT" else sig
Expand All @@ -162,40 +174,35 @@ onExit' cb options = do
, originalProcessReallyExit
} <- getGlobalRecOnProcessObject
let exitCode = fromMaybe 0 $ toMaybe code
runEffectFn2 unsafeWriteProcessProp "exit" exitCode
void $ withEmit $ emit _exit emitter (notNull exitCode) null
void $ withEmit $ emit _afterexit emitter (notNull exitCode) null
runEffectFn2 unsafeWriteProcessProp exitEvent exitCode
emitter # exitE (Just exitCode) Nothing
emitter # afterexitE (Just exitCode) Nothing
runEffectFn2 processCallFn originalProcessReallyExit (notNull exitCode)

processEmitFn = customProcessEmit $ mkEffectFn3 \runOriginalProcessEmit ev arg -> do
{ originalProcessEmit } <- getGlobalRecOnProcessObject
if ev == (reflectSymbol _exit) then do
if ev == exitEvent then do
exitCode <- case toMaybe arg of
Nothing -> processExitCode
Just exitCode' -> do
runEffectFn2 unsafeWriteProcessProp "exit" exitCode'
runEffectFn2 unsafeWriteProcessProp exitEvent exitCode'
pure $ notNull exitCode'

ret <- runEffectFn1 runOriginalProcessEmit originalProcessEmit
runEffectFn3 emitFn (reflectSymbol _exit) exitCode null
runEffectFn3 emitFn (reflectSymbol _afterexit) exitCode null
runEffectFn3 emitFn exitEvent exitCode null
runEffectFn3 emitFn afterexitEvent exitCode null
pure ret
else do
runEffectFn1 runOriginalProcessEmit originalProcessEmit

signalExitProp :: String
signalExitProp = "__purescript_signal_exit__"

type EmitterRows =
( exit :: Nullable Int -> Nullable String -> Effect Unit
, afterexit :: Nullable Int -> Nullable String -> Effect Unit
)

type SignalEventRecord =
{ originalProcessEmit :: ProcessEmitFn
, originalProcessReallyExit :: ProcessReallyExitFn
, restoreOriginalProcessFunctions :: Effect Unit
, emitter :: TypedEmitter CanEmit CanHandle EmitterRows
, emitter :: ExitEmitter
, countRef :: Ref Int
, emittedEventsRef :: Ref (Set String)
, loadedRef :: Ref Boolean
Expand All @@ -217,8 +224,8 @@ getGlobalRecOnProcessObject =
runEffectFn2 unsafeWriteProcessProp "emit" originalProcessEmit
runEffectFn2 unsafeWriteProcessProp "reallyExit" originalProcessReallyExit

emitter <- TypedEmitter.new (Proxy :: Proxy EmitterRows)
TypedEmitter.setUnlimitedListeners emitter
emitter <- EventEmitter.new
EventEmitter.setUnlimitedListeners emitter
countRef <- Ref.new 0
emittedEventsRef <- Ref.new Set.empty
loadedRef <- Ref.new false
Expand All @@ -229,7 +236,7 @@ getGlobalRecOnProcessObject =
{ originalProcessEmit
, originalProcessReallyExit
, restoreOriginalProcessFunctions
, emitter
, emitter: ExitEmitter emitter
, countRef
, emittedEventsRef
, loadedRef
Expand Down

0 comments on commit a4c7520

Please sign in to comment.