From 44aaa15927388af1ddda51299537dd05a9d27bcf Mon Sep 17 00:00:00 2001 From: Max Don Date: Mon, 30 Mar 2026 20:55:29 +0300 Subject: [PATCH] Add auto-recompile for host router when external modules change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inject __mix_recompile__?/0 into the host router via phoenix_kit_routes() macro. Uses a compile-time hash of discovered modules — when a new module dep is added or removed, the router automatically recompiles and picks up the new routes. Also registers mix.lock as @external_resource for belt-and- suspenders change detection. Documents the mechanism in AGENTS.md. Co-Authored-By: Claude Opus 4.6 (1M context) --- AGENTS.md | 12 ++++++++++++ lib/phoenix_kit/module_discovery.ex | 14 ++++++++++++++ lib/phoenix_kit_web/integration.ex | 13 +++++++++++++ 3 files changed, 39 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 16ff86d97..1c8affc9c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -233,3 +233,15 @@ url = Routes.url("/users/confirm/#{token}") Features: versioned migrations, database tables prefix support, idempotent operations, PostgreSQL validation, production mailer templates. +### External module route discovery + +Routes from external PhoenixKit modules (e.g., `phoenix_kit_entities`) are auto-discovered at compile time via `ModuleDiscovery` beam scanning. The host router automatically recompiles when module deps are added or removed — the `phoenix_kit_routes()` macro injects `__mix_recompile__?/0` into the host router with a hash of the discovered module set. + +No manual config is needed. If auto-discovery fails, register route modules explicitly as a fallback: + +```elixir +# config/config.exs +config :phoenix_kit, + route_modules: [PhoenixKitEntities.Routes] +``` + diff --git a/lib/phoenix_kit/module_discovery.ex b/lib/phoenix_kit/module_discovery.ex index ff21ea846..055fef6ed 100644 --- a/lib/phoenix_kit/module_discovery.ex +++ b/lib/phoenix_kit/module_discovery.ex @@ -35,6 +35,20 @@ defmodule PhoenixKit.ModuleDiscovery do Enum.uniq(scanned ++ configured) end + @doc """ + Returns a deterministic hash of the current set of discovered external modules. + + Used by `__mix_recompile__?/0` (injected into the host router) to detect when + modules are added or removed, triggering router recompilation. + """ + @spec module_hash() :: binary() + def module_hash do + discover_external_modules() + |> Enum.sort() + |> :erlang.term_to_binary() + |> then(&:erlang.md5/1) + end + @doc """ Scans beam files of phoenix_kit-dependent apps for `@phoenix_kit_module` attribute. diff --git a/lib/phoenix_kit_web/integration.ex b/lib/phoenix_kit_web/integration.ex index c872a7503..af1e68cff 100644 --- a/lib/phoenix_kit_web/integration.ex +++ b/lib/phoenix_kit_web/integration.ex @@ -1149,7 +1149,20 @@ defmodule PhoenixKitWeb.Integration do # Auto-discovered public routes from external PhoenixKit modules module_public_routes = compile_module_public_routes(url_prefix) + # Snapshot discovered modules so the host router auto-recompiles when deps change + current_hash = PhoenixKit.ModuleDiscovery.module_hash() + mix_lock_path = Path.expand("mix.lock") + quote do + # Recompile router when deps change (mix.lock is updated by mix deps.get) + @external_resource unquote(mix_lock_path) + + # Precise check: only actually recompile if the set of PhoenixKit modules changed + @doc false + def __mix_recompile__? do + unquote(current_hash) != PhoenixKit.ModuleDiscovery.module_hash() + end + # Generate pipeline definitions unquote(generate_pipelines())