defmodule NPM.Lockfile.Stats do @moduledoc """ Computes statistics about the lockfile itself. """ @doc """ Computes lockfile metadata. """ @spec compute(String.t()) :: {:ok, map()} | {:error, term()} def compute(path) do case File.stat(path) do {:ok, stat} -> {:ok, %{ size: stat.size, modified: stat.mtime, size_human: format_size(stat.size) }} {:error, reason} -> {:error, reason} end end @doc """ Computes lockfile content stats. """ @spec content_stats(map()) :: map() def content_stats(lockfile) do total = map_size(lockfile) with_integrity = Enum.count(lockfile, fn {_, e} -> has_field?(e, :integrity) end) with_deps = Enum.count(lockfile, fn {_, e} -> has_deps?(e) end) %{ total_packages: total, with_integrity: with_integrity, with_deps: with_deps, leaf_packages: total - with_deps, integrity_pct: if(total > 0, do: Float.round(with_integrity / total * 100, 1), else: 0.0) } end @doc """ Estimates disk size of node_modules from lockfile. """ @spec estimated_size(map()) :: non_neg_integer() def estimated_size(lockfile) do map_size(lockfile) * 50_000 end @doc """ Formats size in human-readable form. """ @spec format_size(non_neg_integer()) :: String.t() defdelegate format_size(bytes), to: NPM.FormatUtil defp has_field?(entry, field) do case Map.get(entry, field) || Map.get(entry, to_string(field)) do nil -> false "" -> false _ -> true end end defp has_deps?(%{dependencies: deps}) when is_map(deps) and map_size(deps) > 0, do: true defp has_deps?(%{"dependencies" => deps}) when is_map(deps) and map_size(deps) > 0, do: true defp has_deps?(_), do: false end