Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

- Update Node.js version resolver binary to emit warnings about wide version ranges and enforce the LTS upper bound. ([#1498](https://github.com/heroku/heroku-buildpack-nodejs/pull/1498))

## [v320] - 2025-12-03

Expand Down
66 changes: 45 additions & 21 deletions lib/binaries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
# Compiled from: https://github.com/heroku/buildpacks-nodejs/blob/main/common/nodejs-utils/src/bin/resolve_version.rs
RESOLVE="$BP_DIR/lib/vendor/resolve-version-$(get_os)"

resolve() {
local binary="$1"
local versionRequirement="$2"
resolve_nodejs() {
local node_version="$1"
local lts_major_version="$2"
local output

if output=$($RESOLVE "$BP_DIR/inventory/$binary.toml" "$versionRequirement"); then
if output=$($RESOLVE "$BP_DIR/inventory/node.toml" "$node_version" "$lts_major_version"); then
if [[ $output = "No result" ]]; then
return 1
else
Expand Down Expand Up @@ -47,52 +47,76 @@ install_yarn() {
}

install_nodejs() {
local version="${1:-}"
local requested_version="${1:-}"
local dir="${2:?}"
local code resolve_result
local lts_major_version="24"

if [[ -z "$version" ]]; then
version="$lts_major_version.x"
if [[ -z "$requested_version" ]]; then
requested_version="$lts_major_version.x"
fi

if [[ -n "$NODE_BINARY_URL" ]]; then
url="$NODE_BINARY_URL"
echo "Downloading and installing node from $url"
download_url="$NODE_BINARY_URL"
echo "Downloading and installing node from $download_url"
else
echo "Resolving node version $version..."
resolve_result=$(resolve node "$version" || echo "failed")

read -r number url checksum_name digest < <(echo "$resolve_result")
echo "Resolving node version $requested_version..."
resolve_result=$(resolve_nodejs "$requested_version" "$lts_major_version" || echo "failed")

if [[ "$resolve_result" == "failed" ]]; then
fail_bin_install node "$version"
fail_bin_install "$requested_version" "$lts_major_version"
fi

version=$(echo "$resolve_result" | jq -r .version)
download_url=$(echo "$resolve_result" | jq -r .url)
checksum_type=$(echo "$resolve_result" | jq -r .checksum_type)
checksum_value=$(echo "$resolve_result" | jq -r .checksum_value)
uses_wide_range=$(echo "$resolve_result" | jq .uses_wide_range)
lts_upper_bound_enforced=$(echo "$resolve_result" | jq .lts_upper_bound_enforced)

if [[ "$uses_wide_range" == "true" ]]; then
echo
echo "! The requested Node.js version is using a wide range ($requested_version) that can resolve to a Node.js major version"
echo " higher than you intended. Limiting the requested range to a major LTS range like \`$lts_major_version.x\` is recommended."
echo " https://devcenter.heroku.com/articles/nodejs-support#specifying-a-node-js-version"
fi

if [[ "$lts_upper_bound_enforced" == "true" ]]; then
echo
echo "! The resolved Node.js version has been limited to the Active LTS ($version) for the requested range of \`$requested_version\`."
echo " To opt-out of this behavior, set the following config var: \`NODEJS_ALLOW_WIDE_RANGE=true\`"
echo " https://devcenter.heroku.com/articles/nodejs-support#supported-node-js-versions"
fi

# if either warning message was displayed, ensure we add a newline before continuing with regular output
if [[ "$uses_wide_range" == "true" ]] || [[ "$lts_upper_bound_enforced" == "true" ]]; then
echo
fi

echo "Downloading and installing node $number..."
echo "Downloading and installing node $version..."

if [[ "$number" == "22.5.0" ]]; then
if [[ "$version" == "22.5.0" ]]; then
warn_about_node_version_22_5_0
fi
fi

output_file="/tmp/node.tar.gz"
code=$(curl "$url" -L --silent --fail --retry 5 --retry-max-time 15 --retry-connrefused --connect-timeout 5 -o "$output_file" --write-out "%{http_code}")
code=$(curl "$download_url" -L --silent --fail --retry 5 --retry-max-time 15 --retry-connrefused --connect-timeout 5 -o "$output_file" --write-out "%{http_code}")

if [ "$code" != "200" ]; then
echo "Unable to download node: $code" && false
fi

if [[ -z "$NODE_BINARY_URL" ]]; then
case "$checksum_name" in
case "$checksum_type" in
"sha256")
echo "Validating checksum"
if ! echo "$digest $output_file" | sha256sum --check --status; then
echo "Checksum validation failed for Node.js $number - $checksum_name:$digest" && false
if ! echo "$checksum_value $output_file" | sha256sum --check --status; then
echo "Checksum validation failed for Node.js $version - $checksum_type:$checksum_value" && false
fi
;;
*)
echo "Unsupported checksum for Node.js $number - $checksum_name:$digest" && false
echo "Unsupported checksum for Node.js $version - $checksum_type:$checksum_value" && false
;;
esac
fi
Expand Down
17 changes: 5 additions & 12 deletions lib/failure.sh
Original file line number Diff line number Diff line change
Expand Up @@ -218,32 +218,25 @@ fail_yarn_lockfile_outdated() {

fail_bin_install() {
local error
local bin="$1"
local version="$2"
local version="$1"
local lts_major_version="$2"

# Allow the subcommand to fail without trapping the error so we can
# get the failing message output
set +e

# re-request the result, saving off the reason for the failure this time
error=$($RESOLVE "$BP_DIR/inventory/$bin.toml" "$version" 2>&1)
error=$($RESOLVE "$BP_DIR/inventory/node.toml" "$version" "$lts_major_version" 2>&1)

# re-enable trapping
set -e

if [[ $error = "No result" ]]; then
case $bin in
node)
echo "Could not find Node version corresponding to version requirement: $version";;
iojs)
echo "Could not find Iojs version corresponding to version requirement: $version";;
yarn)
echo "Could not find Yarn version corresponding to version requirement: $version";;
esac
echo "Could not find Node version corresponding to version requirement: $version"
elif [[ $error == "Could not parse"* ]] || [[ $error == "Could not get"* ]]; then
echo "Error: Invalid semantic version \"$version\""
else
echo "Error: Unknown error installing \"$version\" of $bin"
echo "Error: Unknown error installing \"$version\" of node"
fi

return 1
Expand Down
Binary file modified lib/vendor/resolve-version-linux
Binary file not shown.
1 change: 1 addition & 0 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build-resolvers: build-resolver-linux
mkdir -p .build

build-resolver-linux: .build
@cargo test --manifest-path ./resolve-version/Cargo.toml
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER="$(shell which x86_64-unknown-linux-musl-gcc)" \
CC_X86_64_UNKNOWN_LINUX_MUSL="$(shell which x86_64-unknown-linux-musl-gcc)" \
cargo build --manifest-path ./resolve-version/Cargo.toml --target x86_64-unknown-linux-musl --profile release
Expand Down
26 changes: 26 additions & 0 deletions resolve-version/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions resolve-version/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ libherokubuildpack = { version = "=0.30.2", default-features = false, features =
"inventory-sha2",
] }
node-semver = "2"
serde_json = "1"
sha2 = "0.10.9"
toml = "0.9"
Loading