defmodule Mix.Tasks.HostKit.Clean do @moduledoc """ Builds and optionally applies a conservative cleanup plan from HostKit release metadata. Cleanup uses the same target selection flags as `host_kit.plan` and the same confirmation model as `host_kit.apply`. Use `--dry-run` to inspect without deleting anything, or `--confirm` to apply the generated cleanup commands. """ use Mix.Task alias Mix.Tasks.HostKit.Options @shortdoc "Clean stale HostKit-managed release artifacts" @impl true def run(args) do Mix.Task.run("app.start") {opts, positional} = OptionParser.parse!(args, strict: [ local: :boolean, host: :string, remote: :string, user: :string, port: :integer, identity_file: :string, password: :string, password_env: :string, silently_accept_hosts: :boolean, sudo: :boolean, require: :keep, service: :keep, keep: :integer, dry_run: :boolean, confirm: :boolean, quiet: :boolean, verbose: :boolean ], aliases: [dry_run: :dry_run] ) path = List.first(positional) || "infra/config.exs" project = HostKit.load!(path, require: Keyword.get_values(opts, :require)) Options.with_target_opts(opts, project, fn target_opts -> case HostKit.clean(project, clean_opts(opts, target_opts)) do {:ok, plan} -> run_plan(plan, opts, target_opts) {:error, reason} -> Mix.raise("HostKit clean failed: #{inspect(reason)}") end end) end defp run_plan(plan, opts, target_opts) do cond do Keyword.get(opts, :dry_run, false) -> IO.puts(HostKit.format_plan(plan)) Keyword.get(opts, :confirm, false) -> case HostKit.apply(plan, apply_opts(opts, target_opts)) do {:ok, results} -> print_results(results) {:error, reason} -> Mix.raise("HostKit clean apply failed: #{inspect(reason)}") end true -> IO.puts(HostKit.format_plan(plan)) Mix.raise("pass --dry-run to inspect or --confirm to apply cleanup") end end defp clean_opts(opts, target_opts) do target_opts |> Options.expand_target_opts() |> put_present(:services, Options.selected_services(opts)) |> put_present(:keep, Keyword.get(opts, :keep)) end defp apply_opts(opts, target_opts) do target_opts |> Options.expand_target_opts() |> Keyword.merge(confirm: true, dry_run: false, track: false) |> put_present(:keep, Keyword.get(opts, :keep)) end defp print_results(results) do results |> Enum.map_join("\n", fn %{change: change, status: status} -> "#{status} #{HostKit.Plan.Format.format_change(change)}" end) |> IO.puts() end defp put_present(opts, _key, nil), do: opts defp put_present(opts, key, value), do: Keyword.put(opts, key, value) end