# `MishkaChelekom.Generators.Npm`
[🔗](https://github.com/mishka-group/mishka_chelekom/blob/v0.0.10/lib/mishka_chelekom/generators/npm.ex#L1)

JS package-dependency handling shared by `mix mishka.assets.deps` and the component generators.

Owns `assets/package.json`: creating it, merging or removing pinned dependencies, choosing a
package manager (bun/npm/yarn on `PATH`, preferring bun; else the `{:bun, ...}` hex binary,
which needs no system Node) and queueing the actual install.

Generators call `install/3` **directly** rather than composing `mix mishka.assets.deps`: both
that task and the generators drive an `Owl.Spinner` registered under `id: :my_spinner`, so
composing makes the inner task stop the outer task's spinner and the outer `stop` then exits
`:noproc` after the files are already written. Calling the functions keeps every change in the
same `Igniter.t()` (one reviewable diff) with no spinner, flag-namespace or task-queue coupling.

# `add_deps`

```elixir
@spec add_deps(Igniter.t(), [String.t() | map()], keyword()) :: Igniter.t()
```

Merges dependencies into `assets/package.json` (`devDependencies` with `--dev`).

Warns before overwriting a dependency the project already pins at a different version — the
manifest belongs to the user, so a silent downgrade is never acceptable.

# `check_package_manager`

```elixir
@spec check_package_manager(Igniter.t(), atom() | nil) :: Igniter.t()
```

Records which package manager to drive. With none available, adds the `{:bun, ...}` hex package
(a self-contained binary — no system Node) and its config.

Gated on `assets/package.json` specifically: a bare `Igniter.has_changes?/1` is true for *any*
pending change, so when called from a generator that just wrote a component it would add `:bun`
to the project's `mix.exs` on every single generation.

# `detect`

```elixir
@spec detect(keyword()) :: atom() | nil
```

Resolves the package manager for this run: an explicit `--npm`/`--bun`/`--yarn` flag wins,
`--mix-bun` forces the hex binary (`nil`), otherwise the first of [:bun, :npm, :yarn] on `PATH`.
Returns `nil` when none is available, which `check_package_manager/2` turns into the hex-bun path.

# `ensure_package_json_exists`

```elixir
@spec ensure_package_json_exists(Igniter.t()) :: Igniter.t()
```

Creates `assets/package.json` when the project has none.

# `install`

```elixir
@spec install(Igniter.t(), [String.t() | map()], keyword()) :: Igniter.t()
```

Full install pipeline: ensure the manifest exists, merge the deps, pick a package manager and
queue the install. `deps` is a list of `"name@version"` strings or `%{name:, version:}` maps.

Every stage after the merge is gated on `assets/package.json` having actually changed, so a
component whose packages are already pinned queues nothing.

# `package_json_path`

```elixir
@spec package_json_path() :: String.t()
```

Path of the managed manifest, relative to the project root.

# `package_managers`

```elixir
@spec package_managers() :: [atom()]
```

Package managers we know how to drive, in preference order.

# `parse_deps`

```elixir
@spec parse_deps([String.t() | map()]) :: [{String.t(), String.t()}]
```

Splits `"name@version"` (including scoped names) into `{name, version}` pairs.

# `prune`

```elixir
@spec prune(Igniter.t(), [String.t()], keyword()) :: Igniter.t()
```

Removes only those `deps` the manifest still pins at exactly the version given — packages we
wrote and nobody has changed since.

Used when switching a component's engine: the previous engine's packages must not be stranded,
but a package the project also depends on (or deliberately re-pinned) is left alone.

# `remove_deps`

```elixir
@spec remove_deps(Igniter.t(), [String.t() | map()], keyword()) :: Igniter.t()
```

Removes dependencies from `assets/package.json` and records them for the uninstall command.

# `run_command`

```elixir
@spec run_command(Igniter.t(), keyword()) :: Igniter.t()
```

Queues `mix mishka.assets.install` for the resolved package manager.

Skipped entirely when the manifest is unchanged or `--no-npm` was given — an empty task queue
matters, because Igniter shells out to `mix deps.get` before running any queued task.

---

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