From 54d9bdd99c83a01163149181d95cda525793c9ec Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Sat, 4 Apr 2026 19:45:32 +0200 Subject: [PATCH] template: make rename portable and add explicit otp_app dev flow --- README.md | 11 ++++++++-- lib/my_plugin/plugin.ex | 46 +++++++++++++++++++++++++++++------------ manifest.json | 1 + mix.exs | 2 +- scripts/rename.sh | 33 ++++++++++++++++++++++------- 5 files changed, 70 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 3b4da80..257f250 100644 --- a/README.md +++ b/README.md @@ -27,15 +27,19 @@ mix test For local development alongside a Tribes checkout: ```bash +# Build plugin code once (host loads BEAM from _build/dev/lib//ebin) +cd /path/to/your-plugin +mix compile + # Symlink into the host plugins directory cd /path/to/tribes ln -s /path/to/your-plugin plugins/your_plugin -# Start Tribes dev server — your plugin loads automatically +# Start Tribes dev server iex --sname dev -S mix phx.server ``` -Edit your plugin source. Phoenix code reloader picks up changes. +When you change plugin Elixir code, re-run `mix compile` in the plugin repo. ## Project Structure @@ -63,6 +67,9 @@ your_plugin/ ```json { "name": "your_plugin", + "entry_module": "YourPlugin.Plugin", + "host_api": "1", + "otp_app": "your_plugin", "provides": ["some_capability@1"], "requires": ["ecto@1"], "enhances_with": ["inference@1"] diff --git a/lib/my_plugin/plugin.ex b/lib/my_plugin/plugin.ex index f0310ff..6ad579f 100644 --- a/lib/my_plugin/plugin.ex +++ b/lib/my_plugin/plugin.ex @@ -58,24 +58,44 @@ defmodule MyPlugin.Plugin do end defp manifest_path do - # In a release, manifest.json sits alongside ebin/ in the plugin directory. - # In dev mode (path dep), it's at the project root. - case :code.priv_dir(:my_plugin) do - {:error, :bad_name} -> - # Dev mode fallback: relative to project root - Path.join(__DIR__, "../../../manifest.json") |> Path.expand() + project_manifest = Path.join(__DIR__, "../../manifest.json") |> Path.expand() - priv_dir -> - priv_dir |> to_string() |> Path.join("../manifest.json") |> Path.expand() - end + 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 - case :code.priv_dir(:my_plugin) do - {:error, :bad_name} -> nil - priv_dir -> priv_dir |> to_string() |> Path.join("repo/migrations") - end + 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 diff --git a/manifest.json b/manifest.json index c4964d6..274851e 100644 --- a/manifest.json +++ b/manifest.json @@ -4,6 +4,7 @@ "description": "TODO: Describe what this plugin does", "entry_module": "MyPlugin.Plugin", "host_api": "1", + "otp_app": "my_plugin", "provides": [], "requires": ["ecto@1"], "enhances_with": [], diff --git a/mix.exs b/mix.exs index 5c4eb2a..576208f 100644 --- a/mix.exs +++ b/mix.exs @@ -29,7 +29,7 @@ defmodule MyPlugin.MixProject do # Host dependency — provides Tribes.Plugin behaviour, types, and test helpers. # # For local development alongside a tribes checkout: - {:tribes, path: "../tribes", only: [:dev, :test]}, + {:tribes, path: "../tribes", only: [:dev, :test], runtime: false}, # # For CI or standalone development (when not co-located with tribes): # {:tribes, github: "your-org/tribes", branch: "master", only: [:dev, :test]}, diff --git a/scripts/rename.sh b/scripts/rename.sh index 701e381..2feba00 100755 --- a/scripts/rename.sh +++ b/scripts/rename.sh @@ -45,13 +45,32 @@ if [ -d "test/my_plugin" ]; then mv "test/my_plugin" "test/$SNAKE" fi -# Replace in all text files -find . -type f \( -name '*.ex' -o -name '*.exs' -o -name '*.json' -o -name '*.js' -o -name '*.css' -o -name '*.md' -o -name '*.yml' -o -name '*.yaml' -o -name '.formatter.exs' \) -exec \ - sed -i '' \ - -e "s/my_plugin/$SNAKE/g" \ - -e "s/MyPlugin/$MODULE/g" \ - -e "s/my-plugin/$SNAKE/g" \ - {} + +# Replace in all text files (portable across GNU/BSD sed) +sed_in_place() { + file=$1 + + if sed --version >/dev/null 2>&1; then + sed -i \ + -e "s/my_plugin/$SNAKE/g" \ + -e "s/MyPlugin/$MODULE/g" \ + -e "s/my-plugin/$SNAKE/g" \ + "$file" + else + sed -i '' \ + -e "s/my_plugin/$SNAKE/g" \ + -e "s/MyPlugin/$MODULE/g" \ + -e "s/my-plugin/$SNAKE/g" \ + "$file" + fi +} + +while IFS= read -r -d '' file; do + sed_in_place "$file" +done < <( + find . -type f \ + \( -name '*.ex' -o -name '*.exs' -o -name '*.json' -o -name '*.js' -o -name '*.css' -o -name '*.md' -o -name '*.yml' -o -name '*.yaml' -o -name '.formatter.exs' \) \ + -print0 +) # Rename asset files for ext in js css; do