defmodule Cmder do @moduledoc """ Cmder is a runner for generic commands. ## Profiles You can define multiple cmder profiles. config :cmder, tailwindcss: [ cmd: "tailwindcss" path: "/usr/bin", args: ~w(--input=css/app.css --output=../priv/static/assets/app.css --postcss), cd: Path.expand("../assets", __DIR__), env: %{"NODE_ENV" => "development"} ] If no cmd is given the profile name is being used as command. """ use Application require Logger @doc false def start(_, _) do Supervisor.start_link([], strategy: :one_for_one) end @doc """ Runs the given profile with `args`. The given args will be appended to the configured args. The task output will be streamed directly to stdio. It returns the status of the underlying call. """ def run(profile, extra_args) when is_atom(profile) and is_list(extra_args) do config = config_for!(profile) cmd = config[:cmd] || Atom.to_string(profile) args = config[:args] || [] opts = [ cd: config[:cd] || File.cwd!(), env: config[:env] || %{}, into: IO.stream(:stdio, :line), stderr_to_stdout: true ] cmd |> bin_path(config) |> System.cmd(args ++ extra_args, opts) |> elem(1) end defp config_for!(profile) when is_atom(profile) do Application.get_env(:cmder, profile) || raise ArgumentError, """ unknown cmder profile. Make sure the profile is defined in your config/config.exs file, such as: config :cmder, #{profile}: [ cmd: "#{profile}" path: "/usr/bin", args: ~w(--input=css/app.css --output=../priv/static/assets/app.css --postcss), cd: Path.expand("../assets", __DIR__), env: %{"NODE_ENV" => "development"} ] """ end defp bin_path(cmd, config) do path = config[:path] || "" Path.join(path, cmd) end end