defmodule NPM.BundleDeps do
@moduledoc """
Handles bundledDependencies in package.json.
bundledDependencies are packages that are included in the tarball
when publishing, rather than being fetched from the registry.
"""
@doc """
Extracts bundledDependencies from package.json data.
Handles both `bundledDependencies` and `bundleDependencies` fields.
"""
@spec extract(map()) :: [String.t()]
def extract(%{"bundledDependencies" => deps}) when is_list(deps), do: deps
def extract(%{"bundleDependencies" => deps}) when is_list(deps), do: deps
def extract(%{"bundledDependencies" => true} = data), do: all_dep_names(data)
def extract(%{"bundleDependencies" => true} = data), do: all_dep_names(data)
def extract(_), do: []
@doc """
Checks if a package is bundled.
"""
@spec bundled?(String.t(), map()) :: boolean()
def bundled?(name, pkg_data) do
name in extract(pkg_data)
end
@doc """
Validates that all bundled deps are also declared as dependencies.
"""
@spec validate(map()) :: {:ok, [String.t()]} | {:error, [String.t()]}
def validate(pkg_data) do
bundled = extract(pkg_data)
declared = Map.keys(pkg_data["dependencies"] || %{})
missing = Enum.reject(bundled, &(&1 in declared))
if missing == [] do
{:ok, bundled}
else
{:error, Enum.map(missing, &"#{&1} is bundled but not in dependencies")}
end
end
@doc """
Returns the count of bundled dependencies.
"""
@spec count(map()) :: non_neg_integer()
def count(pkg_data), do: pkg_data |> extract() |> length()
defp all_dep_names(data) do
data
|> Map.get("dependencies", %{})
|> Map.keys()
|> Enum.sort()
end
end