You've already forked tribes-plugin-new
feat: generate TribeOne plugin namespaces
Default generated plugins to TribeOne.TribesPlugin.* entry modules and remove the old Tribes.Plugins bridge module pattern from templates and tests.
This commit is contained in:
@@ -10,11 +10,11 @@ defmodule TribesPlugin.Naming do
|
||||
title: nil,
|
||||
css_class: nil,
|
||||
web_module: nil,
|
||||
bridge_module: nil
|
||||
entry_module: nil
|
||||
|
||||
def resolve(path, opts \\ []) do
|
||||
app = opts |> Keyword.get(:app) |> blank_to_nil() || derive_app(path)
|
||||
module = opts |> Keyword.get(:module) |> blank_to_nil() || Macro.camelize(app)
|
||||
module = opts |> Keyword.get(:module) |> blank_to_nil() |> normalize_module(app)
|
||||
|
||||
with :ok <- validate_app(app),
|
||||
:ok <- validate_module(module) do
|
||||
@@ -26,7 +26,7 @@ defmodule TribesPlugin.Naming do
|
||||
title: titleize(app),
|
||||
css_class: String.replace(app, "_", "-"),
|
||||
web_module: module <> "Web",
|
||||
bridge_module: "Tribes.Plugins." <> module <> ".Plugin"
|
||||
entry_module: module <> ".Plugin"
|
||||
}}
|
||||
end
|
||||
end
|
||||
@@ -84,6 +84,16 @@ defmodule TribesPlugin.Naming do
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_module(nil, app), do: "TribeOne.TribesPlugin." <> Macro.camelize(app)
|
||||
|
||||
defp normalize_module(module, _app) when is_binary(module) do
|
||||
if String.contains?(module, ".") do
|
||||
module
|
||||
else
|
||||
"TribeOne.TribesPlugin." <> module
|
||||
end
|
||||
end
|
||||
|
||||
defp blank_to_nil(nil), do: nil
|
||||
|
||||
defp blank_to_nil(value) when is_binary(value) do
|
||||
|
||||
@@ -52,7 +52,6 @@ defmodule TribesPlugin.Templates do
|
||||
"docs/plugin-contract.md" => plugin_contract(naming),
|
||||
"lib/#{naming.app}/application.ex" => application_module(naming),
|
||||
"lib/#{naming.app}/plugin.ex" => plugin_module(naming, opts),
|
||||
"lib/tribes/plugins/#{naming.app}/plugin.ex" => bridge_module(naming),
|
||||
"manifest.json" => manifest(naming, opts),
|
||||
"mix.exs" => mix_exs(naming, opts),
|
||||
"devenv.nix" => devenv_nix(opts),
|
||||
@@ -399,7 +398,7 @@ defmodule TribesPlugin.Templates do
|
||||
|
||||
## Plugin Contract
|
||||
|
||||
- `manifest.json` `entry_module` must point at the host bridge module in `lib/tribes/plugins/<plugin>/plugin.ex`.
|
||||
- `manifest.json` `entry_module` must point at the plugin entry module.
|
||||
- The dev `:tribes` dependency is compile-only so host plugin loading can build `_build/dev` beams without starting a nested host app.
|
||||
- The test `:tribes` dependency is runtime-enabled so host-backed tests can start `Tribes.Repo` and related services.
|
||||
- If the plugin adds cluster-synced Ash resources, use `AshNostrSync` deliberately and document replication semantics in `docs/plugin-contract.md`.
|
||||
@@ -558,7 +557,7 @@ defmodule TribesPlugin.Templates do
|
||||
```json
|
||||
{
|
||||
"name": "#{naming.app}",
|
||||
"entry_module": "#{naming.bridge_module}",
|
||||
"entry_module": "#{naming.entry_module}",
|
||||
"host_api": "1",
|
||||
"otp_app": "#{naming.app}",
|
||||
"provides": ["some_capability@1"],
|
||||
@@ -567,7 +566,7 @@ defmodule TribesPlugin.Templates do
|
||||
}
|
||||
```
|
||||
|
||||
- **entry_module** - must be `Tribes.Plugins.*.Plugin`
|
||||
- **entry_module** - must be a valid module ending in `.Plugin`
|
||||
- **otp_app** - required and must match `name`
|
||||
- **provides** - capabilities this plugin makes available
|
||||
- **requires** - hard plugin/API contracts (build fails without them)
|
||||
@@ -650,7 +649,7 @@ defmodule TribesPlugin.Templates do
|
||||
## After Generation
|
||||
|
||||
- Confirm `manifest.json` `name`, `otp_app`, and `entry_module`.
|
||||
- Confirm the bridge module path is `lib/tribes/plugins/<plugin>/plugin.ex`.
|
||||
- Confirm `manifest.json` `entry_module` matches your plugin module.
|
||||
- Run `mix deps.get`.
|
||||
- Run `scripts/plugin smoke`.
|
||||
- Run `scripts/plugin test`.
|
||||
@@ -734,7 +733,7 @@ defmodule TribesPlugin.Templates do
|
||||
`manifest.json` is the runtime contract consumed by Tribes:
|
||||
|
||||
- `name` and `otp_app` must match the Mix application name.
|
||||
- `entry_module` must be a loadable `Tribes.Plugins.*.Plugin` module.
|
||||
- `entry_module` must be a loadable module ending in `.Plugin`.
|
||||
- `provides` declares capabilities exported by the plugin.
|
||||
- `requires` declares hard plugin/API contracts beyond the `host_api` foundation.
|
||||
- `enhances_with` declares optional host capabilities.
|
||||
@@ -751,16 +750,17 @@ defmodule TribesPlugin.Templates do
|
||||
|
||||
## Entry Modules
|
||||
|
||||
The plugin uses a thin host bridge:
|
||||
The plugin entry module is:
|
||||
|
||||
```elixir
|
||||
defmodule #{naming.bridge_module} do
|
||||
defdelegate register(context), to: #{naming.module}.Plugin
|
||||
defmodule #{naming.entry_module} do
|
||||
use Tribes.Plugin.Base, otp_app: :#{naming.app}
|
||||
end
|
||||
```
|
||||
|
||||
Keep plugin implementation code in `#{naming.module}.Plugin`. Keep the bridge
|
||||
stable so the host plugin manager can load it from the plugin build output.
|
||||
Keep plugin implementation code under your own namespace. The default
|
||||
generator uses `TribeOne.TribesPlugin.*`; third-party plugins should use their own
|
||||
organization or project namespace.
|
||||
|
||||
## Host Dependencies
|
||||
|
||||
@@ -896,16 +896,6 @@ defmodule TribesPlugin.Templates do
|
||||
"""
|
||||
end
|
||||
|
||||
defp bridge_module(naming) do
|
||||
"""
|
||||
defmodule #{naming.bridge_module} do
|
||||
@moduledoc false
|
||||
|
||||
defdelegate register(context), to: #{naming.module}.Plugin
|
||||
end
|
||||
"""
|
||||
end
|
||||
|
||||
defp manifest(naming, opts) do
|
||||
assets = manifest_assets(opts[:assets?], naming)
|
||||
|
||||
@@ -914,7 +904,7 @@ defmodule TribesPlugin.Templates do
|
||||
"name": "#{naming.app}",
|
||||
"version": "0.1.0",
|
||||
"description": #{JSON.encode!(opts[:description])},
|
||||
"entry_module": "#{naming.bridge_module}",
|
||||
"entry_module": "#{naming.entry_module}",
|
||||
"host_api": "1",
|
||||
"otp_app": "#{naming.app}",
|
||||
"provides": #{json_array(opts[:provides])},
|
||||
@@ -1178,7 +1168,7 @@ defmodule TribesPlugin.Templates do
|
||||
defp contract_test(naming) do
|
||||
"""
|
||||
defmodule #{naming.module}.PluginContractTest do
|
||||
use Tribes.PluginTest.ContractTest, plugin: #{naming.bridge_module}
|
||||
use Tribes.PluginTest.ContractTest, plugin: #{naming.entry_module}
|
||||
end
|
||||
"""
|
||||
end
|
||||
@@ -1227,7 +1217,7 @@ defmodule TribesPlugin.Templates do
|
||||
defp home_page_test(naming) do
|
||||
"""
|
||||
defmodule #{naming.module}.HomePageTest do
|
||||
use Tribes.PluginTest.PageCase, plugin: #{naming.bridge_module}
|
||||
use Tribes.PluginTest.PageCase, plugin: #{naming.entry_module}
|
||||
|
||||
test "renders the plugin home page for signed-out visitors", %{conn: conn} do
|
||||
{:ok, view, html} = live(conn, "#{naming.path}")
|
||||
@@ -1281,7 +1271,7 @@ defmodule TribesPlugin.Templates do
|
||||
|
||||
"""
|
||||
defmodule #{naming.module}.#{page_module}PageTest do
|
||||
use Tribes.PluginTest.PageCase, plugin: #{naming.bridge_module}
|
||||
use Tribes.PluginTest.PageCase, plugin: #{naming.entry_module}
|
||||
|
||||
test "renders #{page_path}", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, "#{naming.path}/#{page_path}")
|
||||
|
||||
@@ -7,9 +7,9 @@ defmodule TribesPlugin.NamingTest do
|
||||
assert {:ok, naming} = Naming.resolve("../tribes-plugin-billing")
|
||||
|
||||
assert naming.app == "billing"
|
||||
assert naming.module == "Billing"
|
||||
assert naming.module == "TribeOne.TribesPlugin.Billing"
|
||||
assert naming.path == "/billing"
|
||||
assert naming.bridge_module == "Tribes.Plugins.Billing.Plugin"
|
||||
assert naming.entry_module == "TribeOne.TribesPlugin.Billing.Plugin"
|
||||
end
|
||||
|
||||
test "accepts explicit app and module names" do
|
||||
@@ -17,7 +17,7 @@ defmodule TribesPlugin.NamingTest do
|
||||
Naming.resolve("../ignored", app: "billing_reports", module: "BillingReports")
|
||||
|
||||
assert naming.app == "billing_reports"
|
||||
assert naming.module == "BillingReports"
|
||||
assert naming.module == "TribeOne.TribesPlugin.BillingReports"
|
||||
assert naming.css_class == "billing-reports"
|
||||
end
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule TribesPlugin.TemplatesTest do
|
||||
manifest = JSON.decode!(files["manifest.json"])
|
||||
|
||||
assert manifest["name"] == "billing_reports"
|
||||
assert manifest["entry_module"] == "Tribes.Plugins.BillingReports.Plugin"
|
||||
assert manifest["entry_module"] == "TribeOne.TribesPlugin.BillingReports.Plugin"
|
||||
assert manifest["requires"] == ["ui@1"]
|
||||
assert Map.has_key?(files, "lib/billing_reports_web/live/home_live.ex")
|
||||
assert files["scripts/plugin"] =~ "plugin smoke"
|
||||
|
||||
Reference in New Issue
Block a user