# `PhoenixKit.Install.RuntimeDetector`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.14.0/lib/phoenix_kit/install/runtime_detector.ex#L1)

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`.

# `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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
