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.
Summary
Functions
Merges dependencies into assets/package.json (devDependencies with --dev).
Records which package manager to drive. With none available, adds the {:bun, ...} hex package
(a self-contained binary — no system Node) and its config.
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.
Creates assets/package.json when the project has none.
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.
Path of the managed manifest, relative to the project root.
Package managers we know how to drive, in preference order.
Splits "name@version" (including scoped names) into {name, version} pairs.
Removes only those deps the manifest still pins at exactly the version given — packages we
wrote and nobody has changed since.
Removes dependencies from assets/package.json and records them for the uninstall command.
Queues mix mishka.assets.install for the resolved package manager.
Functions
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.
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.
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.
Creates assets/package.json when the project has none.
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.
@spec package_json_path() :: String.t()
Path of the managed manifest, relative to the project root.
@spec package_managers() :: [atom()]
Package managers we know how to drive, in preference order.
Splits "name@version" (including scoped names) into {name, version} pairs.
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.
Removes dependencies from assets/package.json and records them for the uninstall command.
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.