Skip to main content

lib/mix/tasks/marea.ex

defmodule Mix.Tasks.Marea do
  @moduledoc """
  Run marea commands without building the escript: `mix marea run local`.

  Calls `Marea.main_inline/1` and, if the chain returns a deferred shell
  command, runs it via Port (forwarding stdin/stdout) so interactive
  commands like `iex -S mix run` work as expected.
  """
  @shortdoc "Run marea commands"

  use Mix.Task

  @impl true
  def run(args) do
    Mix.Task.run("app.start")

    case Marea.main_inline(args) do
      {:exec, cmd} ->
        Marea.Lib.print([:bright, "Exec: #{cmd}"])
        run_interactive(cmd)

      :ok ->
        :ok
    end
  end

  defp run_interactive(cmd) do
    port =
      Port.open(
        {:spawn, cmd},
        [:binary, :exit_status, :use_stdio, :stderr_to_stdout]
      )

    stdin_forwarder =
      spawn_link(fn -> forward_stdin(port) end)

    code = receive_loop(port)
    Process.exit(stdin_forwarder, :kill)

    if code != 0, do: System.halt(code)
  end

  defp receive_loop(port) do
    receive do
      {^port, {:data, data}} ->
        IO.write(data)
        receive_loop(port)

      {^port, {:exit_status, code}} ->
        code
    end
  end

  defp forward_stdin(port) do
    case IO.read(:stdio, :line) do
      :eof ->
        :ok

      {:error, _} ->
        :ok

      data ->
        Port.command(port, data)
        forward_stdin(port)
    end
  end
end