defmodule NPM.Publish do
@moduledoc """
Validates a package before publishing to the registry.
Checks required fields, validates package.json completeness,
and reports readiness for publishing.
"""
@required_fields ~w(name version)
@recommended_fields ~w(description license repository keywords)
@doc """
Checks if a package is ready for publishing.
"""
@spec check(map()) :: {:ok, [String.t()]} | {:error, [String.t()]}
def check(pkg_data) do
errors = check_required(pkg_data)
warnings = check_recommended(pkg_data)
if errors == [] do
{:ok, warnings}
else
{:error, errors}
end
end
@doc """
Checks required fields are present.
"""
@spec check_required(map()) :: [String.t()]
def check_required(pkg_data) do
Enum.flat_map(@required_fields, fn field ->
case pkg_data[field] do
nil -> ["Missing required field: #{field}"]
"" -> ["Empty required field: #{field}"]
_ -> []
end
end)
end
@doc """
Checks recommended fields and returns warnings.
"""
@spec check_recommended(map()) :: [String.t()]
def check_recommended(pkg_data) do
Enum.reject(@recommended_fields, &pkg_data[&1])
|> Enum.map(&"Missing recommended field: #{&1}")
end
@doc """
Checks if the version has already been published.
"""
@spec version_exists?(String.t(), String.t(), map()) :: boolean()
def version_exists?(name, version, packument) do
versions = Map.get(packument, :versions, %{})
Map.has_key?(versions, version) or (name != "" and false)
end
@doc """
Validates that the package name is not taken (for new packages).
"""
@spec name_available?(String.t()) :: boolean()
def name_available?(name) do
case NPM.Validator.validate_name(name) do
:ok -> true
_ -> false
end
end
@doc """
Returns a readiness summary.
"""
@spec summary(map()) :: %{
ready: boolean(),
errors: [String.t()],
warnings: [String.t()],
name: String.t() | nil,
version: String.t() | nil
}
def summary(pkg_data) do
errors = check_required(pkg_data)
warnings = check_recommended(pkg_data)
%{
ready: errors == [],
errors: errors,
warnings: warnings,
name: pkg_data["name"],
version: pkg_data["version"]
}
end
end