MishkaChelekom.Generators.Npm (Mishka Chelekom v0.0.10)

Copy Markdown View Source

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

add_deps(igniter, deps, options \\ [])

@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(igniter, manager)

@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(options \\ [])

@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(igniter)

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

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

install(igniter, deps, options \\ [])

@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()

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

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

package_managers()

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

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

parse_deps(deps)

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

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

prune(igniter, deps, options)

@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(igniter, deps, options \\ [])

@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(igniter, options)

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