From aed19eb997b7bc5e09381eafa6c207ca9607f0d3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 21 Oct 2026 12:40:42 -0700 Subject: [PATCH] [Fix] `nvm_ls`: list every installed prerelease, in semver order `sort -u` keyed only on X.Y.Z treated prereleases of a release, and the release itself, as duplicates, so `nvm ls` hid all but one of them, and which one survived depended on directory order. `nvm_sort_versions` orders them by semver precedence instead, so `v24.0.0-rc.2` sorts before `v24.0.0-rc.10`, and both before `v24.0.0`. --- nvm.sh | 35 ++++++++++++++- .../nvm_ls lists every installed prerelease | 43 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100755 test/fast/Unit tests/nvm_ls lists every installed prerelease diff --git a/nvm.sh b/nvm.sh index 673fa050..fef2673a 100755 --- a/nvm.sh +++ b/nvm.sh @@ -1984,6 +1984,37 @@ nvm_strip_iojs_prefix() { esac } +# Sorts version lines, keyed on their first field, which may end in `.node` or `.iojs`, +# by semver precedence: prereleases come before their release, and their numeric +# identifiers compare as numbers, so `v24.0.0-rc.2` comes before `v24.0.0-rc.10`. +nvm_sort_versions() { + command awk '{ + version = $1; + sub(/\.(node|iojs)$/, "", version); + sub(/^(iojs-)?v/, "", version); + prerelease = "~"; + dash = index(version, "-"); + if (dash) { + count = split(substr(version, dash + 1), identifiers, "."); + prerelease = ""; + for (i = 1; i <= count; i++) { + identifier = identifiers[i]; + if (identifier ~ /^[0-9]+$/) { + sub(/^0+/, "", identifier); + while (length(identifier) < 20) { identifier = "0" identifier } + identifier = "0" identifier; + } else { + identifier = "1" identifier; + } + prerelease = prerelease identifier "!"; + } + version = substr(version, 1, dash - 1); + } + split(version, parts, "."); + printf "%d %d %d %s\t%s\n", parts[1], parts[2], parts[3], prerelease, $0; + }' | LC_ALL=C command sort -k 1,1n -k 2,2n -k 3,3n -k 4,4 | command cut -f 2- | command uniq +} + nvm_ls() { local PATTERN PATTERN="${1-}" @@ -2128,7 +2159,7 @@ nvm_ls() { \\#${SEARCH_PATTERN}# !d; " \ -e 's#^\([^/]\{1,\}\)/\(.*\)$#\2.\1#;' \ - | command sort -t. -u -k 1.2,1n -k 2,2n -k 3,3n \ + | nvm_sort_versions \ | command sed -e 's#\(.*\)\.\([^\.]\{1,\}\)$#\2-\1#;' \ -e "s#^${NVM_NODE_PREFIX}-##;" \ )" @@ -5634,7 +5665,7 @@ nvm() { nvm_add_iojs_prefix nvm_strip_iojs_prefix \ nvm_is_iojs_version nvm_is_alias nvm_has_non_aliased \ nvm_ls_remote nvm_ls_remote_iojs nvm_ls_remote_index_tab nvm_get_remote_aliases \ - nvm_ls nvm_remote_version nvm_remote_versions \ + nvm_ls nvm_remote_version nvm_remote_versions nvm_sort_versions \ nvm_install_binary nvm_install_source nvm_clang_version \ nvm_get_mirror nvm_get_download_slug nvm_download_artifact nvm_is_cached_artifact \ nvm_install_npm_if_needed nvm_use_if_needed nvm_check_file_permissions \ diff --git a/test/fast/Unit tests/nvm_ls lists every installed prerelease b/test/fast/Unit tests/nvm_ls lists every installed prerelease new file mode 100755 index 00000000..b808ae7a --- /dev/null +++ b/test/fast/Unit tests/nvm_ls lists every installed prerelease @@ -0,0 +1,43 @@ +#!/bin/sh + +ORIG_NVM_DIR="${NVM_DIR}" +TMP_DIR='' + +cleanup () { + case "${TMP_DIR}" in + /*) rm -rf "${TMP_DIR}" ;; + esac + NVM_DIR="${ORIG_NVM_DIR}" + export NVM_DIR +} + +die () { echo "$@" ; cleanup ; exit 1; } + +: nvm.sh +\. ../../../nvm.sh + +\. ../../common.sh + +TMP_DIR="$(mktemp -d)" +NVM_DIR="${TMP_DIR}/nvm" +export NVM_DIR + +for VERSION in v24.0.0-rc.10 v24.0.0-rc.2 v24.0.0-rc.1; do + mkdir -p "${NVM_DIR}/versions/node/${VERSION}/bin" || die "fake node ${VERSION} could not be made" + make_echo "${NVM_DIR}/versions/node/${VERSION}/bin/node" "${VERSION}" +done + +OUTPUT="$(nvm_ls 24 | tr '\n' ' ')" +[ "_${OUTPUT}" = '_v24.0.0-rc.1 v24.0.0-rc.2 v24.0.0-rc.10 ' ] || die "nvm_ls 24 should list every prerelease, in order; got >${OUTPUT}<" +[ "_$(nvm_version 24)" = '_v24.0.0-rc.10' ] || die "nvm_version 24 should be the latest prerelease; got >$(nvm_version 24)<" + +make_fake_node v24.0.0 || die 'fake node v24.0.0 could not be made' +OUTPUT="$(nvm_ls 24 | tr '\n' ' ')" +[ "_${OUTPUT}" = '_v24.0.0-rc.1 v24.0.0-rc.2 v24.0.0-rc.10 v24.0.0 ' ] || die "nvm_ls 24 should list the prereleases before their release; got >${OUTPUT}<" +[ "_$(nvm_version 24)" = '_v24.0.0' ] || die "nvm_version 24 should be the release; got >$(nvm_version 24)<" + +# semver orders prerelease identifiers in order, and a shorter run of them first +OUTPUT="$(printf '%s\n' v24.0.0-rc-x.node v24.0.0.node v24.0.0-rc.1.a.node v24.0.0-rc.2.node v24.0.0-rc.node v24.0.0-rc.1.node | nvm_sort_versions | tr '\n' ' ')" +[ "_${OUTPUT}" = '_v24.0.0-rc.node v24.0.0-rc.1.node v24.0.0-rc.1.a.node v24.0.0-rc.2.node v24.0.0-rc-x.node v24.0.0.node ' ] || die "nvm_sort_versions should follow semver precedence; got >${OUTPUT}<" + +cleanup