Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/mix/tasks/phoenix_kit.install.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ if Code.ensure_loaded?(Igniter.Mix.Task) do
alias PhoenixKit.Install.{
ApplicationSupervisor,
AssetRebuild,
BasicConfiguration,
BrowserPipelineIntegration,
CssIntegration,
DemoFiles,
Expand Down Expand Up @@ -89,6 +90,7 @@ if Code.ensure_loaded?(Igniter.Mix.Task) do
opts = igniter.args.options

igniter
|> BasicConfiguration.add_basic_config()
|> RepoDetection.add_phoenix_kit_configuration(opts[:repo])
|> MailerConfig.add_mailer_configuration()
|> RateLimiterConfig.add_rate_limiter_configuration()
Expand Down Expand Up @@ -192,7 +194,7 @@ if Code.ensure_loaded?(Igniter.Mix.Task) do
# Install with custom PostgreSQL schema prefix for table isolation
mix phoenix_kit.install --prefix "auth" --create-schema


# Install with custom router path
mix phoenix_kit.install --router-path lib/my_app_web/router.ex

Expand Down
2 changes: 2 additions & 0 deletions lib/mix/tasks/phoenix_kit.update.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ if Code.ensure_loaded?(Igniter.Mix.Task) do
alias PhoenixKit.Install.{
ApplicationSupervisor,
AssetRebuild,
BasicConfiguration,
Common,
CssIntegration,
RateLimiterConfig
Expand Down Expand Up @@ -111,6 +112,7 @@ if Code.ensure_loaded?(Igniter.Mix.Task) do
igniter
else
igniter
|> BasicConfiguration.add_basic_config()
|> ApplicationSupervisor.add_supervisor()
|> perform_igniter_update(opts)
end
Expand Down
130 changes: 99 additions & 31 deletions lib/phoenix_kit/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"""

@default_config [
parent_app_name: nil,
parent_module: nil,
repo: nil,
mailer: nil,
scheme: "http",
Expand Down Expand Up @@ -204,16 +206,11 @@
"""
@spec get_parent_endpoint() :: {:ok, module()} | :error
def get_parent_endpoint do
case get_parent_app() do
nil ->
:error

app_name ->
base_module = app_name |> to_string() |> Macro.camelize()

case get(:parent_module) do
{:ok, parent_module} ->
potential_endpoints = [
Module.concat([base_module <> "Web", "Endpoint"]),
Module.concat([base_module, "Endpoint"])
Module.concat([String.to_atom("#{parent_module}Web"), Endpoint]),
Module.concat([parent_module, Endpoint])
]

Enum.reduce_while(potential_endpoints, :error, fn endpoint, _acc ->
Expand All @@ -223,6 +220,9 @@
{:cont, :error}
end
end)

_ ->
:error
end
end

Expand All @@ -246,30 +246,12 @@
"""
@spec get_parent_app() :: atom() | nil
def get_parent_app do
# Get the application of the configured repo to determine parent app
case get(:repo) do
{:ok, repo_module} when is_atom(repo_module) ->
# Extract app name from repo module (e.g. MyApp.Repo -> :my_app)
repo_module
|> Module.split()
|> hd()
|> Macro.underscore()
|> String.to_atom()
case get(:parent_app_name) do
{:ok, app_name} ->
app_name

_ ->
# Fallback: try to find the main application from the loaded applications
Application.loaded_applications()
|> Enum.find(fn {app, _, _} ->
app != :phoenix_kit and
app != :kernel and
app != :stdlib and
app != :elixir and
not String.starts_with?(to_string(app), "ex_")
end)
|> case do
{app, _, _} -> app
nil -> nil
end
get_parent_app_fallback()
end
end

Expand Down Expand Up @@ -304,4 +286,90 @@

:ok
end

# Fallback method to determine the parent application when explicit configuration is not available.
#
# This function implements a two-stage detection strategy:
#
# 1. **Primary Strategy**: Extract the application name from the configured repository module.
# For example, if `:repo` is configured as `MyApp.Repo`, this will return `:my_app`.
#
# 2. **Fallback Strategy**: Search through loaded applications to find the most likely
# parent application by filtering out system applications and dependencies.
#
# ## Detection Logic
#
# ### Repository-based Detection
# - Converts repository module names like `MyApp.Repo` to application atoms like `:my_app`
# - Uses Module.split() to break down the module name
# - Extracts the first segment and converts it to underscore format
#
# ### Application Search
# - Filters out system applications (`:kernel`, `:stdlib`, `:elixir`)
# - Excludes PhoenixKit itself (`:phoenix_kit`)
# - Excludes standard library applications (those starting with "ex_")
# - Returns the first remaining application, which is typically the parent app
#
# ## Examples
#
# # When repo is configured as MyApp.Repo
# # get_parent_app_fallback() -> :my_app
#
# # When no repo is configured, searches loaded applications
# # get_parent_app_fallback() -> :my_parent_app # First non-system application found
#
# # Returns nil if no suitable application is found
# # get_parent_app_fallback() -> nil
#
# ## Return Values
#
# - `atom()` - The detected parent application name
# - `nil` - No suitable parent application could be determined
#
# ## ⚠️ Reliability Warning
#
# **This function is not reliable and should not be depended upon for critical functionality.**
#
# The detection logic makes several assumptions that may not hold true in all environments:
#
# - Repository modules may not follow the `MyApp.Repo` convention
# - Application search may return incorrect results in complex dependency trees
# - Order of loaded applications is not guaranteed to be predictable
# - May return dependency applications instead of the actual parent application
#
# **For reliable behavior, always configure `:parent_app_name` explicitly** in your application
# configuration instead of relying on this fallback detection.
#
# ## Notes
#
# This function is used as a fallback when explicit `:parent_app_name` configuration
# is not provided. It enables PhoenixKit to automatically integrate with parent
# applications without requiring additional configuration in most cases.
defp get_parent_app_fallback() do

Check warning on line 348 in lib/phoenix_kit/config.ex

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Do not use parentheses when defining a function which has no arguments.
# Get the application of the configured repo to determine parent app
case get(:repo) do
{:ok, repo_module} when is_atom(repo_module) ->
# Extract app name from repo module (e.g. MyApp.Repo -> :my_app)
repo_module
|> Module.split()
|> hd()
|> Macro.underscore()
|> String.to_atom()

_ ->
# Fallback: try to find the main application from the loaded applications
Application.loaded_applications()
|> Enum.find(fn {app, _, _} ->
app != :phoenix_kit and
app != :kernel and
app != :stdlib and
app != :elixir and
not String.starts_with?(to_string(app), "ex_")
end)
|> case do
{app, _, _} -> app
nil -> nil
end
end
end
end
33 changes: 33 additions & 0 deletions lib/phoenix_kit/install/basic_configuration.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule PhoenixKit.Install.BasicConfiguration do
@moduledoc """
Installation helper for adding PhoenixKit supervisor to parent application.
Used by `mix phoenix_kit.install` task.
"""
alias Igniter.Project.Config

alias PhoenixKit.Install.IgniterHelpers

@doc """
Adds basic PhoenixKit configuration to the parent application.

Configures the parent app name and module in config.exs for PhoenixKit integration.
"""
def add_basic_config(igniter) do
parent_app_name = IgniterHelpers.get_parent_app_name(igniter)
parent_module = Igniter.Project.Module.module_name_prefix(igniter)

igniter
|> Config.configure_new(
"config.exs",
:phoenix_kit,
:parent_app_name,
parent_app_name
)
|> Config.configure_new(
"config.exs",
:phoenix_kit,
:parent_module,
parent_module
)
end
end
3 changes: 1 addition & 2 deletions lib/phoenix_kit/install/repo_detection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PhoenixKit.Install.IgniterCompat

alias Igniter.Libs.Ecto
alias Igniter.Project.Config
alias Igniter.Project.Module, as: IgniterModule
alias PhoenixKit.Install.IgniterHelpers

Expand Down Expand Up @@ -150,9 +151,7 @@

# Add repo configuration to config files
defp add_repo_config_to_files(igniter, repo_module) do
alias Igniter.Project.Config

try do

Check warning on line 154 in lib/phoenix_kit/install/repo_detection.ex

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Prefer using an implicit `try` rather than explicit `try`.
igniter
# Add repo config to main config.exs
|> Config.configure_new(
Expand Down
21 changes: 2 additions & 19 deletions lib/phoenix_kit/pages.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule PhoenixKit.Pages do
Provides filesystem operations for creating, editing, and organizing
files and folders in a web-based interface.
"""
require Logger

alias PhoenixKit.Config
alias PhoenixKit.Pages.FileOperations
Expand Down Expand Up @@ -133,10 +134,9 @@ defmodule PhoenixKit.Pages do
"/path/to/app/priv/static/pages"
"""
def root_path do
parent_app = get_parent_app()
parent_app = Config.get_parent_app()
path = resolve_pages_path(parent_app)

require Logger
Logger.debug("Pages root_path: parent_app=#{inspect(parent_app)}, path=#{inspect(path)}")

case File.mkdir_p(path) do
Expand All @@ -147,23 +147,6 @@ defmodule PhoenixKit.Pages do

# Private Helpers

defp get_parent_app do
case Config.get(:repo, nil) do
nil ->
# Fallback to phoenix_kit if no repo configured
:phoenix_kit

repo_module ->
# Extract app name from repo module
# e.g., PhoenixKitTesting.Repo -> :phoenix_kit_testing
repo_module
|> Module.split()
|> List.first()
|> Macro.underscore()
|> String.to_atom()
end
end

defp resolve_pages_path(parent_app) do
priv_dir = :code.priv_dir(parent_app) |> to_string()

Expand Down
Loading