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
14 changes: 9 additions & 5 deletions extension/schemas/aspire-global-settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -296,21 +296,25 @@
"type": "string"
},
"overrideStagingFeed": {
"description": "Override the NuGet feed URL used by the staging channel. When set, this URL is used instead of the default SHA-based build-specific feed.",
"description": "[Internal] Override the NuGet feed URL used by the staging channel. When set, this URL is used instead of the default SHA-based build-specific feed.",
"type": "string"
},
"overrideStagingQuality": {
"description": "Override the package quality filter for the staging channel. Set to \"Prerelease\" when staging builds are not yet marked as stable to use the shared daily feed instead of the SHA-based feed. Valid values: \"Stable\", \"Prerelease\", \"Both\".",
"description": "[Internal] Override the package quality filter for the staging channel. Set to \"Prerelease\" when staging builds are not yet marked as stable to use the shared daily feed instead of the SHA-based feed. Valid values: \"Stable\", \"Prerelease\", \"Both\".",
"type": "string",
"enum": [
"Stable",
"Prerelease",
"Both"
]
},
"stagingVersionPrefix": {
"description": "Filter staging channel packages to a specific Major.Minor version (e.g., \"13.2\"). When set, only packages matching this version prefix are shown, preventing newer daily versions from being selected.",
"type": "string"
"stagingPinToCliVersion": {
"description": "[Internal] When set to \"true\" and using the staging channel with Prerelease quality on the shared feed, all template and integration packages are pinned to the exact version of the installed CLI. This bypasses NuGet search entirely, ensuring version consistency.",
"type": "string",
"enum": [
"true",
"false"
]
}
},
"additionalProperties": false
Expand Down
14 changes: 9 additions & 5 deletions extension/schemas/aspire-settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,21 +300,25 @@
"type": "string"
},
"overrideStagingFeed": {
"description": "Override the NuGet feed URL used by the staging channel. When set, this URL is used instead of the default SHA-based build-specific feed.",
"description": "[Internal] Override the NuGet feed URL used by the staging channel. When set, this URL is used instead of the default SHA-based build-specific feed.",
"type": "string"
},
"overrideStagingQuality": {
"description": "Override the package quality filter for the staging channel. Set to \"Prerelease\" when staging builds are not yet marked as stable to use the shared daily feed instead of the SHA-based feed. Valid values: \"Stable\", \"Prerelease\", \"Both\".",
"description": "[Internal] Override the package quality filter for the staging channel. Set to \"Prerelease\" when staging builds are not yet marked as stable to use the shared daily feed instead of the SHA-based feed. Valid values: \"Stable\", \"Prerelease\", \"Both\".",
"type": "string",
"enum": [
"Stable",
"Prerelease",
"Both"
]
},
"stagingVersionPrefix": {
"description": "Filter staging channel packages to a specific Major.Minor version (e.g., \"13.2\"). When set, only packages matching this version prefix are shown, preventing newer daily versions from being selected.",
"type": "string"
"stagingPinToCliVersion": {
"description": "[Internal] When set to \"true\" and using the staging channel with Prerelease quality on the shared feed, all template and integration packages are pinned to the exact version of the installed CLI. This bypasses NuGet search entirely, ensuring version consistency.",
"type": "string",
"enum": [
"true",
"false"
]
}
},
"additionalProperties": false
Expand Down
43 changes: 25 additions & 18 deletions src/Aspire.Cli/Packaging/PackageChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,18 @@

namespace Aspire.Cli.Packaging;

internal class PackageChannel(string name, PackageChannelQuality quality, PackageMapping[]? mappings, INuGetPackageCache nuGetPackageCache, bool configureGlobalPackagesFolder = false, string? cliDownloadBaseUrl = null, SemVersion? versionPrefix = null)
internal class PackageChannel(string name, PackageChannelQuality quality, PackageMapping[]? mappings, INuGetPackageCache nuGetPackageCache, bool configureGlobalPackagesFolder = false, string? cliDownloadBaseUrl = null, string? pinnedVersion = null)
{
public string Name { get; } = name;
public PackageChannelQuality Quality { get; } = quality;
public PackageMapping[]? Mappings { get; } = mappings;
public PackageChannelType Type { get; } = mappings is null ? PackageChannelType.Implicit : PackageChannelType.Explicit;
public bool ConfigureGlobalPackagesFolder { get; } = configureGlobalPackagesFolder;
public string? CliDownloadBaseUrl { get; } = cliDownloadBaseUrl;
public SemVersion? VersionPrefix { get; } = versionPrefix;
public string? PinnedVersion { get; } = pinnedVersion;

public string SourceDetails { get; } = ComputeSourceDetails(mappings);

private bool MatchesVersionPrefix(SemVersion semVer)
{
if (VersionPrefix is null)
{
return true;
}

return semVer.Major == VersionPrefix.Major && semVer.Minor == VersionPrefix.Minor;
}

private static string ComputeSourceDetails(PackageMapping[]? mappings)
{
if (mappings is null)
Expand All @@ -52,6 +42,11 @@ private static string ComputeSourceDetails(PackageMapping[]? mappings)

public async Task<IEnumerable<NuGetPackage>> GetTemplatePackagesAsync(DirectoryInfo workingDirectory, CancellationToken cancellationToken)
{
if (PinnedVersion is not null)
{
return [new NuGetPackage { Id = "Aspire.ProjectTemplates", Version = PinnedVersion, Source = SourceDetails }];
}

var tasks = new List<Task<IEnumerable<NuGetPackage>>>();

using var tempNuGetConfig = Type is PackageChannelType.Explicit ? await TemporaryNuGetConfig.CreateAsync(Mappings!) : null;
Expand Down Expand Up @@ -80,7 +75,7 @@ public async Task<IEnumerable<NuGetPackage>> GetTemplatePackagesAsync(DirectoryI
{ Quality: PackageChannelQuality.Stable, SemVer: { IsPrerelease: false } } => true,
{ Quality: PackageChannelQuality.Prerelease, SemVer: { IsPrerelease: true } } => true,
_ => false
}).Where(p => MatchesVersionPrefix(SemVersion.Parse(p.Version)));
});

return filteredPackages;
}
Expand Down Expand Up @@ -115,13 +110,25 @@ public async Task<IEnumerable<NuGetPackage>> GetIntegrationPackagesAsync(Directo
{ Quality: PackageChannelQuality.Stable, SemVer: { IsPrerelease: false } } => true,
{ Quality: PackageChannelQuality.Prerelease, SemVer: { IsPrerelease: true } } => true,
_ => false
}).Where(p => MatchesVersionPrefix(SemVersion.Parse(p.Version)));
});

// When pinned to a specific version, override the version on each discovered package
// so the correct version gets installed regardless of what the feed reports as latest.
if (PinnedVersion is not null)
{
return filteredPackages.Select(p => new NuGetPackage { Id = p.Id, Version = PinnedVersion, Source = p.Source });
}

return filteredPackages;
}

public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(string packageId, DirectoryInfo workingDirectory, CancellationToken cancellationToken)
{
if (PinnedVersion is not null)
{
return [new NuGetPackage { Id = packageId, Version = PinnedVersion, Source = SourceDetails }];
}

var tasks = new List<Task<IEnumerable<NuGetPackage>>>();

using var tempNuGetConfig = Type is PackageChannelType.Explicit ? await TemporaryNuGetConfig.CreateAsync(Mappings!) : null;
Expand Down Expand Up @@ -170,7 +177,7 @@ public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(string packageId,
useCache: true, // Enable caching for package channel resolution
cancellationToken: cancellationToken);

return packages.Where(p => MatchesVersionPrefix(SemVersion.Parse(p.Version)));
return packages;
}

// When doing a `dotnet package search` the the results may include stable packages even when searching for
Expand All @@ -181,14 +188,14 @@ public async Task<IEnumerable<NuGetPackage>> GetPackagesAsync(string packageId,
{ Quality: PackageChannelQuality.Stable, SemVer: { IsPrerelease: false } } => true,
{ Quality: PackageChannelQuality.Prerelease, SemVer: { IsPrerelease: true } } => true,
_ => false
}).Where(p => MatchesVersionPrefix(SemVersion.Parse(p.Version)));
});

return filteredPackages;
}

public static PackageChannel CreateExplicitChannel(string name, PackageChannelQuality quality, PackageMapping[]? mappings, INuGetPackageCache nuGetPackageCache, bool configureGlobalPackagesFolder = false, string? cliDownloadBaseUrl = null, SemVersion? versionPrefix = null)
public static PackageChannel CreateExplicitChannel(string name, PackageChannelQuality quality, PackageMapping[]? mappings, INuGetPackageCache nuGetPackageCache, bool configureGlobalPackagesFolder = false, string? cliDownloadBaseUrl = null, string? pinnedVersion = null)
{
return new PackageChannel(name, quality, mappings, nuGetPackageCache, configureGlobalPackagesFolder, cliDownloadBaseUrl, versionPrefix);
return new PackageChannel(name, quality, mappings, nuGetPackageCache, configureGlobalPackagesFolder, cliDownloadBaseUrl, pinnedVersion);
}

public static PackageChannel CreateImplicitChannel(INuGetPackageCache nuGetPackageCache)
Expand Down
23 changes: 10 additions & 13 deletions src/Aspire.Cli/Packaging/PackagingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Aspire.Cli.Configuration;
using Aspire.Cli.NuGet;
using Microsoft.Extensions.Configuration;
using Semver;
using System.Reflection;

namespace Aspire.Cli.Packaging;
Expand Down Expand Up @@ -92,13 +91,13 @@ public Task<IEnumerable<PackageChannel>> GetChannelsAsync(CancellationToken canc
return null;
}

var versionPrefix = GetStagingVersionPrefix();
var pinnedVersion = GetStagingPinnedVersion(useSharedFeed);

var stagingChannel = PackageChannel.CreateExplicitChannel(PackageChannelNames.Staging, stagingQuality, new[]
{
new PackageMapping("Aspire*", stagingFeedUrl),
new PackageMapping(PackageMapping.AllPackages, "https://api.nuget.org/v3/index.json")
}, nuGetPackageCache, configureGlobalPackagesFolder: !useSharedFeed, cliDownloadBaseUrl: "https://aka.ms/dotnet/9/aspire/rc/daily", versionPrefix: versionPrefix);
}, nuGetPackageCache, configureGlobalPackagesFolder: !useSharedFeed, cliDownloadBaseUrl: "https://aka.ms/dotnet/9/aspire/rc/daily", pinnedVersion: pinnedVersion);

return stagingChannel;
}
Expand Down Expand Up @@ -166,20 +165,18 @@ private PackageChannelQuality GetStagingQuality()
return PackageChannelQuality.Stable;
}

private SemVersion? GetStagingVersionPrefix()
private string? GetStagingPinnedVersion(bool useSharedFeed)
{
var versionPrefixValue = configuration["stagingVersionPrefix"];
if (string.IsNullOrEmpty(versionPrefixValue))
// Only pin versions when using the shared feed and the config flag is set
var pinToCliVersion = configuration["stagingPinToCliVersion"];
if (!useSharedFeed || !string.Equals(pinToCliVersion, "true", StringComparison.OrdinalIgnoreCase))
{
return null;
}

// Parse "Major.Minor" format (e.g., "13.2") as a SemVersion for comparison
if (SemVersion.TryParse($"{versionPrefixValue}.0", SemVersionStyles.Strict, out var semVersion))
{
return semVersion;
}

return null;
// Get the CLI's own version and strip build metadata (+hash)
var cliVersion = Utils.VersionHelper.GetDefaultTemplateVersion();
var plusIndex = cliVersion.IndexOf('+');
return plusIndex >= 0 ? cliVersion[..plusIndex] : cliVersion;
}
}
4 changes: 3 additions & 1 deletion src/Shared/PackageUpdateHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ public static List<NuGetPackage> ParsePackageSearchResults(string stdout, string
{
var id = packageResult.GetProperty("id").GetString()!;

var version = packageResult.GetProperty("latestVersion").GetString()!;
var version = packageResult.TryGetProperty("latestVersion", out var latestVersionProp)
? latestVersionProp.GetString()!
: packageResult.GetProperty("version").GetString()!;

if (packageId == null || id == packageId)
{
Expand Down
Loading
Loading