From 63e10ad5bc0130869735f69f5009e3dc1af7e5fe Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Wed, 8 Apr 2026 13:11:39 +0200 Subject: [PATCH] Align aether plugin entrypoint and docs --- .gitignore | 2 + README.md | 125 ++++++++++++-------------- lib/aether/plugin.ex | 34 ------- lib/tribes/plugins/aether/plugin.ex | 33 ++++++- manifest.json | 2 +- test/aether/host_integration_test.exs | 2 +- test/aether/plugin_test.exs | 4 +- 7 files changed, 96 insertions(+), 106 deletions(-) delete mode 100644 lib/aether/plugin.ex diff --git a/.gitignore b/.gitignore index 2a5eb8c..bbbae40 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ # Node /node_modules/ /assets/node_modules/ +/priv/static/* +!/priv/static/.gitkeep # Generated on crash erl_crash.dump diff --git a/README.md b/README.md index c34bce7..7f28587 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,11 @@ -# Tribes Plugin Template +# Aether -Template for creating [Tribes](https://github.com/your-org/tribes) plugins. +`aether` is an external Tribes plugin that provides the `aether@1` +capability. ## Getting Started -1. Click **"Use this template"** on GitHub to create your own repo -2. Clone and rename: - -```bash -git clone https://github.com/you/your-plugin.git -cd your-plugin -./scripts/rename.sh your_plugin YourPlugin -``` - -3. Edit `manifest.json` — set description, capabilities, requirements -4. Implement your plugin in `lib/your_plugin/plugin.ex` -5. Run tests: +Install deps and run the test suite: ```bash mix deps.get @@ -27,85 +17,86 @@ mix test For local development alongside a Tribes checkout: ```bash -# Symlink into the host plugins directory +# Symlink into the host plugins directory once cd /path/to/tribes -ln -s /path/to/your-plugin plugins/your_plugin +ln -s /path/to/tribes-plugin-aether plugins/aether -# Start Tribes dev server — your plugin loads automatically +# Start the Tribes dev server iex --sname dev -S mix phx.server ``` -Edit your plugin source. Phoenix code reloader picks up changes. +Then edit `aether` in its own repo. In development, the Tribes host watches +symlinked external plugins and automatically: + +- runs `mix compile` for Elixir, HEEx, manifest, and migration changes +- runs `npm run build --prefix assets` +- reloads the plugin in the running Tribes VM +- triggers a Phoenix browser reload after the rebuild finishes + +That means the normal loop is: + +```bash +cd /path/to/tribes-plugin-aether +mix deps.get +mix test + +# in another terminal +cd /path/to/tribes +iex --sname dev -S mix phx.server +``` ## Project Structure ``` -your_plugin/ -├── manifest.json # Plugin metadata (Nix build + runtime) -├── mix.exs # Dependencies +aether/ +├── manifest.json +├── mix.exs ├── lib/ -│ ├── your_plugin/ -│ │ ├── plugin.ex # Tribes.Plugin entry point -│ │ └── application.ex # OTP supervision tree (optional) -│ └── your_plugin_web/ -│ └── live/ # LiveView pages -├── assets/ # JS/CSS (one bundle per plugin) +│ ├── aether/ +│ │ ├── application.ex +│ │ └── plugin.ex +│ └── aether_web/ +│ └── live/ +├── assets/ +│ ├── css/aether.css +│ ├── js/aether.js +│ ├── package.json +│ └── package-lock.json ├── priv/ -│ ├── static/ # Built assets for release -│ └── repo/migrations/ # Ecto migrations └── test/ ``` ## Manifest -`manifest.json` declares your plugin's identity and capabilities: +`manifest.json` declares the plugin contract: ```json { - "name": "your_plugin", - "provides": ["some_capability@1"], - "requires": ["ecto@1"], - "enhances_with": ["inference@1"] + "name": "aether", + "version": "0.1.0", + "entry_module": "Tribes.Plugins.Aether.Plugin", + "host_api": "1", + "otp_app": "aether", + "provides": ["aether@1"], + "requires": ["ecto@1", "phoenix@1"], + "enhances_with": [], + "assets": { + "global_js": ["aether.js"], + "global_css": ["aether.css"] + } } ``` -- **provides** — capabilities this plugin makes available -- **requires** — hard dependencies (build fails without them) -- **enhances_with** — optional dependencies (plugin degrades gracefully) - -See the [Plugin System docs](https://github.com/your-org/tribes/blob/master/docs/PLUGINS.md) for the full specification. - -## Testing - -Three test levels: - -- **Unit tests** (`test/your_plugin/`) — plugin logic in isolation -- **Manifest tests** (`test/your_plugin/manifest_test.exs`) — manifest schema validation -- **Contract tests** (`test/contract_test.exs`) — runtime spec matches manifest - -Run all: `mix test` - -For DB setup/migrations in local development, run: - -```bash -mix tribes.migrate -``` - -This runs Tribes + Parrhesia + plugin migrations via `Tribes.Release`. +See the host plugin docs in [PLUGINS.md](https://github.com/your-org/tribes/blob/master/docs/PLUGINS.md) +for the full manifest and runtime contract. ## Building for Release ```bash MIX_ENV=prod mix compile - -mkdir -p dist/your_plugin -cp -r _build/prod/lib/your_plugin/ebin dist/your_plugin/ -cp -r priv dist/your_plugin/ -cp manifest.json dist/your_plugin/ +npm run build --prefix assets ``` -For Nix-based deployment, add your plugin to the host's `plugins.json`. - -## Licence - -TODO: Choose a licence. +Guix packaging assembles the plugin artifact from the compiled BEAM output, +`priv/`, and `manifest.json`. Enable it from the `guix-tribes` channel-side node +configuration. diff --git a/lib/aether/plugin.ex b/lib/aether/plugin.ex deleted file mode 100644 index dee32e6..0000000 --- a/lib/aether/plugin.ex +++ /dev/null @@ -1,34 +0,0 @@ -defmodule Aether.Plugin do - @moduledoc """ - Tribes plugin entry point. - - Implements the `Tribes.Plugin` behaviour. This module is referenced by - `entry_module` in manifest.json and is called by the host's PluginManager - during startup. - """ - - use Tribes.Plugin.Base, otp_app: :aether - - @impl true - def register(context) do - super(context) - |> Map.merge(%{ - nav_items: [ - %{ - label: "Aether", - path: "/aether", - icon: nil, - requires: [], - order: 50 - } - ], - pages: [ - %{ - path: "/aether", - live_view: AetherWeb.TimelineLive, - layout: nil - } - ], - }) - end -end diff --git a/lib/tribes/plugins/aether/plugin.ex b/lib/tribes/plugins/aether/plugin.ex index e1189f7..c54420c 100644 --- a/lib/tribes/plugins/aether/plugin.ex +++ b/lib/tribes/plugins/aether/plugin.ex @@ -1,5 +1,34 @@ defmodule Tribes.Plugins.Aether.Plugin do - @moduledoc false + @moduledoc """ + Tribes plugin entry point. - defdelegate register(context), to: Aether.Plugin + Implements the `Tribes.Plugin` behaviour. This module is referenced by + `entry_module` in manifest.json and is called by the host's PluginManager + during startup. + """ + + use Tribes.Plugin.Base, otp_app: :aether + + @impl true + def register(context) do + super(context) + |> Map.merge(%{ + nav_items: [ + %{ + label: "Aether", + path: "/aether", + icon: nil, + requires: [], + order: 50 + } + ], + pages: [ + %{ + path: "/aether", + live_view: AetherWeb.TimelineLive, + layout: nil + } + ] + }) + end end diff --git a/manifest.json b/manifest.json index bb2c819..108768e 100644 --- a/manifest.json +++ b/manifest.json @@ -5,7 +5,7 @@ "entry_module": "Tribes.Plugins.Aether.Plugin", "host_api": "1", "otp_app": "aether", - "provides": ["timeline_ui@1"], + "provides": ["aether@1"], "requires": ["ecto@1", "phoenix@1"], "enhances_with": [], "assets": { diff --git a/test/aether/host_integration_test.exs b/test/aether/host_integration_test.exs index c2ca236..2e0fc94 100644 --- a/test/aether/host_integration_test.exs +++ b/test/aether/host_integration_test.exs @@ -17,7 +17,7 @@ if Code.ensure_loaded?(Tribes.PluginRegistry) and Code.ensure_loaded?(TribesWeb. test "registers timeline capability and /aether route with host", %{spec: spec} do assert spec.name == "aether" - assert %{name: "aether"} = Tribes.PluginRegistry.provider!("timeline_ui@1") + assert %{name: "aether"} = Tribes.PluginRegistry.provider!("aether@1") assert {:ok, "aether", %{path: "/aether"}} = Tribes.PluginRegistry.page_for_path("/aether/tribe-123") diff --git a/test/aether/plugin_test.exs b/test/aether/plugin_test.exs index 0dd1775..8cd4f70 100644 --- a/test/aether/plugin_test.exs +++ b/test/aether/plugin_test.exs @@ -1,10 +1,12 @@ defmodule Aether.PluginTest do use ExUnit.Case, async: true + @plugin Tribes.Plugins.Aether.Plugin + describe "register/1" do setup do context = %{pubsub: nil, repo: nil} - %{spec: Aether.Plugin.register(context)} + %{spec: @plugin.register(context)} end test "returns plugin name and version", %{spec: spec} do