Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 src/Aspire.Cli/Aspire.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<Compile Include="$(SharedDir)CircularBuffer.cs" Link="Utils\CircularBuffer.cs" />
<Compile Include="$(SharedDir)StringComparers.cs" Link="StringComparers.cs" />
<Compile Include="$(RepoRoot)src\Aspire.Hosting\Backchannel\BackchannelDataTypes.cs" Link="Backchannel\CliBackchannelDataTypes.cs" />
<Compile Include="$(SharedDir)PackageUpdateHelpers.cs" Link="Utils\PackageUpdateHelpers.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Cli/Commands/AddCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Aspire.Cli.Telemetry;
using Aspire.Cli.Utils;
using Semver;
using NuGetPackage = Aspire.Shared.NuGetPackageCli;

namespace Aspire.Cli.Commands;

Expand Down
5 changes: 4 additions & 1 deletion src/Aspire.Cli/Commands/BaseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using System.Diagnostics;
using Aspire.Cli.Configuration;
using Aspire.Cli.Utils;

Expand Down Expand Up @@ -29,7 +30,9 @@ protected BaseCommand(string name, string description, IFeatures features, ICliU
// but we'll only wait so long before we get details back about updates
// being available (it should already be in the cache for longer running
// commands and some commands will opt out entirely)
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var cts = !Debugger.IsAttached
? new CancellationTokenSource(TimeSpan.FromSeconds(10))
: new CancellationTokenSource();
await updateNotifier.NotifyIfUpdateAvailableAsync(currentDirectory, cancellationToken: cts.Token);
}
catch
Expand Down
2 changes: 2 additions & 0 deletions src/Aspire.Cli/Commands/NewCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
using Aspire.Cli.Templating;
using Aspire.Cli.Utils;
using Spectre.Console;
using NuGetPackage = Aspire.Shared.NuGetPackageCli;

namespace Aspire.Cli.Commands;

internal sealed class NewCommand : BaseCommand
Expand Down
41 changes: 4 additions & 37 deletions src/Aspire.Cli/DotNetCliRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
using Aspire.Cli.Resources;
using Aspire.Cli.Telemetry;
using Aspire.Hosting;
using Aspire.Shared;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NuGetPackage = Aspire.Shared.NuGetPackageCli;

namespace Aspire.Cli;

Expand Down Expand Up @@ -716,52 +718,17 @@ public async Task<int> AddPackageAsync(FileInfo projectFilePath, string packageN
return (ExitCodeConstants.FailedToAddPackage, null);
}

var foundPackages = new List<NuGetPackage>();
try
{
using var document = JsonDocument.Parse(stdout);

var searchResultsArray = document.RootElement.GetProperty("searchResult");

foreach (var sourceResult in searchResultsArray.EnumerateArray())
{
var source = sourceResult.GetProperty("sourceName").GetString();
var sourcePackagesArray = sourceResult.GetProperty("packages");

foreach (var packageResult in sourcePackagesArray.EnumerateArray())
{
var id = packageResult.GetProperty("id").GetString();

// var version = prerelease switch {
// true => packageResult.GetProperty("version").GetString(),
// false => packageResult.GetProperty("latestVersion").GetString()
// };

var version = packageResult.GetProperty("latestVersion").GetString();

foundPackages.Add(new NuGetPackage
{
Id = id!,
Version = version!,
Source = source!
});
}
}
var foundPackages = PackageUpdateHelpers.ParsePackageSearchResults(stdout);
return (result, foundPackages.ToArray());
}
catch (JsonException ex)
{
logger.LogError($"Failed to read JSON returned by the package search. {ex.Message}");
return (ExitCodeConstants.FailedToAddPackage, null);
}

return (result, foundPackages.ToArray());
}
}
}

internal class NuGetPackage
{
public string Id { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
public string Source { get; set; } = string.Empty;
}
1 change: 1 addition & 0 deletions src/Aspire.Cli/NuGet/NuGetPackageCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Aspire.Cli.Telemetry;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using NuGetPackage = Aspire.Shared.NuGetPackageCli;

namespace Aspire.Cli.NuGet;

Expand Down
64 changes: 4 additions & 60 deletions src/Aspire.Cli/Utils/CliUpdateNotifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Aspire.Cli.Interaction;
using Aspire.Cli.NuGet;
using Aspire.Shared;
using Microsoft.Extensions.Logging;
using Semver;

Expand Down Expand Up @@ -31,7 +32,7 @@ public async Task NotifyIfUpdateAvailableAsync(DirectoryInfo workingDirectory, C
}

var availablePackages = await nuGetPackageCache.GetCliPackagesAsync(workingDirectory, prerelease: true, source: null, cancellationToken);
var newerVersion = GetNewerVersion(currentVersion, availablePackages);
var newerVersion = PackageUpdateHelpers.GetNewerVersion(currentVersion, availablePackages);

if (newerVersion is not null)
{
Expand All @@ -46,63 +47,6 @@ public async Task NotifyIfUpdateAvailableAsync(DirectoryInfo workingDirectory, C

protected virtual SemVersion? GetCurrentVersion()
{
try
{
var versionString = VersionHelper.GetDefaultTemplateVersion();
// Remove any build metadata (e.g., +sha.12345) for comparison
var cleanVersionString = versionString.Split('+')[0];
return SemVersion.Parse(cleanVersionString, SemVersionStyles.Strict);
}
catch
{
return null;
}
return PackageUpdateHelpers.GetCurrentPackageVersion();
}

private static SemVersion? GetNewerVersion(SemVersion currentVersion, IEnumerable<NuGetPackage> availablePackages)
{
SemVersion? newestStable = null;
SemVersion? newestPrerelease = null;

foreach (var package in availablePackages)
{
if (SemVersion.TryParse(package.Version, SemVersionStyles.Strict, out var version))
{
if (version.IsPrerelease)
{
newestPrerelease = newestPrerelease is null || SemVersion.PrecedenceComparer.Compare(version, newestPrerelease) > 0 ? version : newestPrerelease;
}
else
{
newestStable = newestStable is null || SemVersion.PrecedenceComparer.Compare(version, newestStable) > 0 ? version : newestStable;
}
}
}

// Apply notification rules
if (currentVersion.IsPrerelease)
{
// Rule 1: If using a prerelease version where the version is lower than the latest stable version, prompt to upgrade
if (newestStable is not null && SemVersion.PrecedenceComparer.Compare(currentVersion, newestStable) < 0)
{
return newestStable;
}

// Rule 2: If using a prerelease version and there is a newer prerelease version, prompt to upgrade
if (newestPrerelease is not null && SemVersion.PrecedenceComparer.Compare(currentVersion, newestPrerelease) < 0)
{
return newestPrerelease;
}
}
else
{
// Rule 3: If using a stable version and there is a newer stable version, prompt to upgrade
if (newestStable is not null && SemVersion.PrecedenceComparer.Compare(currentVersion, newestStable) < 0)
{
return newestStable;
}
}

return null;
}
}
}
10 changes: 2 additions & 8 deletions src/Aspire.Cli/Utils/VersionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Cli.Resources;
using Aspire.Shared;

namespace Aspire.Cli.Utils;

internal static class VersionHelper
{
public static string GetDefaultTemplateVersion()
{
// Write some code that gets the informational assembly version of the current assembly and returns it as a string.
var assembly = typeof(VersionHelper).Assembly;
var informationalVersion = assembly
.GetCustomAttributes(typeof(System.Reflection.AssemblyInformationalVersionAttribute), false)
.OfType<System.Reflection.AssemblyInformationalVersionAttribute>()
.FirstOrDefault()?.InformationalVersion;

return informationalVersion ?? throw new InvalidOperationException(ErrorStrings.UnableToRetrieveAssemblyVersion);
return PackageUpdateHelpers.GetCurrentAssemblyVersion() ?? throw new InvalidOperationException(ErrorStrings.UnableToRetrieveAssemblyVersion);
}
}
1 change: 1 addition & 0 deletions src/Aspire.Hosting/Aspire.Hosting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<Compile Include="$(SharedDir)LaunchProfiles\*.cs" />
<Compile Include="$(SharedDir)PortAllocator.cs" Link="Publishing\PortAllocator.cs" />
<Compile Include="$(SharedDir)OverloadResolutionPriorityAttribute.cs" Link="Utils\OverloadResolutionPriorityAttribute.cs" />
<Compile Include="$(SharedDir)PackageUpdateHelpers.cs" Link="Utils\PackageUpdateHelpers.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting/DistributedApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)
_innerBuilder.Services.AddHostedService<DistributedApplicationLifecycle>();
_innerBuilder.Services.AddHostedService<DistributedApplicationRunner>();
_innerBuilder.Services.AddHostedService<VersionCheckService>();
_innerBuilder.Services.AddSingleton<IVersionFetcher, VersionFetcher>();
_innerBuilder.Services.AddSingleton<IPackageFetcher, PackageFetcher>();
_innerBuilder.Services.AddSingleton(options);
_innerBuilder.Services.AddSingleton<ResourceNotificationService>();
_innerBuilder.Services.AddSingleton<ResourceLoggerService>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Semver;
using Aspire.Shared;

namespace Aspire.Hosting.VersionChecking;

internal interface IVersionFetcher
internal interface IPackageFetcher
{
Task<SemVersion?> TryFetchLatestVersionAsync(string appHostDirectory, CancellationToken cancellationToken);
Task<List<NuGetPackage>> TryFetchPackagesAsync(string appHostDirectory, CancellationToken cancellationToken);
}
6 changes: 0 additions & 6 deletions src/Aspire.Hosting/VersionChecking/Package.cs

This file was deleted.

65 changes: 65 additions & 0 deletions src/Aspire.Hosting/VersionChecking/PackageFetcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text;
using Aspire.Hosting.Dcp.Process;
using Aspire.Shared;
using Microsoft.Extensions.Logging;

namespace Aspire.Hosting.VersionChecking;

internal sealed class PackageFetcher : IPackageFetcher
{
public const string PackageId = "Aspire.Hosting.AppHost";

private readonly ILogger<PackageFetcher> _logger;

public PackageFetcher(ILogger<PackageFetcher> logger)
{
_logger = logger;
}

public async Task<List<NuGetPackage>> TryFetchPackagesAsync(string appHostDirectory, CancellationToken cancellationToken)
{
var outputJson = new StringBuilder();
var spec = new ProcessSpec("dotnet")
{
Arguments = $"package search {PackageId} --format json",
ThrowOnNonZeroReturnCode = false,
InheritEnv = true,
OnOutputData = output =>
{
outputJson.Append(output);
_logger.LogDebug("dotnet (stdout): {Output}", output);
},
OnErrorData = error =>
{
_logger.LogDebug("dotnet (stderr): {Error}", error);
},
WorkingDirectory = appHostDirectory
};

_logger.LogDebug("Running dotnet CLI to check for latest version of {PackageId} with arguments: {ArgumentList}", PackageId, spec.Arguments);
var (pendingProcessResult, processDisposable) = ProcessUtil.Run(spec);

await using (processDisposable)
{
var processResult = await pendingProcessResult
.WaitAsync(cancellationToken)
.ConfigureAwait(false);

if (processResult.ExitCode != 0)
{
_logger.LogDebug("The dotnet CLI call to check for latest version failed with exit code {ExitCode}.", processResult.ExitCode);
return [];
}
}

// Filter packages to only consider "Aspire.Hosting.AppHost".
// Although the CLI command 'dotnet package search Aspire.Hosting.AppHost --format json'
// should already limit results according to NuGet search syntax
// (https://learn.microsoft.com/en-us/nuget/consume-packages/finding-and-choosing-packages#search-syntax),
// we add this extra check for robustness in case the CLI output includes unexpected packages.
return PackageUpdateHelpers.ParsePackageSearchResults(outputJson.ToString(), PackageId);
}
}
Loading