defmodule NPM.FileSize do
@moduledoc """
Analyzes file sizes within installed packages.
Identifies large files, breaks down by extension, and helps
optimize node_modules size.
"""
@doc """
Analyzes files in a package directory.
"""
@spec analyze(String.t()) :: [%{path: String.t(), size: non_neg_integer()}]
def analyze(package_dir) do
case list_files(package_dir) do
{:ok, files} ->
Enum.map(files, fn file ->
%{path: file, size: file_size(Path.join(package_dir, file))}
end)
|> Enum.sort_by(& &1.size, :desc)
_ ->
[]
end
end
@doc """
Returns the N largest files in a package.
"""
@spec largest(String.t(), non_neg_integer()) :: [%{path: String.t(), size: non_neg_integer()}]
def largest(package_dir, n \\ 10) do
analyze(package_dir) |> Enum.take(n)
end
@doc """
Groups file sizes by extension.
"""
@spec by_extension(String.t()) :: %{String.t() => non_neg_integer()}
def by_extension(package_dir) do
analyze(package_dir)
|> Enum.group_by(fn %{path: path} ->
case Path.extname(path) do
"" -> "(none)"
ext -> ext
end
end)
|> Map.new(fn {ext, files} ->
{ext, files |> Enum.map(& &1.size) |> Enum.sum()}
end)
end
@doc """
Returns total size of all files in a package.
"""
@spec total(String.t()) :: non_neg_integer()
def total(package_dir) do
analyze(package_dir) |> Enum.map(& &1.size) |> Enum.sum()
end
@doc """
Formats a byte size to human-readable string.
"""
@spec format_size(non_neg_integer()) :: String.t()
defdelegate format_size(bytes), to: NPM.FormatUtil
defp list_files(dir) do
if File.dir?(dir) do
files =
dir
|> Path.join("**/*")
|> Path.wildcard()
|> Enum.filter(&File.regular?/1)
|> Enum.map(&Path.relative_to(&1, dir))
{:ok, files}
else
{:error, :not_dir}
end
end
defp file_size(path) do
case File.stat(path) do
{:ok, %{size: size}} -> size
_ -> 0
end
end
end