PhoenixKit.Install.RuntimeDetector (phoenix_kit v2.13.9)

Copy Markdown View Source

Detects Phoenix runtime configuration patterns and determines appropriate config strategy.

This module analyzes Phoenix project configuration files to determine:

  • Whether the project uses runtime.exs patterns
  • The appropriate configuration file to modify
  • The correct insertion location for PhoenixKit configuration

A stock Phoenix 1.7+ app has both a simple config/dev.exs and a config/runtime.exs that calls System.get_env. The Local mailer adapter belongs in dev.exs on that layout. Preferring runtime.exs just because it exists is how 2.13.6 wrote config :phoenix_kit, PhoenixKit.Mailer above import Config and crashed the next boot with undefined function config/3.

Summary

Functions

Detects if the project uses runtime configuration patterns.

Same as detect_config_pattern/0 but takes file contents so it can be tested without the process cwd. Pass nil for a missing file.

Checks if dev.exs file exists.

Moves any config calls that appear before import Config to just after it.

Finds the appropriate insertion point for PhoenixKit configuration.

Finds the appropriate location within runtime.exs for development configuration.

Checks if runtime.exs contains runtime configuration patterns.

Inserts snippet into a config file immediately after import Config.

Checks if runtime.exs file exists in the project.

Checks if dev.exs file exists and is simple enough to modify.

Wraps the unguarded 2.13.6 Local-adapter snippet in if config_env() == :dev. No-op when already wrapped or absent.

Functions

detect_config_pattern()

Detects if the project uses runtime configuration patterns.

Prefers a simple config/dev.exs even when config/runtime.exs exists (every mix phx.new tree). runtime.exs is only chosen when there is no simple dev.exs to write to.

Returns

  • :dev_exs - Simple config/dev.exs (stock Phoenix)
  • :runtime - Uses runtime.exs with no simple dev.exs (e.g. Dotenvy)
  • :config_exs - Fall back to config/config.exs

Examples

iex> RuntimeDetector.detect_config_pattern()
:dev_exs

detect_config_pattern(runtime_content, dev_content)

@spec detect_config_pattern(String.t() | nil, String.t() | nil) ::
  :runtime | :dev_exs | :config_exs

Same as detect_config_pattern/0 but takes file contents so it can be tested without the process cwd. Pass nil for a missing file.

dev_exs_exists?()

Checks if dev.exs file exists.

Returns

true if dev.exs exists, false otherwise.

ensure_import_config_first(content)

@spec ensure_import_config_first(String.t()) :: String.t()

Moves any config calls that appear before import Config to just after it.

A stock Phoenix runtime.exs starts with import Config; writing above that line is a CompileError (undefined function config/3). Comments and blank lines above the import are left alone. If import Config is missing and the file already has config calls, the import is prepended.

Idempotent.

find_insertion_point()

Finds the appropriate insertion point for PhoenixKit configuration.

Returns

  • {:runtime, line_number} - Insert at specific line in runtime.exs
  • {:dev_exs, line_number} - Insert at end of dev.exs
  • {:config_exs, line_number} - Insert in config.exs with env check

Examples

iex> RuntimeDetector.find_insertion_point()
{:dev_exs, 40}

find_runtime_insertion_point()

Finds the appropriate location within runtime.exs for development configuration.

Returns

line_number (1-based) where PhoenixKit config should be inserted — always strictly after import Config when that line is present.

find_runtime_insertion_point_in(content)

@spec find_runtime_insertion_point_in(String.t()) :: pos_integer()

Same as find_runtime_insertion_point/0 for a content string.

The returned 1-based line number is the line to insert AT (existing content at that line is pushed down). import Config on line 1 therefore returns 2, not 1 — inserting at line 1 is the 2.13.6 CompileError (undefined function config/3).

has_runtime_patterns?()

Checks if runtime.exs contains runtime configuration patterns.

Returns

true if runtime patterns are detected, false otherwise.

insert_after_import_config(content, snippet)

@spec insert_after_import_config(String.t(), String.t()) :: String.t()

Inserts snippet into a config file immediately after import Config.

If import Config is missing, it is added at the top. Never places a config/3 call above import Config — that is a CompileError.

snippet is trimmed; a blank line is kept on either side.

runtime_exists?()

Checks if runtime.exs file exists in the project.

Returns

true if runtime.exs exists, false otherwise.

simple_dev_config?()

Checks if dev.exs file exists and is simple enough to modify.

Returns

true if simple dev.exs exists, false otherwise.

wrap_unguarded_local_mailer(content)

@spec wrap_unguarded_local_mailer(String.t()) :: String.t()

Wraps the unguarded 2.13.6 Local-adapter snippet in if config_env() == :dev. No-op when already wrapped or absent.

Idempotent.