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
16 changes: 8 additions & 8 deletions src/Tools/CLI/Commands/InfoCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ protected override async Task<int> ExecuteAsync(CommandContext context, Cancella
// Latest CLI
if (latestCli is not null)
{
bool isUpToDate = latestCli == currentVersion;
table.AddRow("Latest CLI", isUpToDate
? $"[{FshConstants.SuccessColor}]{latestCli} (up to date)[/]"
: $"[{FshConstants.WarningColor}]{latestCli} (update available — run 'fsh update')[/]");
bool updateAvailable = VersionComparer.IsNewer(latestCli, currentVersion);
table.AddRow("Latest CLI", updateAvailable
? $"[{FshConstants.WarningColor}]{latestCli} (update available — run 'fsh update')[/]"
: $"[{FshConstants.SuccessColor}]{latestCli} (up to date)[/]");
}
else
{
Expand All @@ -61,10 +61,10 @@ protected override async Task<int> ExecuteAsync(CommandContext context, Cancella

if (latestTemplate is not null)
{
bool isUpToDate = latestTemplate == templateVersion;
table.AddRow("Latest Template", isUpToDate
? $"[{FshConstants.SuccessColor}]{latestTemplate} (up to date)[/]"
: $"[{FshConstants.WarningColor}]{latestTemplate} (update available)[/]");
bool updateAvailable = VersionComparer.IsNewer(latestTemplate, templateVersion);
table.AddRow("Latest Template", updateAvailable
? $"[{FshConstants.WarningColor}]{latestTemplate} (update available)[/]"
: $"[{FshConstants.SuccessColor}]{latestTemplate} (up to date)[/]");
}

// .NET SDK
Expand Down
2 changes: 1 addition & 1 deletion src/Tools/CLI/Commands/NewCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ private static async Task CheckForUpdatesAsync(CancellationToken cancellationTok

string currentVersion = typeof(NewCommand).Assembly.GetName().Version?.ToString(3) ?? "0.0.0";

if (latest is not null && latest != currentVersion)
if (VersionComparer.IsNewer(latest, currentVersion))
{
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine($"[{FshConstants.WarningColor}]A newer version of FSH CLI is available: {latest} (current: {currentVersion})[/]");
Expand Down
55 changes: 55 additions & 0 deletions src/Tools/CLI/Infrastructure/VersionComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
namespace FSH.CLI.Infrastructure;

/// <summary>
/// Minimal semantic-version comparison for the CLI's "update available" hints.
/// Purpose-built so a locally-built CLI/template that is AHEAD of what's published
/// (e.g. a 10.0.0 dev build vs a 10.0.0-rc.2 on NuGet) never nags about a phantom
/// update — the old code compared with string inequality, which also tripped over
/// the `+gitsha` build-metadata suffix on the informational version.
/// </summary>
internal static class VersionComparer
{
/// <summary>
/// True only when <paramref name="latest"/> is a strictly higher semantic
/// version than <paramref name="current"/>. Build metadata (<c>+...</c>) is
/// ignored; a stable release outranks any pre-release of the same core
/// version (per SemVer precedence).
/// </summary>
internal static bool IsNewer(string? latest, string? current)
{
if (string.IsNullOrWhiteSpace(latest)) return false;
if (string.IsNullOrWhiteSpace(current)) return true;

(Version latestCore, string latestPre) = Parse(latest);
(Version currentCore, string currentPre) = Parse(current);

int coreComparison = latestCore.CompareTo(currentCore);
if (coreComparison != 0) return coreComparison > 0;

// Same core version: a stable build (no pre-release tag) ranks above any
// pre-release; between two pre-releases, compare the tags ordinally.
bool latestStable = latestPre.Length == 0;
bool currentStable = currentPre.Length == 0;
if (latestStable && currentStable) return false; // identical
if (latestStable) return true; // stable > pre-release
if (currentStable) return false; // pre-release < stable
return string.CompareOrdinal(latestPre, currentPre) > 0; // both pre-release
}

// Splits "10.0.0-rc.2+abc123" into (Version 10.0.0, "rc.2"), dropping build metadata.
private static (Version Core, string PreRelease) Parse(string version)
{
int plus = version.IndexOf('+', StringComparison.Ordinal);
if (plus >= 0) version = version[..plus];

string pre = string.Empty;
int dash = version.IndexOf('-', StringComparison.Ordinal);
if (dash >= 0)
{
pre = version[(dash + 1)..];
version = version[..dash];
}

return (Version.TryParse(version, out Version? core) ? core : new Version(0, 0, 0), pre);
}
}
8 changes: 8 additions & 0 deletions src/Tools/CLI/Program.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
using System.Reflection;
using FSH.CLI.Commands;
using Spectre.Console.Cli;

// Strip the +gitsha build-metadata suffix so `fsh --version` prints a clean version.
var cliVersion = Assembly.GetExecutingAssembly()
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion?.Split('+')[0]
?? Assembly.GetExecutingAssembly().GetName().Version?.ToString(3)
?? "unknown";

var app = new CommandApp();

app.Configure(config =>
{
config.SetApplicationName("fsh");
config.SetApplicationVersion(cliVersion);

config.AddCommand<NewCommand>("new")
.WithDescription("Create a new FullStackHero .NET project.")
Expand Down
Loading