#!/bin/sh # Locate a usable node interpreter and write its path to $1 (or stdout). # # Resolution order, stopping at the first that satisfies the minimum version: # 1. $NODE_DIR/bin/node -- explicit override; authoritative (errors if too old) # 2. node / nodejs on PATH -- system node, and any version manager with shell # integration (fnm, asdf, nodenv, volta, an active nvm) # 3. a detected version manager, honouring .node-version -- the rescue path for # nvm's off-PATH default, or a manager without `cd` auto-switching # # The minimum version lives in .node-version (the same file version managers read) # so there's a single source of truth. We compare the full major.minor.patch so a # node that satisfies .node-version also satisfies package.json's `engines` gate; # anything newer is fine. Version-manager *lookups* use the major only, since nvm/fnm # take a "22" prefix and resolve the newest installed 22.x -- which we then validate. set -e OUT="$1" # shellcheck disable=SC1007 # `CDPATH= cd` is the intended empty-assignment idiom ROOT="$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd -P)" MIN="$(sed 's/^v//; q' "${ROOT}/.node-version")" # full minimum, e.g. 22.12.0 MIN_MAJOR="${MIN%%.*}" # major only, for manager prefix lookups # Full version (major.minor.patch) of the node at $1, or empty if it can't be run. node_version() { "$1" --version 2>/dev/null | sed 's/^v//' } # True if dotted-numeric version $1 is >= $2. Missing components count as 0, and any # non-numeric suffix (a prerelease tag) is dropped -- good enough for node's scheme. semver_ge() { awk -v a="$1" -v b="$2" 'BEGIN { na = split(a, x, "."); nb = split(b, y, "."); n = (na > nb) ? na : nb; for (i = 1; i <= n; i++) { xi = (i <= na) ? x[i] + 0 : 0; yi = (i <= nb) ? y[i] + 0 : 0; if (xi > yi) exit 0; if (xi < yi) exit 1; } exit 0; }' } # True if $1 is an executable node of at least the minimum version. usable() { [ -n "$1" ] && [ -x "$1" ] || return 1 _v="$(node_version "$1")" [ -n "${_v}" ] && semver_ge "${_v}" "${MIN}" } # Echo the first usable node discovered on PATH or via a version manager. discover() { for _cand in node nodejs; do _p="$(command -v "${_cand}" 2>/dev/null || true)" if usable "${_p}"; then echo "${_p}"; return 0; fi done # nvm is a shell function, not a PATH binary, so source it and ask for the # pinned major (a prefix like "22" resolves to the latest installed 22.x, which # usable() then version-checks). This is the case plain PATH discovery can't cover. _nvm="${NVM_DIR:-${HOME}/.nvm}/nvm.sh" if [ -s "${_nvm}" ]; then # shellcheck disable=SC1090 # nvm.sh location is user-dependent _p="$(. "${_nvm}" >/dev/null 2>&1 && nvm which "${MIN_MAJOR}" 2>/dev/null || true)" if usable "${_p}"; then echo "${_p}"; return 0; fi fi # Other managers expose a binary that honours the local .node-version. if command -v fnm >/dev/null 2>&1; then _p="$(fnm exec --using "${MIN_MAJOR}" -- node -p 'process.execPath' 2>/dev/null || true)" if usable "${_p}"; then echo "${_p}"; return 0; fi fi if command -v nodenv >/dev/null 2>&1; then _p="$(nodenv which node 2>/dev/null || true)" if usable "${_p}"; then echo "${_p}"; return 0; fi fi if command -v asdf >/dev/null 2>&1; then _p="$(asdf which node 2>/dev/null || true)" if usable "${_p}"; then echo "${_p}"; return 0; fi fi return 1 } fail() { echo >&2 "$@" echo >&2 echo >&2 "Compiler Explorer needs node ${MIN} or newer (see .node-version)." echo >&2 "Install it from https://nodejs.org/, or:" echo >&2 " - point NODE_DIR at an existing install (we use \$NODE_DIR/bin/node), or" echo >&2 " - select it with your version manager (e.g. 'nvm use', 'fnm use')." exit 1 } if [ -n "${NODE_DIR}" ]; then # Explicit override: honour it, and complain clearly if it's unusable rather # than silently falling back to something else. NODE="${NODE_DIR}/bin/node" if [ ! -x "${NODE}" ]; then fail "NODE_DIR is set to '${NODE_DIR}' but '${NODE}' is not executable." fi _v="$(node_version "${NODE}")" if [ -z "${_v}" ] || ! semver_ge "${_v}" "${MIN}"; then fail "NODE_DIR points at node v${_v:-?}, but ${MIN} or newer is required." fi else NODE="$(discover || true)" if [ -z "${NODE}" ]; then fail "Unable to find node ${MIN} or newer on your PATH." fi fi if [ -n "${OUT}" ]; then echo "${NODE}" >"${OUT}" else echo "${NODE}" fi