defmodule NPM.Dist do
@moduledoc """
Handles package distribution metadata.
Parses dist info from registry responses including tarball URLs,
shasum, integrity, and unpacked size.
"""
@doc """
Extracts dist metadata from a registry version entry.
"""
@spec extract(map()) :: map()
def extract(%{"dist" => dist}) when is_map(dist) do
%{
tarball: dist["tarball"],
shasum: dist["shasum"],
integrity: dist["integrity"],
unpacked_size: dist["unpackedSize"],
file_count: dist["fileCount"],
npm_signature: dist["npm-signature"]
}
end
def extract(_),
do: %{
tarball: nil,
shasum: nil,
integrity: nil,
unpacked_size: nil,
file_count: nil,
npm_signature: nil
}
@doc """
Checks if dist metadata has integrity hash.
"""
@spec has_integrity?(map()) :: boolean()
def has_integrity?(%{integrity: integrity}) when is_binary(integrity) and integrity != "",
do: true
def has_integrity?(_), do: false
@doc """
Extracts the tarball URL from dist metadata.
"""
@spec tarball_url(map()) :: String.t() | nil
def tarball_url(%{tarball: url}), do: url
def tarball_url(_), do: nil
@doc """
Formats unpacked size for display.
"""
@spec format_size(non_neg_integer() | nil) :: String.t()
def format_size(nil), do: "unknown"
def format_size(bytes) when bytes < 1024, do: "#{bytes} B"
def format_size(bytes) when bytes < 1_048_576, do: "#{Float.round(bytes / 1024, 1)} KB"
def format_size(bytes), do: "#{Float.round(bytes / 1_048_576, 1)} MB"
@doc """
Constructs a default tarball URL from registry and package info.
"""
@spec default_tarball_url(String.t(), String.t(), String.t()) :: String.t()
def default_tarball_url(registry, name, version) do
encoded = URI.encode(name, &(&1 != ?/ and &1 != ?@))
"#{String.trim_trailing(registry, "/")}/#{encoded}/-/#{Path.basename(name)}-#{version}.tgz"
end
@doc """
Validates that dist metadata contains minimum required fields.
"""
@spec valid?(map()) :: boolean()
def valid?(%{tarball: tarball}) when is_binary(tarball) and tarball != "", do: true
def valid?(_), do: false
end