mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2026-09-10 16:57:56 -04:00
## What Reworks the bespoke `etc/scripts/find-node` and its `Makefile` interaction so node discovery works out of the box on Linux and macOS, is optionally friendly to version managers, and stays sympathetic to people who just use system node. ### Before - Only ever looked at `PATH`, plus a vestigial `/opt/compiler-explorer/node` hardcode (an affordance for one dev's local setup, unused by the live site). - Duplicated the required node version in three places (`find-node` had `22` twice; `.node-version`; `package.json` `engines`). - Silently fell back *past* an unusable `NODE_DIR` to whatever else it found. - nvm users got whatever default was active, not the version this repo pins. - Nagged on any version that wasn't exactly `22.x`, even newer ones. ### After — clear, permissive resolution order 1. **`$NODE_DIR/bin/node`** — explicit override, now authoritative. If it's set but missing/too old you get a clear error instead of a silent fallback. 2. **`node`/`nodejs` on `PATH`** — covers system node *and* any version manager with shell integration (fnm, asdf, nodenv, volta, an active nvm). Zero extra steps on a well-configured machine. 3. **Manager rescue** — only when PATH node is absent or below the minimum: source `nvm` and ask for the pinned version (the one manager that's a shell function rather than a PATH binary), then try `fnm`/`nodenv`/`asdf`. Each branch is inert unless that tool is installed, so nobody is pushed onto a manager. Other changes: - **Single source of truth**: the minimum major now comes solely from `.node-version` (the same file the managers read). Newer majors are always fine — the "only tested against v22.x" warning is dropped. - **`Makefile`**: `.node-bin` now also depends on `.node-version`, so bumping the pin re-resolves node instead of serving a stale cache. The lazy-dotfile pattern (so failures abort `make` and `make help` pays nothing) is kept. - **`README`**: documents the PATH-first + manager-rescue behaviour. ## Testing - `shellcheck` clean. - Verified: PATH discovery, valid/bogus `NODE_DIR`, nvm rescue (shadowed `node`+`nodejs` with v18 shims → skipped them and resolved v22.22 via nvm), too-old fallthrough, no-node/no-manager failure message, and `make info` end-to-end. - `fnm` branch is logically straightforward but was not exercised (no fnm on the test box) — worth a smoke test if you have one. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Matt Godbolt <matt@godbolt.org> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
121 lines
4.4 KiB
Bash
Executable File
121 lines
4.4 KiB
Bash
Executable File
#!/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
|