You've already forked tribes-plugin-supertest
forked from tribes/tribes-plugin-template
Update plugin template for host-driven dev reload
This commit is contained in:
@@ -27,11 +27,7 @@ mix test
|
||||
For local development alongside a Tribes checkout:
|
||||
|
||||
```bash
|
||||
# Build plugin code once (host loads BEAM from _build/dev/lib/<otp_app>/ebin)
|
||||
cd /path/to/your-plugin
|
||||
mix compile
|
||||
|
||||
# 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
|
||||
|
||||
@@ -39,7 +35,30 @@ ln -s /path/to/your-plugin plugins/your_plugin
|
||||
iex --sname dev -S mix phx.server
|
||||
```
|
||||
|
||||
When you change plugin Elixir code, re-run `mix compile` in the plugin repo.
|
||||
Then edit the plugin in its own repo. In development, the host now watches
|
||||
symlinked external plugins and automatically:
|
||||
|
||||
- runs `mix compile` for Elixir/HEEx or manifest changes
|
||||
- runs `npm run build --prefix assets` when `assets/package.json` is present
|
||||
- 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/your-plugin
|
||||
mix deps.get
|
||||
mix test
|
||||
|
||||
# in another terminal
|
||||
cd /path/to/tribes
|
||||
iex --sname dev -S mix phx.server
|
||||
```
|
||||
|
||||
If your plugin does not need a custom frontend pipeline, you can skip
|
||||
`assets/package.json` and write browser-ready files directly under `assets/js`
|
||||
and `assets/css`; the host dev watcher will copy them into `priv/static` for
|
||||
you in development.
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -54,6 +73,8 @@ your_plugin/
|
||||
│ └── your_plugin_web/
|
||||
│ └── live/ # LiveView pages
|
||||
├── assets/ # JS/CSS (one bundle per plugin)
|
||||
│ ├── package.json # Optional build script used by dev + Guix packaging
|
||||
│ └── package-lock.json # Optional, recommended for reproducible builds
|
||||
├── priv/
|
||||
│ ├── static/ # Built assets for release
|
||||
│ └── repo/migrations/ # Ecto migrations
|
||||
@@ -106,6 +127,7 @@ This runs Tribes + Parrhesia + plugin migrations via `Tribes.Release`.
|
||||
|
||||
```bash
|
||||
MIX_ENV=prod mix compile
|
||||
npm run build --prefix assets
|
||||
|
||||
mkdir -p dist/your_plugin
|
||||
cp -r _build/prod/lib/your_plugin/ebin dist/your_plugin/
|
||||
@@ -113,7 +135,8 @@ cp -r priv dist/your_plugin/
|
||||
cp manifest.json dist/your_plugin/
|
||||
```
|
||||
|
||||
For Nix-based deployment, add your plugin to the host's `plugins.json`.
|
||||
For Guix-based deployment, package your plugin in the `guix-tribes` channel and
|
||||
enable it from the node config.
|
||||
|
||||
## Licence
|
||||
|
||||
|
||||
Generated
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "my-plugin-assets",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "my-plugin-assets",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "my-plugin-assets",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "mkdir -p ../priv/static && cp -r css/. ../priv/static && cp -r js/. ../priv/static"
|
||||
}
|
||||
}
|
||||
+6
-74
@@ -1,30 +1,14 @@
|
||||
defmodule MyPlugin.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.
|
||||
"""
|
||||
|
||||
# Once Tribes.Plugin.Base is available, replace the manual implementation
|
||||
# with:
|
||||
#
|
||||
# use Tribes.Plugin.Base, otp_app: :my_plugin
|
||||
#
|
||||
# and override only register/1.
|
||||
use Tribes.Plugin.Base, otp_app: :my_plugin
|
||||
|
||||
# @behaviour Tribes.Plugin
|
||||
|
||||
def register(_context) do
|
||||
manifest = read_manifest()
|
||||
|
||||
%{
|
||||
name: manifest["name"],
|
||||
version: manifest["version"],
|
||||
provides: manifest["provides"] || [],
|
||||
requires: manifest["requires"] || [],
|
||||
enhances_with: manifest["enhances_with"] || [],
|
||||
@impl true
|
||||
def register(context) do
|
||||
super(context)
|
||||
|> Map.merge(%{
|
||||
nav_items: [
|
||||
%{
|
||||
label: "My Plugin",
|
||||
@@ -43,59 +27,7 @@ defmodule MyPlugin.Plugin do
|
||||
],
|
||||
api_routes: [],
|
||||
plugs: [],
|
||||
children: [],
|
||||
global_js: get_in(manifest, ["assets", "global_js"]) || [],
|
||||
global_css: get_in(manifest, ["assets", "global_css"]) || [],
|
||||
migrations_path: migrations_path(manifest),
|
||||
hooks: %{}
|
||||
}
|
||||
end
|
||||
|
||||
defp read_manifest do
|
||||
manifest_path()
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
end
|
||||
|
||||
defp manifest_path do
|
||||
project_manifest = Path.join(__DIR__, "../../manifest.json") |> Path.expand()
|
||||
|
||||
candidates =
|
||||
case :code.priv_dir(:my_plugin) do
|
||||
{:error, :bad_name} ->
|
||||
[project_manifest]
|
||||
|
||||
priv_dir ->
|
||||
priv_dir = to_string(priv_dir)
|
||||
|
||||
[
|
||||
Path.join(priv_dir, "../manifest.json") |> Path.expand(),
|
||||
project_manifest
|
||||
]
|
||||
end
|
||||
|
||||
first_existing_path(candidates) || project_manifest
|
||||
end
|
||||
|
||||
defp migrations_path(manifest) do
|
||||
if manifest["migrations"] do
|
||||
candidates =
|
||||
case :code.priv_dir(:my_plugin) do
|
||||
{:error, :bad_name} ->
|
||||
[Path.join(__DIR__, "../../priv/repo/migrations") |> Path.expand()]
|
||||
|
||||
priv_dir ->
|
||||
[
|
||||
Path.join(to_string(priv_dir), "repo/migrations") |> Path.expand(),
|
||||
Path.join(__DIR__, "../../priv/repo/migrations") |> Path.expand()
|
||||
]
|
||||
end
|
||||
|
||||
first_existing_path(candidates)
|
||||
end
|
||||
end
|
||||
|
||||
defp first_existing_path(paths) do
|
||||
Enum.find(paths, &File.exists?/1)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,15 +26,17 @@ defmodule MyPlugin.MixProject do
|
||||
|
||||
defp deps do
|
||||
[
|
||||
# Host dependency — provides Tribes.Plugin behaviour, types, and test helpers.
|
||||
# Plugin API dependency for local development alongside a tribes checkout.
|
||||
#
|
||||
# For local development alongside a tribes checkout:
|
||||
{:tribes, path: "../tribes", only: [:dev, :test], runtime: false},
|
||||
{:tribes_plugin_api, path: "../tribes/tribes_plugin_api", runtime: false},
|
||||
#
|
||||
# For CI or standalone development (when not co-located with tribes):
|
||||
# {:tribes, github: "your-org/tribes", branch: "master", only: [:dev, :test]},
|
||||
|
||||
{:jason, "~> 1.2"}
|
||||
# For CI or standalone development, this can be replaced with a published
|
||||
# package once tribes_plugin_api is released.
|
||||
# {:tribes_plugin_api, github: "your-org/tribes", sparse: "tribes_plugin_api"},
|
||||
{:phoenix, "~> 1.8"},
|
||||
{:phoenix_html, "~> 4.1"},
|
||||
{:phoenix_live_view, "~> 1.1.0"}
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
%{
|
||||
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
|
||||
"phoenix": {:hex, :phoenix, "1.8.5", "919db335247e6d4891764dc3063415b0d2457641c5f9b3751b5df03d8e20bbcf", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "83b2bb125127e02e9f475c8e3e92736325b5b01b0b9b05407bcb4083b7a32485"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "1.1.28", "8a8e123d018025f756605a2fb02a4854f0d3cd7b207f710fef1fd5d9d72d0254", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "24faad535b65089642c3a7d84088109dc58f49c1f1c5a978659855d643466353"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"},
|
||||
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
|
||||
"plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
|
||||
"telemetry": {:hex, :telemetry, "1.4.1", "ab6de178e2b29b58e8256b92b382ea3f590a47152ca3651ea857a6cae05ac423", [:rebar3], [], "hexpm", "2172e05a27531d3d31dd9782841065c50dd5c3c7699d95266b2edd54c2dafa1c"},
|
||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||
"websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"},
|
||||
}
|
||||
@@ -21,7 +21,7 @@ defmodule MyPlugin.ContractTest do
|
||||
@manifest_path Path.join(__DIR__, "../manifest.json") |> Path.expand()
|
||||
|
||||
setup_all do
|
||||
manifest = @manifest_path |> File.read!() |> Jason.decode!()
|
||||
manifest = @manifest_path |> File.read!() |> JSON.decode!()
|
||||
spec = @plugin.register(%{pubsub: nil, repo: nil})
|
||||
%{manifest: manifest, spec: spec}
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule MyPlugin.ManifestTest do
|
||||
|
||||
setup_all do
|
||||
content = File.read!(@manifest_path)
|
||||
manifest = Jason.decode!(content)
|
||||
manifest = JSON.decode!(content)
|
||||
%{manifest: manifest}
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user