defmodule Mix.Tasks.Npm.Prune do
alias NPM.Install.Linker
@shortdoc "Remove extraneous packages"
@moduledoc """
Remove packages from `node_modules/` not listed in `npm.lock`.
mix npm.prune
Useful after manually removing dependencies from `package.json`
without running `mix npm.install`.
"""
use Mix.Task
@impl true
def run([]) do
Application.ensure_all_started(:req)
case NPM.Lockfile.read() do
{:ok, lockfile} -> do_prune(lockfile)
{:error, reason} -> Mix.shell().error("Failed to read lockfile: #{inspect(reason)}")
end
end
def run(_) do
Mix.shell().error("Usage: mix npm.prune")
end
defp do_prune(lockfile) do
expected = MapSet.new(Map.keys(lockfile))
before_count = count_packages("node_modules")
Linker.prune("node_modules", expected)
print_result(before_count - count_packages("node_modules"))
end
defp print_result(removed) when removed > 0 do
Mix.shell().info("Removed #{removed} extraneous package#{if removed != 1, do: "s"}")
end
defp print_result(_), do: Mix.shell().info("No extraneous packages found.")
defp count_packages(nm_dir) do
case File.ls(nm_dir) do
{:ok, entries} ->
entries
|> Enum.reject(&String.starts_with?(&1, "."))
|> Enum.count()
{:error, _} ->
0
end
end
end