Packages
phoenix_kit
1.7.63
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit/module_discovery.ex
defmodule PhoenixKit.ModuleDiscovery do
@moduledoc """
Zero-config auto-discovery of external PhoenixKit modules.
Uses the same pattern as Elixir's protocol consolidation: scans `.beam` files
for persisted `@phoenix_kit_module` attributes via `:beam_lib.chunks/2`.
No module loading required — pure file I/O.
## How It Works
1. `use PhoenixKit.Module` persists `@phoenix_kit_module true` in the `.beam` file
2. This module scans only deps that depend on `:phoenix_kit` (fast, targeted)
3. Reads the persisted attribute from each beam file without loading the module
4. Works at both compile time (route generation) and runtime (ModuleRegistry)
## Fallback
Also reads `Application.get_env(:phoenix_kit, :modules, [])` for backwards
compatibility. Both sources are merged and deduplicated.
"""
require Logger
@doc """
Discovers external PhoenixKit modules from beam files + config fallback.
Returns a deduplicated list of module atoms that implement `PhoenixKit.Module`.
Excludes internal modules (those in the `PhoenixKit.Modules` namespace that are
bundled with PhoenixKit itself).
"""
@spec discover_external_modules() :: [module()]
def discover_external_modules do
scanned = scan_beam_files()
configured = Application.get_env(:phoenix_kit, :modules, [])
Enum.uniq(scanned ++ configured)
end
@doc """
Scans beam files of phoenix_kit-dependent apps for `@phoenix_kit_module` attribute.
Only checks apps that explicitly list `:phoenix_kit` in their dependencies,
keeping the scan fast and targeted.
"""
@spec scan_beam_files() :: [module()]
def scan_beam_files do
phoenix_kit_dependent_apps()
|> Enum.flat_map(&app_phoenix_kit_modules/1)
rescue
error ->
Logger.warning("[ModuleDiscovery] Beam scanning failed: #{Exception.message(error)}")
[]
end
# Find all loaded applications that depend on :phoenix_kit
defp phoenix_kit_dependent_apps do
for {app, _, _} <- :application.loaded_applications(),
app != :phoenix_kit,
depends_on_phoenix_kit?(app) do
app
end
end
defp depends_on_phoenix_kit?(app) do
case :application.get_key(app, :applications) do
{:ok, apps} -> :phoenix_kit in apps
_ -> false
end
end
# Get all PhoenixKit modules from a specific app
defp app_phoenix_kit_modules(app) do
case :application.get_key(app, :modules) do
{:ok, modules} ->
Enum.filter(modules, &phoenix_kit_module?/1)
_ ->
# App modules not available via :application, try beam file scan
scan_app_ebin(app)
end
end
# Check if a module has the @phoenix_kit_module persisted attribute.
# First tries :code.which/1 to locate the beam file, then reads attributes
# via :beam_lib.chunks/2 without fully loading the module into the VM.
defp phoenix_kit_module?(mod) do
case :code.which(mod) do
:non_existing ->
# Module not on code path — try loading it first, then re-check
case Code.ensure_loaded(mod) do
{:module, _} ->
case :code.which(mod) do
beam_path when is_list(beam_path) -> check_beam_attribute(beam_path)
_ -> false
end
_ ->
false
end
beam_path when is_list(beam_path) ->
check_beam_attribute(beam_path)
_ ->
false
end
end
defp check_beam_attribute(mod_or_path) do
case :beam_lib.chunks(mod_or_path, [:attributes]) do
{:ok, {_, [{:attributes, attrs}]}} ->
attrs[:phoenix_kit_module] == [true]
_ ->
false
end
end
# Fallback: scan the ebin directory directly for beam files
defp scan_app_ebin(app) do
dir = Application.app_dir(app, "ebin")
dir
|> Path.join("*.beam")
|> Path.wildcard()
|> Enum.filter(&phoenix_kit_beam_file?/1)
|> Enum.map(&beam_file_to_module/1)
rescue
_ -> []
end
defp phoenix_kit_beam_file?(path) do
case :beam_lib.chunks(String.to_charlist(path), [:attributes]) do
{:ok, {_, [{:attributes, attrs}]}} ->
attrs[:phoenix_kit_module] == [true]
_ ->
false
end
end
# Using String.to_existing_atom/1 — any module with @phoenix_kit_module attribute
# will already exist as an atom (it's in the app's module list).
defp beam_file_to_module(path) do
path
|> Path.basename(".beam")
|> String.to_existing_atom()
end
end