diff --git a/CHANGELOG.md b/CHANGELOG.md
index 610af1b..dc7ec41 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
+## [0.1.9] — 2026-06-01
+
+### Fixed
+
+- **Prerelease versions with multi-digit numeric identifiers compared as strings, so `rc.10` looked older than `rc.9`.** `ComparePrerelease` used `string.CompareOrdinal`, which orders `"rc.9"` after `"rc.10"` character-by-character (`'9'` > `'1'`). A CLI on `0.5.0-rc.9` running `update --prerelease` against a `0.5.0-rc.10` release therefore reported "Already up to date." Comparison now follows Semantic Versioning §11: dot-separated prerelease identifiers compare left to right, numeric identifiers compare numerically, numeric ranks below alphanumeric, and a longer identifier set outranks a shorter one with an equal prefix.
+
+### Changed
+
+- Bumped NuGet dependencies to their latest versions: `Microsoft.Extensions.DependencyInjection.Abstractions` and `Microsoft.Extensions.Http` 10.0.5 → 10.0.8, `Microsoft.SourceLink.GitHub` 8.0.0 → 10.0.300, and the test stack (`Microsoft.NET.Test.Sdk` 17.11.1 → 18.6.0, `xunit` 2.9.2 → 2.9.3, `xunit.runner.visualstudio` 2.8.2 → 3.1.5, `coverlet.collector` 6.0.2 → 10.0.1).
+
+---
+
## [0.1.8] — 2026-05-27
### Added
diff --git a/src/NextIteration.SpectreConsole.SelfUpdate/NextIteration.SpectreConsole.SelfUpdate.csproj b/src/NextIteration.SpectreConsole.SelfUpdate/NextIteration.SpectreConsole.SelfUpdate.csproj
index 98968c8..83b231a 100644
--- a/src/NextIteration.SpectreConsole.SelfUpdate/NextIteration.SpectreConsole.SelfUpdate.csproj
+++ b/src/NextIteration.SpectreConsole.SelfUpdate/NextIteration.SpectreConsole.SelfUpdate.csproj
@@ -11,7 +11,7 @@
NextIteration.SpectreConsole.SelfUpdate
- 0.1.8
+ 0.1.9
Stuart Meeks
Self-update for Spectre.Console CLIs: pluggable update sources (GitHub Releases over HTTP, GitHub Releases via gh CLI for private repos, generic HTTPS manifest, custom), SHA-256 verification, atomic file swap, and a drop-in `update` command.
true
@@ -39,11 +39,11 @@
-
-
+
+
-
+
diff --git a/src/NextIteration.SpectreConsole.SelfUpdate/Pipeline/UpdateChecker.cs b/src/NextIteration.SpectreConsole.SelfUpdate/Pipeline/UpdateChecker.cs
index ae04d08..64c2234 100644
--- a/src/NextIteration.SpectreConsole.SelfUpdate/Pipeline/UpdateChecker.cs
+++ b/src/NextIteration.SpectreConsole.SelfUpdate/Pipeline/UpdateChecker.cs
@@ -175,6 +175,31 @@ internal static int ComparePrerelease(string? current, string? latest)
if (current is null && latest is null) return 0;
if (current is null) return 1; // no-prerelease > prerelease, so current is newer
if (latest is null) return -1; // current has prerelease, latest does not → current is older
+
+ // Semver §11: compare dot-separated identifiers left to right.
+ // Numeric identifiers compare numerically; numeric is always lower
+ // than alphanumeric; otherwise compare lexically (ASCII order). A
+ // larger set of identifiers outranks a smaller one when all preceding
+ // identifiers are equal.
+ var cParts = current.Split('.');
+ var lParts = latest.Split('.');
+ var shared = Math.Min(cParts.Length, lParts.Length);
+ for (var i = 0; i < shared; i++)
+ {
+ var cmp = ComparePrereleaseIdentifier(cParts[i], lParts[i]);
+ if (cmp != 0) return cmp;
+ }
+ return cParts.Length.CompareTo(lParts.Length);
+ }
+
+ private static int ComparePrereleaseIdentifier(string current, string latest)
+ {
+ var cNumeric = long.TryParse(current, out var cn);
+ var lNumeric = long.TryParse(latest, out var ln);
+
+ if (cNumeric && lNumeric) return cn.CompareTo(ln);
+ if (cNumeric) return -1; // numeric identifiers rank lower than alphanumeric
+ if (lNumeric) return 1;
return string.CompareOrdinal(current, latest);
}
diff --git a/tests/NextIteration.SpectreConsole.SelfUpdate.Tests/NextIteration.SpectreConsole.SelfUpdate.Tests.csproj b/tests/NextIteration.SpectreConsole.SelfUpdate.Tests/NextIteration.SpectreConsole.SelfUpdate.Tests.csproj
index a2de16c..2b27af2 100644
--- a/tests/NextIteration.SpectreConsole.SelfUpdate.Tests/NextIteration.SpectreConsole.SelfUpdate.Tests.csproj
+++ b/tests/NextIteration.SpectreConsole.SelfUpdate.Tests/NextIteration.SpectreConsole.SelfUpdate.Tests.csproj
@@ -22,13 +22,13 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/tests/NextIteration.SpectreConsole.SelfUpdate.Tests/Pipeline/UpdateCheckerVersionTests.cs b/tests/NextIteration.SpectreConsole.SelfUpdate.Tests/Pipeline/UpdateCheckerVersionTests.cs
index 5aa20ac..f8475c9 100644
--- a/tests/NextIteration.SpectreConsole.SelfUpdate.Tests/Pipeline/UpdateCheckerVersionTests.cs
+++ b/tests/NextIteration.SpectreConsole.SelfUpdate.Tests/Pipeline/UpdateCheckerVersionTests.cs
@@ -26,6 +26,17 @@ public void IsNewer_when_numeric_versions_compares_correctly(string current, str
[InlineData("1.0.0-beta", "1.0.0-alpha", false)] // beta > alpha lexicographically
[InlineData("1.0.0-beta.1", "1.0.0-beta.2", true)]
[InlineData("1.0.0-beta", "1.0.0-beta", false)]
+ // Numeric prerelease identifiers compare numerically, not lexically.
+ [InlineData("0.5.0-rc.9", "0.5.0-rc.10", true)] // rc.10 is newer than rc.9
+ [InlineData("0.5.0-rc.10", "0.5.0-rc.9", false)] // rc.9 is not newer than rc.10
+ [InlineData("1.0.0-alpha.2", "1.0.0-alpha.10", true)]
+ [InlineData("1.0.0-rc.1", "1.0.0-rc.1", false)]
+ // Fewer identifiers rank lower when the shared prefix is equal.
+ [InlineData("1.0.0-beta", "1.0.0-beta.1", true)]
+ [InlineData("1.0.0-beta.1", "1.0.0-beta", false)]
+ // Numeric identifiers rank lower than alphanumeric ones.
+ [InlineData("1.0.0-1", "1.0.0-alpha", true)]
+ [InlineData("1.0.0-alpha", "1.0.0-1", false)]
public void IsNewer_with_semver_prerelease_compares_correctly(string current, string latest, bool expected)
{
Assert.Equal(expected, UpdateChecker.IsNewer(current, latest));