lib/mix/tasks/relyra.refresh_due.ex

defmodule Mix.Tasks.Relyra.RefreshDue do
  @moduledoc """
  Runs any due Phase 21 scheduled metadata refreshes once.

  Suitable for system cron, `kubectl run`, fly.io scheduled machines, and
  any other host scheduler that prefers a CLI invocation over an Oban
  worker. Adopters who use Oban add the documented Cron one-liner
  instead — see the README "Operations" section.

      mix relyra.refresh_due --repo MyApp.Repo

  Returns `:ok` on a clean tick (including the no-due-sources case, which
  emits the `[:relyra, :saml, :metadata, :auto_refresh, :skipped]` event).
  """
  @shortdoc "Refresh any metadata sources whose schedule is due."

  use Mix.Task

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

    {opts, _argv, _invalid} =
      OptionParser.parse(args,
        strict: [repo: :string],
        aliases: [r: :repo]
      )

    repo_string =
      Keyword.get(opts, :repo) ||
        Mix.raise("--repo is required: mix relyra.refresh_due --repo MyApp.Repo")

    repo =
      try do
        String.to_existing_atom(repo_string)
      rescue
        ArgumentError -> Mix.raise("Repo module #{repo_string} is not loaded")
      end

    case Relyra.Metadata.Scheduler.run_due(repo, []) do
      {:ok, results} when is_map(results) ->
        Mix.shell().info("relyra.refresh_due: #{map_size(results)} sources processed.")
        :ok

      other ->
        # Defensive fallback for the Ecto-absent compile lane (the
        # Scheduler stub returns `{:error, %Relyra.Error{}}`); the
        # present-Ecto body returns only `{:ok, _}` so the typer cannot
        # see this path.
        Mix.raise("relyra.refresh_due failed: " <> inspect(other))
    end
  end
end