From 4acd71fe7729721b7afa7658627347d8839d6382 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 12 Apr 2026 21:31:55 +1200 Subject: [PATCH 1/5] Rework for new Node.js release policy: - current should ignore alpha - beef up local sorting to get releases after prerelease - support offline install of alpha version - deprecate lts_latest --- README.md | 41 +++++++++++++++++++++-------------------- bin/n | 55 ++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 65 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 1e2a7ac6..8fb818e4 100644 --- a/README.md +++ b/README.md @@ -10,20 +10,20 @@ Node.js version management: no subshells, no profile setup, no convoluted API, j ![usage animation](https://nimit.io/images/n/n.gif) - [`n` – Interactively Manage Your Node.js Versions](#n--interactively-manage-your-nodejs-versions) - - [Supported Platforms](#supported-platforms) - - [Installation](#installation) - - [Third Party Installers](#third-party-installers) - - [Replacing a previous node install](#replacing-a-previous-node-install) - - [Installing Node.js Versions](#installing-nodejs-versions) - - [Specifying Node.js Versions](#specifying-nodejs-versions) - - [Removing Versions](#removing-versions) - - [Using Downloaded Node.js Versions Without Reinstalling](#using-downloaded-nodejs-versions-without-reinstalling) - - [Preserving npm](#preserving-npm) - - [Miscellaneous](#miscellaneous) - - [Custom Mirror](#custom-mirror) - - [Custom Architecture](#custom-architecture) - - [Optional Environment Variables](#optional-environment-variables) - - [How It Works](#how-it-works) + - [Supported Platforms](#supported-platforms) + - [Installation](#installation) + - [Third Party Installers](#third-party-installers) + - [Replacing a previous node install](#replacing-a-previous-node-install) + - [Installing Node.js Versions](#installing-nodejs-versions) + - [Specifying Node.js Versions](#specifying-nodejs-versions) + - [Removing Versions](#removing-versions) + - [Using Downloaded Node.js Versions Without Reinstalling](#using-downloaded-nodejs-versions-without-reinstalling) + - [Preserving npm](#preserving-npm) + - [Miscellaneous](#miscellaneous) + - [Custom Mirror](#custom-mirror) + - [Custom Architecture](#custom-architecture) + - [Optional Environment Variables](#optional-environment-variables) + - [How It Works](#how-it-works) ## Supported Platforms @@ -132,11 +132,12 @@ Numeric version numbers can be complete or incomplete, with an optional leading - `8`: 8.x.y versions - `v6.1`: 6.1.x versions -There are labels for two especially useful versions: +There are labels for three especially useful versions: + +- `lts`: newest Long Term Support release +- `latest`: newest release +- `current`: newest release ignoring alpha versions -- `lts`, `lts_latest`: newest Long Term Support official release -- `latest`, `current`: newest official release - There is an `auto` label to read the target version from a file in the current directory, or any parent directory. `n` looks for in order: - `.n-node-version`: version on single line. Custom to `n`. @@ -288,8 +289,8 @@ You can override the default architecture by using the `-a` or `--arch` option, e.g. reinstall latest version of Node.js with x64 binaries: - n rm current - n --arch x64 current + n rm lts + n --arch x64 lts ## Optional Environment Variables diff --git a/bin/n b/bin/n index 917fe16f..23d9746b 100755 --- a/bin/n +++ b/bin/n @@ -306,6 +306,16 @@ function is_exact_numeric_version() { [[ "$1" =~ ^[v]{0,1}[0-9]+\.[0-9]+\.[0-9]+$ ]] } +# +# Synopsis: is_alpha_version version +# + +function is_alpha_version() { + # e.g. 26.0.0-alpha.1 + [[ "$1" =~ ^[v]{0,1}[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]] +} + + # # Synopsis: is_node_support_version version # Reference: https://github.com/nodejs/package-maintenance/issues/236#issue-474783582 @@ -319,10 +329,7 @@ function is_node_support_version() { # Synopsis: display_latest_node_support_alias version # Map aliases onto existing n aliases, current and lts. # -# Since we resolve to a single version these are of limited use. Now only -# mention lts_latest in README and help, since that one is used a bit in the wild. -# Caveat: lts_active may be incorrect across node releases when -# there isn't actually an active version for a brief period. +# Deprecated since little used and Node.js changing their release policy in 2026. # function display_latest_node_support_alias() { @@ -427,8 +434,9 @@ Versions: and other downloadable releases by / 4.9.1, 8, v6.1 Numeric versions - lts, lts_latest Newest Long Term Support official release - latest, current Newest official release + lts Newest Long Term Support release + latest Newest release + current Newest release ignoring alpha versions auto Read version from file: .n-node-version, .node-version, .nvmrc, or package.json engine Read version from package.json boron, carbon Codenames for release streams @@ -500,11 +508,24 @@ function set_active_node() { # display_versions_paths() { + # Example listings: + # node/26.0.0 + # node/27.0.0-alpha.1 + # nightly/26.0.0-nightly20260411726b22048a + # Split sort keys on '.' and do numeric sorting on major, minor, patch, and prerelease number. + # Tricks with sed substitutions to add key separators: + # k1: "node/" => "node." => "node/" + # k5: "-alpha" => ".~alpha" => "-alpha" + # k6: "26.0.0" => "26.0.0.~ZZZ" => "26.0.0" so release versions sort after semver prerelease find "$CACHE_DIR" -maxdepth 2 -type d \ | sed 's|'"$CACHE_DIR"'/||g' \ | n_grep -E "/[0-9]+\.[0-9]+\.[0-9]+" \ | sed 's|/|.|' \ - | sort -k 1,1 -k 2,2n -k 3,3n -k 4,4n -t . \ + | sed 's/-/.~/' \ + | sed '/\.~/! s/$/.~ZZZ/' \ + | sort -k 1,1 -k 2,2n -k 3,3n -k 4,4n -k 5,5 -k 6,6n -t . \ + | sed 's|\.~ZZZ$||' \ + | sed 's|\.~|-|' \ | sed 's|\.|/|' } @@ -1244,8 +1265,8 @@ function get_latest_resolved_version() { fi simple_version=${version#node/} # Only place supporting node/ [sic] - if is_exact_numeric_version "${simple_version}"; then - # Just numbers, already resolved, no need to lookup first. + if is_exact_numeric_version "${simple_version}" || is_alpha_version "${version}"; then + # Already fully resolved, no need to lookup first. simple_version="${simple_version#v}" g_target_node="${simple_version}" elif [[ "$OFFLINE" == "true" ]]; then @@ -1359,9 +1380,14 @@ function display_remote_versions() { match_count=1 # Codename is last field, first one with a name is newest lts match="${TAB_CHAR}[a-zA-Z]+\$" - elif [[ "${version}" = "latest" || "${version}" = "current" ]]; then + elif [[ "${version}" = "latest" ]]; then match_count=1 match='.' + elif [[ "${version}" = "current" ]]; then + match_count=1 + # Use simple numeric match to match "v26.0.0" and not alphas like "v27.0.0-alpha.1" + # Expecting one or the other. + match='^v[0-9]+\.[0-9]+\.[0-9]+\t' elif is_numeric_version "${version}"; then version="v${version#v}" # Avoid restriction message if exact version @@ -1370,6 +1396,13 @@ function display_remote_versions() { match="${version//\./\.}" # Avoid 1.2 matching 1.23 match="^${match}[^0-9]" + elif is_alpha_version "${version}"; then + match_count=1 + version="v${version#v}" + # Quote any dots in version so they are literal for expression + match="${version//\./\.}" + # Avoid -alpha.1 matching -alpha.12 + match="^${match}\t" elif is_lts_codename "${version}"; then # Capitalise (could alternatively make grep case insensitive) codename="$(echo "${version:0:1}" | tr '[:lower:]' '[:upper:]')${version:1}" @@ -1379,7 +1412,7 @@ function display_remote_versions() { match='.' elif is_download_version "${version}"; then version="${version#"${g_mirror_folder_name}"/}" - if [[ "${version}" = "latest" || "${version}" = "current" ]]; then + if [[ "${version}" = "latest" ]]; then match_count=1 match='.' else From 2914947f9334acdfa76075fe36effe2f89000556 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 15 Aug 2026 11:56:52 +1200 Subject: [PATCH 2/5] Weaken is_alpha_version and add is_exact_alpha_version to parallel numeric processing --- bin/n | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bin/n b/bin/n index 23d9746b..4b3f7385 100755 --- a/bin/n +++ b/bin/n @@ -308,9 +308,20 @@ function is_exact_numeric_version() { # # Synopsis: is_alpha_version version +# Matching n.n.n-alpha.* # function is_alpha_version() { + # e.g. 26.0.0-alpha.1 + [[ "$1" =~ ^[v]{0,1}[0-9]+\.[0-9]+\.[0-9]+-alpha\. ]] +} + +# +# Synopsis: is_exact_alpha_version version +# Matching n.n.n-alpha.n +# + +function is_exact_alpha_version() { # e.g. 26.0.0-alpha.1 [[ "$1" =~ ^[v]{0,1}[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]] } @@ -1265,7 +1276,7 @@ function get_latest_resolved_version() { fi simple_version=${version#node/} # Only place supporting node/ [sic] - if is_exact_numeric_version "${simple_version}" || is_alpha_version "${version}"; then + if is_exact_numeric_version "${simple_version}" || is_exact_alpha_version "${version}"; then # Already fully resolved, no need to lookup first. simple_version="${simple_version#v}" g_target_node="${simple_version}" From d504df3a779d35866f095535d2fd542305668023 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 22 Aug 2026 10:49:16 +1200 Subject: [PATCH 3/5] alpha versions will have three numeric fields --- bin/n | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/bin/n b/bin/n index 4b3f7385..044f41b6 100755 --- a/bin/n +++ b/bin/n @@ -308,22 +308,22 @@ function is_exact_numeric_version() { # # Synopsis: is_alpha_version version -# Matching n.n.n-alpha.* +# Matching n.n.n-alpha # function is_alpha_version() { - # e.g. 26.0.0-alpha.1 - [[ "$1" =~ ^[v]{0,1}[0-9]+\.[0-9]+\.[0-9]+-alpha\. ]] + # e.g. 26.0.0-alpha or 26.0.0-alpha.0 + [[ "$1" =~ ^[v]{0,1}[0-9]+\.[0-9]+\.[0-9]+-alpha(\.|$) ]] } # # Synopsis: is_exact_alpha_version version -# Matching n.n.n-alpha.n +# Matching n.n.n-alpha.n.n.n # function is_exact_alpha_version() { # e.g. 26.0.0-alpha.1 - [[ "$1" =~ ^[v]{0,1}[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]] + [[ "$1" =~ ^[v]{0,1}[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+\.[0-9]+\.[0-9]+$ ]] } @@ -1396,7 +1396,7 @@ function display_remote_versions() { match='.' elif [[ "${version}" = "current" ]]; then match_count=1 - # Use simple numeric match to match "v26.0.0" and not alphas like "v27.0.0-alpha.1" + # Use simple numeric match to match "v26.0.0" and not alphas like "v27.0.0-alpha.0.0.0" # Expecting one or the other. match='^v[0-9]+\.[0-9]+\.[0-9]+\t' elif is_numeric_version "${version}"; then @@ -1408,12 +1408,13 @@ function display_remote_versions() { # Avoid 1.2 matching 1.23 match="^${match}[^0-9]" elif is_alpha_version "${version}"; then - match_count=1 version="v${version#v}" + # Avoid restriction message if exact version + is_exact_alpha_version "${version}" && match_count=1 # Quote any dots in version so they are literal for expression match="${version//\./\.}" # Avoid -alpha.1 matching -alpha.12 - match="^${match}\t" + match="^${match}[^0-9]" elif is_lts_codename "${version}"; then # Capitalise (could alternatively make grep case insensitive) codename="$(echo "${version:0:1}" | tr '[:lower:]' '[:upper:]')${version:1}" From f748e795346b4548ce5cade4f8dd8d50303b56f3 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 22 Aug 2026 12:34:07 +1200 Subject: [PATCH 4/5] Better match text to changed example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fb818e4..f078ecd2 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ On a Mac with Apple silicon: You can override the default architecture by using the `-a` or `--arch` option, or set `N_ARCH` environment variable. -e.g. reinstall latest version of Node.js with x64 binaries: +e.g. reinstall Node.js with x64 binaries: n rm lts n --arch x64 lts From 00b1933b4423a0679def892c82184362f1dbfa18 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 30 Aug 2026 14:27:23 +1200 Subject: [PATCH 5/5] Fix comment for triple digit alpha version --- bin/n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/n b/bin/n index 044f41b6..19836036 100755 --- a/bin/n +++ b/bin/n @@ -322,7 +322,7 @@ function is_alpha_version() { # function is_exact_alpha_version() { - # e.g. 26.0.0-alpha.1 + # e.g. 26.0.0-alpha.0.0.0 [[ "$1" =~ ^[v]{0,1}[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+\.[0-9]+\.[0-9]+$ ]] }