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

Recovery for the `mix phoenix_kit.*` tasks that cannot run without igniter.

Igniter is an optional dependency (see `mix.exs`), and every task that drives
it is wrapped in `if Code.ensure_loaded?(Igniter.Mix.Task)`. Without a
fallback the guard's `else` branch defines nothing at all, so the task simply
vanishes and Mix reports:

    ** (Mix) The task "phoenix_kit.update" could not be found

which names neither PhoenixKit nor igniter, and leaves a host stranded on an
old schema with nothing to search for. This module defines the stand-in task
that takes its place, and — since the dep line is the same every time —
offers to write it into the host's `mix.exs` instead of only describing it.

## Why the dep is optional at all

A stock `mix phx.new` app declares `{:igniter, "~> 0.6", only: [:dev, :test]}`.
A non-optional dep here resolves for all environments, and Mix refuses to
converge the two — which broke `mix igniter.install phoenix_kit` on every
freshly generated project. Optional means the host's own declaration wins.

The cost is this case: a host that never declared igniter itself was getting
it transitively, and an upgrade drops it. That is what this module covers.

## Why the compile-time guard is not enough

`Code.ensure_loaded?/1` runs when PhoenixKit is compiled into the host's
`_build`, and PhoenixKit is not recompiled when the host's own dependency
list changes. Either side of the guard can therefore outlive the fact that
chose it:

  * **Compiled with igniter, igniter since dropped.** The stale beam still
    takes the igniter branch, the `else` branch was never compiled, and the
    host gets `** (UndefinedFunctionError) function
    Igniter.Mix.Task.help_requested?/1 is undefined` out of the `run/1`
    generated by `use Igniter.Mix.Task`. Every igniter-backed task calls
    `ensure_available!/3` at the top of its own `run/1` to catch this.

  * **Compiled without igniter, igniter since added.** The stand-in below is
    still what Mix loads, so following its own advice appears to change
    nothing. `stand_in_run/2` detects that and asks for the one-line
    recompile instead of repeating the instructions.

## What it offers to do

With igniter genuinely absent, `ensure_available!/3` prints the situation and
asks to add `{:igniter, "~> 0.7", only: [:dev, :test]}` to the host's
`deps/0` (auto-accepted under `--yes`/`-y`), then shells out to `mix deps.get`
— a fresh OS process, because the running one evaluated `mix.exs` at boot and
will not see the new dep. It stops there rather than continuing: PhoenixKit
itself has to be recompiled to pick the other side of the guard, which cannot
happen inside the run that is already executing the wrong side. Declining, a
MIX_ENV where `only: [:dev, :test]` would not help, an unparseable `deps/0`,
or a failed fetch all fall back to the manual instructions.

# `failure`

```elixir
@type failure() ::
  :declined
  | :already_declared
  | :deps_not_found
  | :no_mix_exs
  | :fetch_failed
  | :unsupported_env
```

Why the dep line could not be added automatically.

# `__using__`
*macro* 

Body of the stand-in task. The caller supplies its own `@moduledoc` —
generating one from here would hide it from static analysis.

# `add_igniter_dep`

```elixir
@spec add_igniter_dep(String.t()) :: {:ok, String.t()} | {:error, failure()}
```

Inserts `{:igniter, "~> 0.7", only: [:dev, :test]}` at the top of a `mix.exs` `deps/0` list.

Returns the rewritten source, or `{:error, :already_declared}` when igniter
is already in the list (a different problem — see `message/2`) and
`{:error, :deps_not_found}` when the list is not in the shape the generators
produce.

# `ensure_available!`

```elixir
@spec ensure_available!(String.t(), [String.t()], module()) :: :ok
```

Ensures igniter is loadable, or stops the task with the best available help.

Called at the top of `run/1` in every igniter-backed task, and by the
stand-in task this module defines. Returns `:ok` only when igniter is really
there; every other outcome raises, because the caller's next move is code
that cannot run.

`argv` is the task's own arguments — `--yes`/`-y` skips the confirmation
prompt. `igniter_module` exists so the failing branch is testable; callers
pass the task name and argv only.

# `message`

```elixir
@spec message(String.t(), failure() | nil) :: String.t()
```

The guidance printed when igniter is missing and cannot be added for us.

`reason` names what stopped the automatic path, so the manual instructions
can lead with the part that is actually different for this host.

# `stand_in_run`

```elixir
@spec stand_in_run(String.t(), [String.t()]) :: no_return()
```

Body of the stand-in task, compiled in place of a task that needs igniter.

Splits the two ways of arriving here: igniter is still missing (offer to add
it), or it has since been added and only this stale module remembers
otherwise (ask for the recompile).

---

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