defmodule NPM.Report do @moduledoc """ Generates comprehensive project dependency reports. Combines data from multiple modules to produce an overview of dependencies, licenses, security, and project health. """ @doc """ Generates a dependency summary from the lockfile. """ @spec dependency_summary(map()) :: map() def dependency_summary(lockfile) do total = map_size(lockfile) scoped = Enum.count(lockfile, fn {name, _} -> NPM.Scope.scoped?(name) end) %{ total: total, scoped: scoped, unscoped: total - scoped, scoped_pct: if(total > 0, do: Float.round(scoped / total * 100, 1), else: 0.0) } end @doc """ Generates a version summary from the lockfile. """ @spec version_summary(map()) :: map() def version_summary(lockfile) do versions = lockfile |> Enum.map(fn {_, entry} -> extract_version(entry) end) |> Enum.reject(&is_nil/1) majors = versions |> Enum.map(&major_version/1) |> Enum.frequencies() |> Enum.sort_by(&elem(&1, 1), :desc) %{ total: length(versions), major_distribution: majors } end @doc """ Formats a dependency summary for display. """ @spec format_summary(map()) :: String.t() def format_summary(summary) do """ Dependencies: #{summary.total} Scoped: #{summary.scoped} (#{summary.scoped_pct}%) Unscoped: #{summary.unscoped}\ """ end @doc """ Returns a combined project report. """ @spec full_report(map(), map()) :: map() def full_report(lockfile, pkg_data) do %{ name: pkg_data["name"], version: pkg_data["version"], dependencies: dependency_summary(lockfile), versions: version_summary(lockfile), has_license: Map.has_key?(pkg_data, "license"), has_repository: Map.has_key?(pkg_data, "repository") } end defp extract_version(%{version: v}), do: v defp extract_version(%{"version" => v}), do: v defp extract_version(_), do: nil defp major_version(version) do case String.split(version, ".", parts: 2) do [major | _] -> major _ -> "0" end end end