defmodule NPM.ProgressReporter do
@moduledoc """
Reports progress during npm operations (resolution, fetching, linking).
"""
@doc """
Formats a resolution progress message.
"""
@spec resolving(non_neg_integer(), non_neg_integer()) :: String.t()
def resolving(resolved, total) do
pct = if total > 0, do: round(resolved / total * 100), else: 0
"Resolving packages... #{resolved}/#{total} (#{pct}%)"
end
@doc """
Formats a fetch progress message.
"""
@spec fetching(String.t(), non_neg_integer(), non_neg_integer()) :: String.t()
def fetching(name, fetched, total) do
"Fetching #{name}... (#{fetched}/#{total})"
end
@doc """
Formats a linking progress message.
"""
@spec linking(non_neg_integer(), non_neg_integer()) :: String.t()
def linking(linked, total) do
"Linking packages... #{linked}/#{total}"
end
@doc """
Formats a completion message with timing.
"""
@spec done(atom(), non_neg_integer()) :: String.t()
def done(:resolve, ms), do: "✓ Resolved in #{format_time(ms)}"
def done(:fetch, ms), do: "✓ Fetched in #{format_time(ms)}"
def done(:link, ms), do: "✓ Linked in #{format_time(ms)}"
def done(:install, ms), do: "✓ Installed in #{format_time(ms)}"
def done(step, ms), do: "✓ #{step} in #{format_time(ms)}"
@doc """
Formats a step breakdown for total timing.
"""
@spec breakdown(keyword()) :: String.t()
def breakdown(steps) do
Enum.map_join(steps, "\n", fn {step, ms} ->
" #{step}: #{format_time(ms)}"
end)
end
@doc """
Formats time in human-readable form.
"""
@spec format_time(non_neg_integer()) :: String.t()
def format_time(ms) when ms < 1000, do: "#{ms}ms"
def format_time(ms), do: "#{Float.round(ms / 1000, 1)}s"
end