Skip to main content

lib/mix/tasks/npm.size.ex

defmodule Mix.Tasks.Npm.Size do
  @shortdoc "Show node_modules size analysis"

  @moduledoc """
  Analyze `node_modules` disk usage.

      mix npm.size
      mix npm.size --top 10

  Shows total size, file count, and per-package breakdown.

  ## Options

    * `--top` — number of largest packages to show (default: 10)
  """

  use Mix.Task

  alias NPM.NodeModules

  @impl true
  def run(args) do
    {opts, _, _} = OptionParser.parse(args, strict: [top: :integer])
    top_n = Keyword.get(opts, :top, 10)
    dir = "node_modules"

    total_size = NodeModules.disk_size(dir)
    total_files = NodeModules.file_count(dir)

    Mix.shell().info("node_modules analysis:")
    Mix.shell().info("  Total size:  #{NPM.FormatUtil.format_size(total_size)}")
    Mix.shell().info("  Total files: #{total_files}")

    packages = NodeModules.installed(dir)
    Mix.shell().info("  Packages:    #{length(packages)}")

    if packages != [] do
      Mix.shell().info("\n  Top #{top_n} by size:")
      print_top(dir, packages, top_n)
    end
  end

  defp print_top(dir, packages, top_n) do
    packages
    |> Enum.map(fn name ->
      pkg_dir = Path.join(dir, name)
      size = NodeModules.disk_size(pkg_dir)
      {name, size}
    end)
    |> Enum.sort_by(fn {_, size} -> -size end)
    |> Enum.take(top_n)
    |> Enum.each(fn {name, size} ->
      Mix.shell().info("    #{NPM.FormatUtil.format_size(size)} #{name}")
    end)
  end
end