defmodule NPM.Diagnostics.EnvCheck do @moduledoc """ Environment checks for npm_ex. Detects available Node.js installations, npm configuration, and checks compatibility with package engine requirements. """ @doc """ Check if Node.js is available on the system PATH. Returns `{:ok, version}` or `:not_found`. """ @spec node_version :: {:ok, String.t()} | :not_found def node_version do case System.cmd("node", ["--version"], stderr_to_stdout: true) do {version, 0} -> {:ok, String.trim(version)} _ -> :not_found end rescue ErlangError -> :not_found end @doc """ Check if a package's engine requirements are met. Compares the `engines` field from package.json against the currently available Node.js version. """ @spec check_engines(%{String.t() => String.t()}) :: :ok | {:warn, [String.t()]} def check_engines(engines) when map_size(engines) == 0, do: :ok def check_engines(engines) do warnings = engines |> Enum.flat_map(&check_single_engine/1) if warnings == [], do: :ok, else: {:warn, warnings} end defp check_single_engine({"node", range}) do case node_version() do {:ok, "v" <> version} -> if version_satisfies?(version, range), do: [], else: ["node #{version} does not satisfy #{range}"] :not_found -> ["node not found (requires #{range})"] end end defp check_single_engine({engine, _range}) do ["unknown engine: #{engine}"] end defp version_satisfies?(version, range) do NPMSemver.matches?(version, range) rescue _ -> true end @doc """ Get a summary of the current environment. Returns a map with system info relevant to npm operations. """ @spec summary :: map() def summary do %{ elixir_version: System.version(), otp_version: :erlang.system_info(:otp_release) |> to_string(), os: NPM.Platform.current_os(), cpu: NPM.Platform.current_cpu(), node: node_version(), npm_ex_version: Application.spec(:duskmoon_npm, :vsn) |> to_string() } end end