defmodule Mix.Tasks.HawkEx.Install do use Mix.Task @shortdoc "Installs HawkEx migrations and prints configuration instructions" @moduledoc """ Installs HawkEx migrations into a Phoenix application. mix hawk_ex.install This task: 1. Copies HawkEx migration templates into `priv/repo/migrations/`. 2. Prints the configuration block to add to `config/config.exs`. 3. Prints the next steps required to finish setup. ## Options * `--repo` - repo module used when naming migration modules. Defaults to the current Mix application name plus `.Repo`. ## Example mix hawk_ex.install mix hawk_ex.install --repo MyApp.Repo """ @template_dir Path.join(:code.priv_dir(:hawk_ex), "templates/migrations") @migrations_dir "priv/repo/migrations" def run(args) do {opts, _, _} = OptionParser.parse(args, strict: [repo: :string]) repo_module = Keyword.get(opts, :repo, detect_repo()) Mix.shell().info(""" Installing HawkEx... """) copy_migrations(repo_module) print_config_instructions(repo_module) print_next_steps() end # ---Private------------------------------------------------------------- defp copy_migrations(repo_module) do File.mkdir_p!(@migrations_dir) templates() |> Enum.with_index() |> Enum.each(fn {template_path, index} -> filename = build_filename(template_path, index) destination = Path.join(@migrations_dir, filename) if File.exists?(destination) do Mix.shell().info(" [skip] #{destination} already exists") else migration_module = build_migration_module(repo_module, filename) content = EEx.eval_file(template_path, migration_module: migration_module) File.write!(destination, content) Mix.shell().info(" [ok] #{destination}") end end) end defp templates do Path.wildcard(Path.join(@template_dir, "*.exs.eex")) end defp build_filename(template_path, index) do base = template_path |> Path.basename(".eex") |> Path.rootname() "#{timestamp(index)}_#{base}.exs" end defp timestamp(offset) do {{y, m, d}, {hh, mm, ss}} = :calendar.universal_time() total_seconds = ss + offset "#{y}#{pad(m)}#{pad(d)}#{pad(hh)}#{pad(mm)}#{pad(total_seconds)}" end defp build_migration_module(repo_module, filename) do # "20240101000001_create_hawk_ex_billing_tables.exs" # → "CreateHawkExBillingTables" migration_suffix = filename # drop .exs |> Path.rootname() # drop timestamp prefix |> String.replace(~r/^\d+_/, "") |> Macro.camelize() "#{repo_module}.Migrations.#{migration_suffix}" end defp print_config_instructions(repo_module) do Mix.shell().info(""" Add this to your config/config.exs: config :hawk_ex, repo: #{repo_module}, account_schema: MyApp.Accounts.Organization Replace MyApp.Accounts.Organization with your actual account schema. """) end defp print_next_steps do Mix.shell().info(""" Next steps: [ ] Update account_schema in config/config.exs [ ] Run mix ecto.migrate [ ] Call HawkEx.Billing.subscribe(account, :free) on user signup Documentation: https://hexdocs.pm/hawk_ex """) end defp detect_repo do Mix.Project.config() |> Keyword.get(:app) |> case do nil -> "MyApp.Repo" app -> app |> to_string() |> Macro.camelize() |> then(&"#{&1}.Repo") end end defp pad(n), do: String.pad_leading(to_string(n), 2, "0") end