defmodule Mix.Tasks.Agentix.Gen.Components do @shortdoc "Copies Agentix's chat components into your project to own and customize" @moduledoc """ Copies the default `Agentix.Chat` components into your project so you can edit the markup directly (an alternative to `import Agentix.Components`). mix agentix.gen.components lib/my_app_web/components mix agentix.gen.components lib/my_app_web/components --module MyAppWeb.AgentixComponents Writes one module file under the given directory. With no `--module`, the module is `AgentixComponents`; otherwise the file is named after the module's last segment. An existing file is left untouched (you own it after copying) unless you pass `--force`. """ use Mix.Task @template "priv/templates/components/agentix_components.ex" @impl Mix.Task def run(args) do {opts, paths} = OptionParser.parse!(args, strict: [module: :string, force: :boolean]) dir = path!(paths) module = validate_module!(opts[:module] || "AgentixComponents") contents = :agentix |> Application.app_dir(@template) |> File.read!() |> rename_module(module) File.mkdir_p!(dir) target = Path.join(dir, file_name(module)) if File.exists?(target) and not Keyword.get(opts, :force, false) do # The host owns and edits this file once copied — don't clobber their changes on a # re-run (e.g. after a library upgrade). Pass `--force` to overwrite. Mix.shell().info([:yellow, "* exists ", :reset, "#{target} (use --force to overwrite)"]) else File.write!(target, contents) Mix.shell().info([:green, "* creating ", :reset, target]) end target end defp path!([dir | _]), do: dir defp path!([]) do Mix.raise("expected a target directory: mix agentix.gen.components DIR [--module MODULE]") end # The module name is spliced into generated source, so reject anything that isn't a plain # dotted Elixir alias (no whitespace, no injected code). defp validate_module!(module) do if Regex.match?(~r/\A[A-Z][A-Za-z0-9_]*(\.[A-Z][A-Za-z0-9_]*)*\z/, module) do module else Mix.raise("invalid --module #{inspect(module)}: expected a name like MyAppWeb.Components") end end defp rename_module(contents, "AgentixComponents"), do: contents defp rename_module(contents, module), do: String.replace(contents, "defmodule AgentixComponents do", "defmodule #{module} do") defp file_name(module), do: (module |> String.split(".") |> List.last() |> Macro.underscore()) <> ".ex" end