defmodule NPM.DevDeps do @moduledoc """ Manages devDependencies from package.json. DevDependencies are only needed during development (testing, building, linting) and should be excluded in production installs. """ @doc """ Extracts devDependencies from package.json data. """ @spec extract(map()) :: map() def extract(%{"devDependencies" => deps}) when is_map(deps), do: deps def extract(_), do: %{} @doc """ Extracts production dependencies only. """ @spec production_deps(map()) :: map() def production_deps(pkg_data) do pkg_data["dependencies"] || %{} end @doc """ Returns all dependencies (production + dev). """ @spec all_deps(map()) :: map() def all_deps(pkg_data) do prod = production_deps(pkg_data) dev = extract(pkg_data) Map.merge(prod, dev) end @doc """ Checks if a package is a dev dependency. """ @spec dev_dep?(String.t(), map()) :: boolean() def dev_dep?(name, pkg_data) do Map.has_key?(extract(pkg_data), name) end @doc """ Categorizes dependencies into production and development. """ @spec categorize(map()) :: %{production: map(), development: map()} def categorize(pkg_data) do %{ production: production_deps(pkg_data), development: extract(pkg_data) } end @doc """ Finds dev deps that are also in production deps (potential misplacement). """ @spec overlapping(map()) :: [String.t()] def overlapping(pkg_data) do prod_names = MapSet.new(Map.keys(production_deps(pkg_data))) dev_names = Map.keys(extract(pkg_data)) Enum.filter(dev_names, &MapSet.member?(prod_names, &1)) |> Enum.sort() end @doc """ Returns a summary of dependency distribution. """ @spec summary(map()) :: %{ production: non_neg_integer(), development: non_neg_integer(), total: non_neg_integer() } def summary(pkg_data) do prod = production_deps(pkg_data) |> map_size() dev = extract(pkg_data) |> map_size() %{production: prod, development: dev, total: prod + dev} end end