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