defmodule Arrea.CLI.Verify do
@moduledoc false
@doc """
Verifies that all runtime versions requested via --asdf-* and --mise-*
exist in their respective tool managers.
"""
@spec runtime_opts!(map() | keyword()) :: :ok | no_return()
def runtime_opts!(runtime_opts) do
Enum.each(runtime_opts, fn {key, version} ->
key_str = to_string(key)
cond do
String.starts_with?(key_str, "asdf_") ->
runtime = String.replace_prefix(key_str, "asdf_", "")
tool = find_asdf()
verify_asdf_version(tool, runtime, version)
String.starts_with?(key_str, "mise_") ->
runtime = String.replace_prefix(key_str, "mise_", "")
verify_mise_version!(runtime, version)
true ->
:ok
end
end)
end
defp find_asdf do
case System.find_executable("asdf") do
nil ->
case System.find_executable("mise") do
nil -> nil
_ -> :mise
end
_ ->
:asdf
end
end
defp verify_asdf_version(:asdf, runtime, version) do
case System.cmd("asdf", ["plugin", "list"], stderr_to_stdout: true) do
{output, 0} ->
plugins = output |> String.split("\n") |> Enum.map(&String.trim/1)
if runtime in plugins do
verify_asdf_version(runtime, version)
else
IO.puts(:stderr, "Error: asdf: plugin #{runtime} not installed")
IO.puts(:stderr, " Run: asdf plugin add #{runtime}")
System.halt(1)
end
{_output, _} ->
IO.puts(:stderr, "Error: asdf: command not found or not working")
System.halt(1)
end
rescue
_ ->
IO.puts(:stderr, "Error: asdf: command not found. Is asdf installed?")
System.halt(1)
end
defp verify_asdf_version(:mise, runtime, version) do
verify_mise_version!(runtime, version)
end
defp verify_asdf_version(nil, runtime, version) do
IO.puts(
:stderr,
"Error: --asdf-#{runtime} #{version} requires asdf or mise, but neither is installed"
)
System.halt(1)
end
defp verify_asdf_version(runtime, version) do
case System.cmd("asdf", ["list", runtime], stderr_to_stdout: true) do
{output, 0} ->
versions = output |> String.split("\n") |> Enum.map(&String.trim/1)
if version in versions do
:ok
else
IO.puts(:stderr, "Error: asdf: version #{version} not found for #{runtime}")
IO.puts(:stderr, " Available: #{Enum.join(versions, ", ")}")
System.halt(1)
end
{_output, _} ->
IO.puts(:stderr, "Error: asdf: no versions installed for #{runtime}")
System.halt(1)
end
end
defp verify_mise_version!(runtime, version) do
if System.find_executable("mise") == nil do
IO.puts(:stderr, "Error: mise is not installed. Please install mise or use asdf.")
System.halt(1)
end
case System.cmd("mise", ["ls", runtime, "--json"], stderr_to_stdout: true) do
{output, 0} ->
versions =
output
|> Jason.decode!()
|> Enum.map(fn %{"version" => v} -> v end)
if version in versions do
:ok
else
IO.puts(:stderr, "Error: mise: version #{version} not found for #{runtime}")
IO.puts(:stderr, " Available: #{Enum.join(versions, ", ")}")
System.halt(1)
end
{output, exit_code} ->
diagnose_mise_error(runtime, version, output, exit_code)
end
rescue
_ ->
IO.puts(:stderr, "Error: mise is not installed. Please install mise or use asdf.")
System.halt(1)
end
@spec diagnose_mise_error(String.t(), String.t(), String.t(), non_neg_integer()) :: no_return()
defp diagnose_mise_error(runtime, version, _output, _exit_code) do
case System.cmd("mise", ["ls"], stderr_to_stdout: true) do
{ls_output, 0} ->
cond do
String.contains?(ls_output, "plugin not installed") or
String.contains?(ls_output, "missing plugin") ->
IO.puts(:stderr, "Error: mise: plugin #{runtime} not installed")
IO.puts(:stderr, " Run: mise plugins install #{runtime}")
String.contains?(ls_output, runtime) ->
IO.puts(:stderr, "Error: mise: version #{version} not found for #{runtime}")
list_mise_versions(runtime)
true ->
IO.puts(:stderr, "Error: mise: plugin #{runtime} not installed")
IO.puts(:stderr, " Run: mise plugins install #{runtime}")
end
_ ->
IO.puts(:stderr, "Error: mise is not installed. Please install mise or use asdf.")
end
System.halt(1)
end
defp list_mise_versions(runtime) do
case System.cmd("mise", ["ls", runtime, "--json"], stderr_to_stdout: true) do
{output, 0} ->
versions =
output
|> Jason.decode!()
|> Enum.map(fn %{"version" => v} -> v end)
if versions != [] do
IO.puts(:stderr, " Available: #{Enum.join(versions, ", ")}")
end
_ ->
:ok
end
end
end