Files
tribes-plugin-new/lib/mix/tasks/tribes_plugin.install.ex
self 1bced15358 fix: generate host-backed plugin workflows
Update plugin-new templates and install task to use the host-backed plugin test/precommit aliases.

Keep framework dependencies under the host API foundation and add regression coverage for generated aliases.
2026-05-09 19:43:20 +02:00

254 lines
8.0 KiB
Elixir

if Code.ensure_loaded?(Igniter) and Code.ensure_loaded?(Igniter.Mix.Task) and
Code.ensure_loaded?(Igniter.Mix.Task.Info) do
defmodule Mix.Tasks.TribesPlugin.Install.Igniter do
@moduledoc """
Installs Tribes plugin files into the current Mix project.
This task is intended for converting a fresh `mix new` project or refreshing
missing generated files in an existing plugin project. Existing files are
preserved unless Igniter project patching is used for dependencies or aliases.
## Examples
mix tribes_plugin.install
mix tribes_plugin.install --config-demo
mix tribes_plugin.install --app billing_reports --module BillingReports
"""
use Igniter.Mix.Task
alias TribesPlugin.Naming
alias TribesPlugin.Options
alias TribesPlugin.Templates
alias Igniter.Code.Common
@shortdoc "Installs Tribes plugin files into the current Mix project"
@impl Igniter.Mix.Task
def info(_argv, _composing_task) do
%Igniter.Mix.Task.Info{
group: :tribes_plugin,
example: "mix tribes_plugin.install --config-demo",
positional: [],
schema: [
app: :string,
module: :string,
description: :string,
host_path: :string,
config_demo: :boolean,
no_live_view: :boolean,
no_assets: :boolean,
provides: :string,
requires: :string,
enhances_with: :string
],
aliases: [],
defaults: [
host_path: "../tribes",
config_demo: false,
no_live_view: false,
no_assets: false
]
}
end
@impl Igniter.Mix.Task
def igniter(igniter) do
opts = igniter.args.options
app = opts[:app] || current_app(igniter)
module = opts[:module] || current_module()
naming = Naming.resolve!(".", app: app, module: module)
generator_opts =
[
description: opts[:description],
repo_name: Path.basename(File.cwd!()),
host_path: opts[:host_path],
live_view?: !opts[:no_live_view],
assets?: !opts[:no_assets],
config_demo?: !!opts[:config_demo],
provides: Options.parse_capabilities(opts[:provides]),
requires: Options.parse_capabilities(opts[:requires]),
enhances_with: Options.parse_capabilities(opts[:enhances_with])
]
|> Enum.reject(fn {_key, value} -> is_nil(value) end)
igniter
|> create_missing_files(Templates.project_files(naming, generator_opts))
|> add_project_deps(opts[:host_path])
|> add_preferred_envs()
|> add_project_aliases()
|> add_alias_helpers()
end
defp create_missing_files(igniter, files) do
Enum.reduce(files, igniter, fn
{"mix.exs", _contents}, igniter ->
igniter
{path, contents}, igniter ->
Igniter.create_new_file(igniter, path, contents, on_exists: :skip)
end)
end
defp add_project_deps(igniter, host_path) do
host_path = host_path || "../tribes"
igniter
|> Igniter.Project.Deps.add_dep(
{:tribes_plugin_api, [path: "#{host_path}/tribes_plugin_api", runtime: false]},
on_exists: :skip
)
|> Igniter.Project.Deps.add_dep(
{:tribes_plugin, [path: "../tribes-plugin-new", only: [:dev, :test], runtime: false]},
on_exists: :skip
)
|> Igniter.Project.Deps.add_dep(
{:igniter, "~> 0.7", only: [:dev, :test], runtime: false},
on_exists: :skip
)
|> Igniter.Project.Deps.add_dep({:phoenix, "~> 1.8"}, on_exists: :skip)
|> Igniter.Project.Deps.add_dep({:phoenix_html, "~> 4.1"}, on_exists: :skip)
|> Igniter.Project.Deps.add_dep({:phoenix_live_view, "~> 1.1.0"}, on_exists: :skip)
|> Igniter.Project.Deps.add_dep(
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
on_exists: :skip
)
|> Igniter.Project.Deps.add_dep({:lazy_html, ">= 0.1.0", only: :test}, on_exists: :skip)
|> Igniter.Project.Deps.add_dep({:usage_rules, "~> 1.2", only: :dev}, on_exists: :skip)
end
defp add_preferred_envs(igniter) do
Enum.reduce([precommit: :test, raw_precommit: :test, raw_test: :test], igniter, fn
{task, env}, igniter ->
Igniter.Project.MixProject.update(igniter, :cli, [:preferred_envs, task], fn _ ->
{:ok, {:code, env}}
end)
end)
end
defp add_project_aliases(igniter) do
igniter
|> Igniter.Project.TaskAliases.add_alias(:test, {:code, quote(do: &plugin_test_message/1)})
|> replace_alias(:test, quote(do: &plugin_test_message/1))
|> Igniter.Project.TaskAliases.add_alias(:raw_test, {:code, quote(do: &raw_test/1)})
|> replace_alias(:raw_test, quote(do: &raw_test/1))
|> Igniter.Project.TaskAliases.add_alias(:lint, [
"format --check-formatted",
"credo"
])
|> Igniter.Project.TaskAliases.add_alias(
:precommit,
{:code, quote(do: &plugin_precommit_message/1)}
)
|> replace_alias(:precommit, quote(do: &plugin_precommit_message/1))
|> Igniter.Project.TaskAliases.add_alias(
:raw_precommit,
{:code, quote(do: &raw_precommit/1)}
)
|> replace_alias(:raw_precommit, quote(do: &raw_precommit/1))
end
defp replace_alias(igniter, name, quoted) do
Igniter.Project.TaskAliases.modify_existing_alias(igniter, name, fn zipper ->
{:ok, Common.replace_code(zipper, quoted)}
end)
end
defp add_alias_helpers(igniter) do
Igniter.update_file(igniter, "mix.exs", fn source ->
content = Rewrite.Source.get(source, :content)
if String.contains?(content, "defp plugin_test_message(") do
source
else
Rewrite.Source.update(source, :content, &insert_alias_helpers/1)
end
end)
end
defp insert_alias_helpers(content) do
helpers = """
defp plugin_test_message(_args) do
Mix.raise(\"\"\"
This plugin test suite is host-backed. Use `plugin test` or `scripts/plugin test`.
For low-level debugging only, set up the host environment yourself and run `mix raw_test`.
\"\"\")
end
defp plugin_precommit_message(_args) do
Mix.raise(\"\"\"
This plugin precommit is host-backed. Use `plugin precommit` or `scripts/plugin precommit`.
For low-level debugging only, set up the host environment yourself and run `mix raw_precommit`.
\"\"\")
end
defp raw_precommit(args) do
Mix.Task.run("format")
Mix.Task.run("compile", ["--warnings-as-errors"])
Mix.Task.run("credo", ["--strict", "--all"])
Mix.Task.run("deps.unlock", ["--unused"])
raw_test(args)
end
defp raw_test(args), do: Mix.Tasks.Test.run(args)
"""
String.replace_suffix(content, "\nend\n", helpers <> "\nend\n")
end
defp current_app(igniter) do
igniter
|> Igniter.Project.Application.app_name()
|> to_string()
end
defp current_module do
Mix.Project.get!()
|> Module.split()
|> Enum.drop(-1)
|> case do
[] -> nil
parts -> Module.concat(parts) |> inspect()
end
end
end
end
defmodule Mix.Tasks.TribesPlugin.Install do
@moduledoc """
Installs Tribes plugin files into the current Mix project.
"""
use Mix.Task
@shortdoc "Installs Tribes plugin files into the current Mix project"
@impl Mix.Task
def run(argv) do
Enum.each(
[TribesPlugin.Naming, TribesPlugin.Options, TribesPlugin.Templates],
&Code.ensure_loaded?/1
)
Mix.Task.run("deps.loadpaths", [])
if Code.ensure_loaded?(Mix.Tasks.TribesPlugin.Install.Igniter) and
Code.ensure_loaded?(Igniter.Mix.Task.Info) do
Mix.Tasks.TribesPlugin.Install.Igniter.run(argv)
else
Mix.shell().error("""
The task 'tribes_plugin.install' requires igniter. Please install igniter and try again.
For more information, see: https://hexdocs.pm/igniter/readme.html#installation
""")
exit({:shutdown, 1})
end
end
end