Skip to main content

lib/mix/tasks/recall.migrate.ex

defmodule Mix.Tasks.Recall.Migrate do
  @shortdoc "Runs the repository's pending Recall migrations"

  @moduledoc """
  Runs pending migrations for the given Recall-backed repository.

  The stock `mix ecto.migrate` tracks applied versions with a string-source
  `schema_migrations` query that this adapter refuses to serve; this task is the
  Recall equivalent, doing version bookkeeping through a schema-backed
  migrations table (see `Recall.Migrator`). It otherwise behaves like
  `ecto.migrate`.

      mix recall.migrate
      mix recall.migrate -r MyApp.Repo --step 2

  ## Options

    * `-r`, `--repo` — the repo to migrate (defaults to the app's configured repos)
    * `--all` — run all pending migrations (the default)
    * `--step` — run N pending migrations
    * `--to` — run up to and including a target version
    * `--migrations-path` — a migrations directory (may be given more than once)
    * `--log-level` — the `Logger` level for migration logs (default `info`)
  """

  use Mix.Task

  import Mix.Ecto

  @aliases [r: :repo, n: :step, v: :to]

  @switches [
    all: :boolean,
    step: :integer,
    to: :integer,
    repo: [:string, :keep],
    migrations_path: [:string, :keep],
    log_level: :string
  ]

  @impl true
  def run(args) do
    repos = parse_repo(args)
    {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)

    opts =
      opts
      |> normalize_log_level()
      |> normalize_paths()

    for repo <- repos do
      ensure_repo(repo, args)

      Ecto.Migrator.with_repo(repo, fn repo ->
        Recall.Migrator.run(repo, :up, opts)
      end)
    end
  end

  defp normalize_log_level(opts) do
    case Keyword.pop(opts, :log_level) do
      {nil, opts} -> opts
      {level, opts} -> Keyword.put(opts, :log, String.to_existing_atom(level))
    end
  end

  defp normalize_paths(opts) do
    case Keyword.get_values(opts, :migrations_path) do
      [] -> Keyword.delete(opts, :migrations_path)
      paths -> opts |> Keyword.delete(:migrations_path) |> Keyword.put(:migrations_paths, paths)
    end
  end
end