Skip to main content

lib/mix/tasks/astral.dev.ex

defmodule Mix.Tasks.Astral.Dev do
  @moduledoc """
  Start the Astral development server.

      mix astral.dev
      mix astral.dev --config astral.config.exs --port 4000
      mix astral.dev --open

  ## Options

    * `--config` - path to an Astral config file (default: `astral.config.exs` when present)
    * `--host` - host interface to bind (default: `localhost`)
    * `--port` - port to bind (default: `4000`)
    * `--open` - open the dev server in the default browser
  """

  @shortdoc "Start the Astral development server"

  use Mix.Task

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

    {parsed, _argv, _invalid} =
      OptionParser.parse(args,
        strict: [config: :string, host: :string, port: :integer, open: :boolean]
      )

    opts = dev_opts(parsed)
    {:ok, _pid} = Astral.Dev.start_link(opts)

    url = "http://#{opts[:host]}:#{opts[:port]}"
    Mix.shell().info("[Astral] Dev server running at #{url}")
    if Keyword.get(parsed, :open, false), do: open_browser(url)

    unless iex_running?() do
      Process.sleep(:infinity)
    end
  end

  defp dev_opts(parsed) do
    [
      config: Keyword.get(parsed, :config, default_config()),
      host: Keyword.get(parsed, :host, "localhost"),
      port: Keyword.get(parsed, :port, 4000)
    ]
    |> Enum.reject(fn {_key, value} -> is_nil(value) end)
  end

  defp default_config do
    if File.regular?("astral.config.exs"), do: "astral.config.exs"
  end

  defp open_browser(url) do
    case open_command() do
      nil -> Mix.shell().error("[Astral] Could not find a browser opener for #{url}")
      {command, args} -> System.cmd(command, args ++ [url], stderr_to_stdout: true)
    end
  end

  defp open_command do
    cond do
      System.find_executable("xdg-open") -> {"xdg-open", []}
      System.find_executable("open") -> {"open", []}
      System.find_executable("cmd") -> {"cmd", ["/c", "start", ""]}
      true -> nil
    end
  end

  @dialyzer {:nowarn_function, iex_running?: 0}
  defp iex_running? do
    Code.ensure_loaded?(IEx) and IEx.started?()
  end
end