Skip to main content

lib/npm/package/publish_config.ex

defmodule NPM.Package.PublishConfig do
  @moduledoc """
  Parses the `publishConfig` field from package.json.

  Controls behavior when `npm publish` is called — registry,
  access level, tag, etc.
  """

  @doc """
  Extracts publishConfig from package.json data.
  """
  @spec extract(map()) :: map()
  def extract(%{"publishConfig" => config}) when is_map(config), do: config
  def extract(_), do: %{}

  @doc """
  Returns the publish registry URL.
  """
  @spec registry(map()) :: String.t() | nil
  def registry(data), do: extract(data) |> Map.get("registry")

  @doc """
  Returns the access level (public or restricted).
  """
  @spec access(map()) :: String.t()
  def access(data) do
    case extract(data) |> Map.get("access") do
      "public" -> "public"
      "restricted" -> "restricted"
      _ -> default_access(data)
    end
  end

  @doc """
  Returns the publish tag (default: "latest").
  """
  @spec tag(map()) :: String.t()
  def tag(data), do: extract(data) |> Map.get("tag", "latest")

  @doc """
  Checks if the package would be published as public.
  """
  @spec public?(map()) :: boolean()
  def public?(data), do: access(data) == "public"

  @doc """
  Checks if publishConfig is set.
  """
  @spec configured?(map()) :: boolean()
  def configured?(data), do: extract(data) != %{}

  @doc """
  Formats publishConfig for display.
  """
  @spec format(map()) :: String.t()
  def format(data) do
    config = extract(data)

    if config == %{} do
      "No publish configuration."
    else
      config
      |> Enum.map_join(", ", fn {k, v} -> "#{k}: #{v}" end)
      |> then(&"Publish config: #{&1}")
    end
  end

  defp default_access(%{"name" => name}) when is_binary(name) do
    if NPM.Scope.scoped?(name), do: "restricted", else: "public"
  end

  defp default_access(_), do: "public"
end