Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

Bugfixes:
* Preserve TTY properties for child processes in `spago run` (#1341)

## [1.0.3] - 2026-02-01

Bugfixes:
Expand Down
27 changes: 23 additions & 4 deletions src/Spago/Cmd.purs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ data StdinConfig
| StdinNewPipe
| StdinWrite String

-- | Controls how stdout/stderr are connected for child processes
data StdioConfig
= StdioInherit -- ^ Use parent's stream (preserves TTY properties)
| StdioPipe -- ^ Create new pipe (captures output but loses TTY)
Comment on lines +26 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this Haskell syntax? PureScript provides no docs for constructors so usually we put all of it in the block comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haskell syntax. I don't mind it 😄


derive instance Eq StdioConfig

type ExecResult =
{ canceled :: Boolean
, escapedCommand :: String
Expand Down Expand Up @@ -78,6 +85,8 @@ type ExecOptions =
{ pipeStdin :: StdinConfig
, pipeStdout :: Boolean
, pipeStderr :: Boolean
, stdoutMode :: StdioConfig
, stderrMode :: StdioConfig
, cwd :: Maybe GlobalPath
, shell :: Boolean
}
Expand All @@ -87,6 +96,8 @@ defaultExecOptions =
{ pipeStdin: StdinNewPipe
, pipeStdout: true
, pipeStderr: true
, stdoutMode: StdioPipe
, stderrMode: StdioPipe
, cwd: Nothing
, shell: false
}
Expand All @@ -98,11 +109,17 @@ spawn cmd args opts = liftAff do
StdinPipeParent -> Just inherit
StdinWrite _ -> Just pipe
StdinNewPipe -> Just pipe
stdoutOpt = case opts.stdoutMode of
StdioInherit -> Just inherit
StdioPipe -> Just pipe
stderrOpt = case opts.stderrMode of
StdioInherit -> Just inherit
StdioPipe -> Just pipe
subprocess <- Execa.execa cmd args _
{ cwd = Path.toRaw <$> opts.cwd
, stdin = stdinOpt
, stdout = Just pipe
, stderr = Just pipe
, stdout = stdoutOpt
, stderr = stderrOpt
, shell = case opts.shell of
-- TODO: execa doesn't support the boolean option yet
true -> Just (unsafeCoerce true)
Expand All @@ -113,9 +130,11 @@ spawn cmd args opts = liftAff do
StdinWrite s | Just { writeUtf8End } <- subprocess.stdin -> writeUtf8End s
_ -> pure unit

when (opts.pipeStderr) do
-- Note: pipeToParentStdout/Stderr only works when mode is StdioPipe
-- When StdioInherit, output goes directly to terminal (no capture needed)
when (opts.pipeStderr && opts.stderrMode == StdioPipe) do
traverse_ _.pipeToParentStderr subprocess.stderr
when (opts.pipeStdout) do
when (opts.pipeStdout && opts.stdoutMode == StdioPipe) do
traverse_ _.pipeToParentStdout subprocess.stdout

pure subprocess
Expand Down
7 changes: 6 additions & 1 deletion src/Spago/Command/Run.purs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ getNode = do
run :: forall a. Spago (RunEnv a) Unit
run = do
{ workspace, node, runOptions: opts, dependencies, selected, rootPath } <- ask
let execOptions = Cmd.defaultExecOptions { pipeStdin = Cmd.StdinPipeParent }
let
execOptions = Cmd.defaultExecOptions
{ pipeStdin = Cmd.StdinPipeParent
, stdoutMode = Cmd.StdioInherit -- Preserve TTY properties for child process
, stderrMode = Cmd.StdioInherit -- Preserve TTY properties for child process
}

case workspace.backend of
Nothing -> do
Expand Down
Loading