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())