defmodule NPM.Resolution.Conditional do @moduledoc """ Resolves conditional exports/imports from package.json. Handles the `exports` and `imports` fields with condition keys like `import`, `require`, `default`, `node`, `browser`, `types`. """ @known_conditions ~w(import require default node browser types development production) @doc """ Resolves an export path for given conditions. """ @spec resolve(map() | String.t(), [String.t()]) :: String.t() | nil def resolve(path, _conditions) when is_binary(path), do: path def resolve(exports, conditions) when is_map(exports) do Enum.find_value(conditions ++ ["default"], fn cond_key -> case Map.get(exports, cond_key) do nil -> nil nested when is_map(nested) -> resolve(nested, conditions) path when is_binary(path) -> path end end) end def resolve(_, _), do: nil @doc """ Returns all conditions used in an exports map. """ @spec conditions(map() | String.t()) :: [String.t()] def conditions(path) when is_binary(path), do: [] def conditions(exports) when is_map(exports) do exports |> Enum.flat_map(fn {key, val} -> nested = if is_map(val), do: conditions(val), else: [] [key | nested] end) |> Enum.uniq() |> Enum.sort() end def conditions(_), do: [] @doc """ Checks if an exports map uses a specific condition. """ @spec uses_condition?(map(), String.t()) :: boolean() def uses_condition?(exports, condition) do condition in conditions(exports) end @doc """ Returns unknown (non-standard) conditions. """ @spec unknown_conditions(map()) :: [String.t()] def unknown_conditions(exports) do exports |> conditions() |> Enum.reject(&(&1 in @known_conditions or String.starts_with?(&1, "."))) end @doc """ Lists all known condition names. """ @spec known_conditions :: [String.t()] def known_conditions, do: @known_conditions end