Skip to main content

lib/mix/tasks/npm.dedupe.ex

defmodule Mix.Tasks.Npm.Dedupe do
  @shortdoc "Deduplicate packages"

  @moduledoc """
  Re-resolve dependencies to minimize duplicate packages.

      mix npm.dedupe

  Re-runs the resolver from scratch to find an optimal solution
  with fewer total packages.
  """

  use Mix.Task

  @impl true
  def run([]) do
    Application.ensure_all_started(:req)

    case NPM.Lockfile.read() do
      {:ok, lockfile} when lockfile == %{} ->
        Mix.shell().info("No npm.lock found, run `mix npm.install` first.")

      {:ok, old_lockfile} ->
        count_before = map_size(old_lockfile)
        NPM.install([])
        report_dedupe(count_before)

      {:error, reason} ->
        Mix.shell().error("Failed to read lockfile: #{inspect(reason)}")
    end
  end

  def run(_) do
    Mix.shell().error("Usage: mix npm.dedupe")
  end

  defp report_dedupe(count_before) do
    {:ok, new_lockfile} = NPM.Lockfile.read()
    diff = count_before - map_size(new_lockfile)
    print_dedupe_result(diff)
  end

  defp print_dedupe_result(diff) when diff > 0 do
    Mix.shell().info("Removed #{diff} duplicate package#{if diff != 1, do: "s"}")
  end

  defp print_dedupe_result(_), do: Mix.shell().info("No duplicates found.")
end