Adopt strict plugin entry module and otp_app conventions
Some checks failed
CI / Test (push) Failing after 35s

This commit is contained in:
2026-04-04 20:33:59 +02:00
parent 54d9bdd99c
commit ed0d4f9c0d
5 changed files with 30 additions and 8 deletions

View File

@@ -67,7 +67,7 @@ your_plugin/
```json ```json
{ {
"name": "your_plugin", "name": "your_plugin",
"entry_module": "YourPlugin.Plugin", "entry_module": "Tribes.Plugins.YourPlugin.Plugin",
"host_api": "1", "host_api": "1",
"otp_app": "your_plugin", "otp_app": "your_plugin",
"provides": ["some_capability@1"], "provides": ["some_capability@1"],
@@ -76,6 +76,8 @@ your_plugin/
} }
``` ```
- **entry_module** — must be `Tribes.Plugins.*.Plugin`
- **otp_app** — required and must match `name`
- **provides** — capabilities this plugin makes available - **provides** — capabilities this plugin makes available
- **requires** — hard dependencies (build fails without them) - **requires** — hard dependencies (build fails without them)
- **enhances_with** — optional dependencies (plugin degrades gracefully) - **enhances_with** — optional dependencies (plugin degrades gracefully)

View File

@@ -0,0 +1,5 @@
defmodule Tribes.Plugins.MyPlugin.Plugin do
@moduledoc false
defdelegate register(context), to: MyPlugin.Plugin
end

View File

@@ -2,7 +2,7 @@
"name": "my_plugin", "name": "my_plugin",
"version": "0.1.0", "version": "0.1.0",
"description": "TODO: Describe what this plugin does", "description": "TODO: Describe what this plugin does",
"entry_module": "MyPlugin.Plugin", "entry_module": "Tribes.Plugins.MyPlugin.Plugin",
"host_api": "1", "host_api": "1",
"otp_app": "my_plugin", "otp_app": "my_plugin",
"provides": [], "provides": [],

View File

@@ -8,7 +8,7 @@ defmodule MyPlugin.ContractTest do
defmodule MyPlugin.ContractTest do defmodule MyPlugin.ContractTest do
use Tribes.Plugin.ContractTest, use Tribes.Plugin.ContractTest,
plugin: MyPlugin.Plugin, plugin: Tribes.Plugins.MyPlugin.Plugin,
otp_app: :my_plugin otp_app: :my_plugin
end end
@@ -17,7 +17,7 @@ defmodule MyPlugin.ContractTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
@plugin MyPlugin.Plugin @plugin Tribes.Plugins.MyPlugin.Plugin
@manifest_path Path.join(__DIR__, "../manifest.json") |> Path.expand() @manifest_path Path.join(__DIR__, "../manifest.json") |> Path.expand()
setup_all do setup_all do

View File

@@ -15,7 +15,15 @@ defmodule MyPlugin.ManifestTest do
end end
test "has required fields", %{manifest: manifest} do test "has required fields", %{manifest: manifest} do
required = ["name", "version", "entry_module", "host_api", "provides", "requires"] required = [
"name",
"version",
"entry_module",
"host_api",
"otp_app",
"provides",
"requires"
]
for field <- required do for field <- required do
assert Map.has_key?(manifest, field), assert Map.has_key?(manifest, field),
@@ -25,12 +33,17 @@ defmodule MyPlugin.ManifestTest do
test "name matches OTP app name", %{manifest: manifest} do test "name matches OTP app name", %{manifest: manifest} do
assert manifest["name"] == "my_plugin" assert manifest["name"] == "my_plugin"
assert manifest["otp_app"] == manifest["name"]
end end
test "entry_module is a valid Elixir module name", %{manifest: manifest} do test "entry_module uses Tribes.Plugins namespace and Plugin suffix", %{manifest: manifest} do
module_name = manifest["entry_module"] module_name = manifest["entry_module"]
assert is_binary(module_name) assert is_binary(module_name)
assert String.starts_with?(module_name, "Elixir.") or not String.contains?(module_name, " ")
assert Regex.match?(
~r/^Tribes\.Plugins\.[A-Z][A-Za-z0-9_]*(\.[A-Z][A-Za-z0-9_]*)*\.Plugin$/,
module_name
)
end end
test "provides contains valid capability identifiers", %{manifest: manifest} do test "provides contains valid capability identifiers", %{manifest: manifest} do
@@ -53,7 +66,9 @@ defmodule MyPlugin.ManifestTest do
test "entry_module matches actual plugin module", %{manifest: manifest} do test "entry_module matches actual plugin module", %{manifest: manifest} do
module = String.to_atom("Elixir.#{manifest["entry_module"]}") module = String.to_atom("Elixir.#{manifest["entry_module"]}")
assert Code.ensure_loaded?(module), "entry_module #{manifest["entry_module"]} must be loadable"
assert Code.ensure_loaded?(module),
"entry_module #{manifest["entry_module"]} must be loadable"
end end
end end
end end