Skip to main content

lib/mix/tasks/agentix.gen.migration.ex

defmodule Mix.Tasks.Agentix.Gen.Migration do
  @shortdoc "Copies Agentix's Ecto migration into your repo's migrations directory"

  @moduledoc """
  Copies the Agentix tables migration into your repo so the Ecto/Postgres persistence
  adapter (`Agentix.Persistence.Ecto`) has its schema.

      mix agentix.gen.migration
      mix agentix.gen.migration priv/repo/migrations --repo MyApp.Repo

  Writes a timestamped `*_create_agentix_tables.exs` under the given directory (default
  `priv/repo/migrations`); then run `mix ecto.migrate`. The migration module is namespaced
  under your repo — inferred from the host app name, or set explicitly with `--repo`. The
  Ecto-backed suspension expiry additionally needs Oban's own migration (see Oban's docs).
  """

  use Mix.Task

  @template "priv/templates/migration/create_agentix_tables.exs"
  @default_dir Path.join(~w(priv repo migrations))

  @impl Mix.Task
  def run(args) do
    {opts, paths} = OptionParser.parse!(args, strict: [repo: :string])
    dir = List.first(paths) || @default_dir

    case Path.wildcard(Path.join(dir, "*_create_agentix_tables.exs")) do
      [existing | _] ->
        # The migration's module name is fixed, so a second copy (a re-run) would clash on
        # compile and break `mix ecto.migrate`. Skip rather than write a conflicting file.
        Mix.shell().info([:yellow, "* skipping ", :reset, "#{existing} already exists"])
        existing

      [] ->
        contents =
          :agentix
          |> Application.app_dir(@template)
          # Land the migration under the host's repo namespace, not Agentix's.
          |> File.read!()
          |> String.replace("Agentix.Repo.Migrations", "#{migrations_namespace(opts[:repo])}")

        File.mkdir_p!(dir)
        target = Path.join(dir, "#{timestamp()}_create_agentix_tables.exs")
        File.write!(target, contents)
        Mix.shell().info([:green, "* creating ", :reset, target])
        target
    end
  end

  # The host's migrations module namespace (e.g. `MyApp.Repo.Migrations`): from `--repo`,
  # else inferred from the host app name.
  defp migrations_namespace(nil) do
    app = Mix.Project.config()[:app] || :my_app
    "#{app |> to_string() |> Macro.camelize()}.Repo.Migrations"
  end

  defp migrations_namespace(repo), do: "#{repo}.Migrations"

  # Ecto's migration timestamp format: UTC, second resolution, zero-padded.
  defp timestamp, do: Calendar.strftime(DateTime.utc_now(), "%Y%m%d%H%M%S")
end