defmodule Mix.Tasks.Npm.Verify do
@shortdoc "Verify node_modules matches lockfile"
@moduledoc """
Check that `node_modules` matches `npm.lock`.
mix npm.verify
Reports missing and extraneous packages. Useful for CI
to ensure `mix npm.get` was run after lockfile changes.
"""
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 lockfile. Nothing to verify.")
{:ok, lockfile} ->
case NPM.Workspace.read_all() do
{:ok, project} ->
expected = expected_packages(lockfile, project.local_links)
expected |> NPM.NodeModules.diff() |> report_diff(map_size(expected))
{:error, reason} ->
Mix.shell().error("Failed: #{inspect(reason)}")
end
{:error, reason} ->
Mix.shell().error("Failed: #{inspect(reason)}")
end
end
def run(_) do
Mix.shell().error("Usage: mix npm.verify")
end
defp expected_packages(lockfile, local_links) do
Map.merge(lockfile, Map.new(local_links, fn {name, _path} -> {name, %{}} end))
end
defp report_diff({[], []}, count) do
Mix.shell().info("node_modules matches lockfile (#{count} packages)")
end
defp report_diff({missing, extra}, _count) do
Enum.each(missing, &Mix.shell().error(" missing: #{&1}"))
Enum.each(extra, &Mix.shell().info(" extra: #{&1}"))
end
end