defmodule Mix.Tasks.Npm.Deprecations do @shortdoc "List deprecated installed packages" @moduledoc """ Show deprecated packages in the current lockfile. mix npm.deprecations Queries the registry for each locked package and reports any deprecation notices. """ 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 packages installed.") {:ok, lockfile} -> lockfile |> find_deprecated() |> print_deprecated() {:error, reason} -> Mix.shell().error("Failed to read lockfile: #{inspect(reason)}") end end def run(_) do Mix.shell().error("Usage: mix npm.deprecations") end defp print_deprecated([]) do Mix.shell().info("No deprecated packages found.") end defp print_deprecated(deprecated) do Mix.shell().info("Deprecated packages:\n") Enum.each(deprecated, fn {name, version, msg} -> Mix.shell().info(" #{name}@#{version}: #{msg}") end) end defp find_deprecated(lockfile) do lockfile |> Task.async_stream( fn {name, entry} -> check_deprecated(name, entry.version) end, max_concurrency: 8, timeout: 30_000 ) |> Enum.flat_map(fn {:ok, nil} -> [] {:ok, result} -> [result] _ -> [] end) |> Enum.sort_by(&elem(&1, 0)) end defp check_deprecated(name, version) do case NPM.Registry.get_packument(name) do {:ok, packument} -> case get_in(packument.versions, [version]) do %{deprecated: msg} when is_binary(msg) and msg != "" -> {name, version, msg} _ -> nil end _ -> nil end end end