Skip to content

Fix PR channel version selection in Aspire CLI#16125

Open
sebastienros wants to merge 25 commits intomainfrom
sebastienros/sebros-fix-cli-prerelease-install
Open

Fix PR channel version selection in Aspire CLI#16125
sebastienros wants to merge 25 commits intomainfrom
sebastienros/sebros-fix-cli-prerelease-install

Conversation

@sebastienros
Copy link
Copy Markdown
Contributor

Description

PR-built CLI installs can see both stable feed results and PR hive packages at the same time. That let aspire add, aspire new, and aspire init drift to a stable or latest version instead of the exact PR build version, which caused mismatches like stable AppHost package requests against prerelease-only PR hives.

This change prefers the current CLI version whenever a pr-* channel or PR hive is in play across package and template selection. It updates the shared .NET template selection path as well, adds focused unit coverage for add, new, and init, and reuses the shared test packaging helper instead of another file-local implementation.

Fixes # N/A

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

Copilot AI review requested due to automatic review settings April 13, 2026 18:21
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 16125

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 16125"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes version drift when the Aspire CLI is running against PR hives / pr-* channels by preferring the currently installed CLI version during template and package selection, preventing mismatched stable vs PR-build dependencies.

Changes:

  • Prefer the current CLI/SDK version when resolving templates/packages from pr-* channels (and when PR hives are present for aspire add).
  • Add unit tests covering aspire new, aspire init, and aspire add PR-channel/PR-hive selection behavior.
  • Extend a shared test IPackagingService helper to allow test-controlled channel enumeration.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/Aspire.Cli.Tests/TestServices/TestPackagingService.cs Adds a callback hook to control GetChannelsAsync in tests.
tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs Adds coverage ensuring PR channel selects the current CLI version without prompting.
tests/Aspire.Cli.Tests/Commands/InitCommandTests.cs Adds coverage ensuring PR channel selects the current CLI version without prompting.
tests/Aspire.Cli.Tests/Commands/AddCommandTests.cs Adds coverage ensuring PR hives cause add to prefer the current CLI version.
src/Aspire.Cli/Utils/VersionHelper.cs Introduces IsPrChannel helper for consistent PR-channel detection.
src/Aspire.Cli/Templating/DotNetTemplateFactory.cs Prefers current CLI version when selecting templates from pr-* channels.
src/Aspire.Cli/Commands/NewCommand.cs Prefers current CLI version when resolving template version from a pr-* channel.
src/Aspire.Cli/Commands/InitCommand.cs Prefers current CLI version when selecting templates from pr-* channels.
src/Aspire.Cli/Commands/AddCommand.cs Prefers current CLI version for integration packages when PR hives are present.

Comment thread tests/Aspire.Cli.Tests/Commands/InitCommandTests.cs
Comment thread src/Aspire.Cli/Utils/VersionHelper.cs
@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

@eerhardt
Copy link
Copy Markdown
Member

/deployment-test

1 similar comment
@eerhardt
Copy link
Copy Markdown
Member

/deployment-test

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Deployment tests starting on PR #16125...

This will deploy to real Azure infrastructure. Results will be posted here when complete.

View workflow run

@eerhardt
Copy link
Copy Markdown
Member

/deployment-test

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Deployment tests starting on PR #16125...

This will deploy to real Azure infrastructure. Results will be posted here when complete.

View workflow run

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Deployment tests starting on PR #16125...

This will deploy to real Azure infrastructure. Results will be posted here when complete.

View workflow run

@github-actions github-actions bot temporarily deployed to deployment-testing April 14, 2026 01:37 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing April 14, 2026 01:37 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing April 14, 2026 01:37 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing April 14, 2026 01:37 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing April 14, 2026 01:37 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing April 14, 2026 01:37 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing April 14, 2026 01:37 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing April 14, 2026 01:37 Inactive
@github-actions github-actions bot temporarily deployed to deployment-testing April 14, 2026 01:37 Inactive
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros sebastienros force-pushed the sebastienros/sebros-fix-cli-prerelease-install branch from 70dbd27 to 36327f3 Compare April 15, 2026 15:43
sebastienros and others added 3 commits April 15, 2026 10:05
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ix-cli-prerelease-install

# Conflicts:
#	tests/Aspire.Deployment.EndToEnd.Tests/AcaCompactNamingUpgradeDeploymentTests.cs
sebastienros and others added 2 commits April 16, 2026 15:46
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

sebastienros and others added 3 commits April 16, 2026 16:48
Reproduced the missing SDK source-mapping failure locally before reapplying this change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

Copy link
Copy Markdown
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 13 changed files (982 additions, 20 deletions). Found 1 issue (resilience/bug category).

Comment thread src/Aspire.Cli/Commands/AddCommand.cs Outdated
Comment on lines +386 to +399
using var projectDocument = _fallbackProjectParser.ParseProject(appHostProjectFile);
if (!projectDocument.RootElement.TryGetProperty("Properties", out var properties) ||
!properties.TryGetProperty("AspireHostingSDKVersion", out var sdkVersionElement))
{
return packageIds;
}

var sdkVersion = sdkVersionElement.GetString();
if (!string.Equals(sdkVersion, selectedPackageVersion, StringComparison.OrdinalIgnoreCase))
{
return packageIds;
}

packageIds.Add("Aspire.AppHost.Sdk");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_fallbackProjectParser.ParseProject(appHostProjectFile) throws ProjectUpdaterException if the project file is malformed or unreadable. This propagates through the NuGet config creation block (lines 216–223) and hits the generic catch (Exception ex) in ExecuteAsync, failing the entire aspire add command with a misleading error about package addition failure.

The method already handles the "file doesn't exist" case gracefully (line 381) but not the "file exists but can't be parsed" case. Since determining whether to include Aspire.AppHost.Sdk in scoped mappings is a best-effort enhancement, a parsing failure should not prevent the package from being added.

Consider wrapping the ParseProject call in a try-catch and returning packageIds (just the selected package) on failure, consistent with the existing "file doesn't exist" fallback:

JsonDocument? projectDocument;
try
{
    projectDocument = _fallbackProjectParser.ParseProject(appHostProjectFile);
}
catch (ProjectUpdaterException)
{
    return packageIds;
}

using (projectDocument)
{
    // existing property checks...
}


private static HashSet<string> GetScopedPackageIds(string source, IEnumerable<string> packageIds)
{
var resolvedPackageIds = new HashSet<string>(packageIds, StringComparer.OrdinalIgnoreCase);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a NuGetPackageId comparer and comparison to known comparers file. It would be OrdinalIgnoreCase still, but centralizing values would help having consistent comparison be used.

Update code to use central value.

Comment on lines +259 to +263
.GroupBy(metadata => metadata.PackageId, StringComparer.OrdinalIgnoreCase)
.ToDictionary(
group => group.Key,
group => group.OrderByDescending(metadata => metadata.Version, SemVersion.PrecedenceComparer).First(),
StringComparer.OrdinalIgnoreCase);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More places to use the comparer

Comment on lines +322 to +330
return [];
}
catch (InvalidDataException)
{
return [];
}
catch (System.Xml.XmlException)
{
return [];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be at least some debug level logging.

Comment on lines +870 to +871
private sealed class CallbackNuGetPackageCache(
Func<DirectoryInfo, bool, FileInfo?, CancellationToken, Task<IEnumerable<Aspire.Shared.NuGetPackageCli>>> getTemplatePackagesAsyncCallback) : INuGetPackageCache
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a shared test type. Trying to avoid many copies.

@mitchdenny
Copy link
Copy Markdown
Member

Wouldn't we still want to be able to do aspire new and target the latest released version of aspire hosting bits even with a PR CLI?

mitchdenny
mitchdenny previously approved these changes Apr 20, 2026
Copy link
Copy Markdown
Member

@mitchdenny mitchdenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the core fix — the version selection logic changes look correct and consistent across all command flows (add, new, init, template). Making channelName and hasPrHives required parameters on TryGetCurrentCliVersionMatch was the right call to prevent future callers from forgetting to pass them.

This should fix the stale package version mixing that causes the IContainerRuntime/AcrLoginService DI failure (#16229).

Note: @JamesNK has some open feedback items (ParseProject error handling, NuGet ID comparer centralization, debug logging, shared test type) that should be addressed before merge.

mitchdenny added a commit that referenced this pull request Apr 20, 2026
PythonFastApi, AppServicePython, and AcrPurgeTask deployment tests fail
with 'Unable to resolve service for type IContainerRuntime while
attempting to activate AcrLoginService' due to stable/dev package
version mixing during aspire deploy.

Tracked by #16229, fix incoming via PR #16125.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mitchdenny and others added 3 commits April 20, 2026 18:56
- Wrap ParseProject in try-catch for ProjectUpdaterException in
  AddCommand.GetScopedPrHivePackageIds so a malformed project file
  does not crash the entire 'aspire add' command (best-effort check).

- Add NuGetPackageId to the shared StringComparers/StringComparisons
  file and use it consistently in PackageChannel.cs for all NuGet
  package ID comparisons instead of inline StringComparer.OrdinalIgnoreCase.

- Add debug-level logging to the catch blocks in
  PackageChannel.GetDependencyPackageIds so IO, invalid data, and XML
  parse errors are no longer silently swallowed. Thread an optional
  ILogger through PackageChannel and wire it from PackagingService via DI.

- Extract the file-local CallbackNuGetPackageCache from InitCommandTests
  into a shared test service under TestServices/ to reduce duplication.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaced by WaitForAspireAddSuccessAsync during merge conflict resolution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mitchdenny added a commit that referenced this pull request Apr 20, 2026
* Unify deployment E2E install helpers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Standardize deployment E2E CLI install

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix deployment E2E test failures

- Bump Azure.Identity from 1.13.2 to 1.18.0 in FoundryHostedAgentDeploymentTests
  to fix NU1605 package downgrade error
- Increase AcaManagedRedis test timeout from 30min to 40min and pipeline wait
  from 30min to 35min to accommodate slow Azure Redis provisioning
- Increase AppServiceReact pipeline wait from 30min to 35min to accommodate
  slow Azure App Service provisioning

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix bundle layout for deployment E2E tests

The deployment workflow only packaged the CLI binary and NuGet packages
but not the managed AppHost server (aspire-managed). Tests that use
InstallCurrentBuildAspireBundleAsync (TypeScript, Python, AppService,
AcrPurge templates) require a valid bundle layout with managed/ and
dcp/ directories for layout discovery to succeed.

- Copy Aspire.Managed build output into CLI artifacts
- Place managed server at ~/.aspire/managed/ during install
- Create dcp/ directory stub for layout validation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Disable Python deployment tests blocked by IContainerRuntime issue

PythonFastApi, AppServicePython, and AcrPurgeTask deployment tests fail
with 'Unable to resolve service for type IContainerRuntime while
attempting to activate AcrLoginService' due to stable/dev package
version mixing during aspire deploy.

Tracked by #16229, fix incoming via PR #16125.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Disable Foundry test (quota), fix upgrade test backup/restore

- Disable FoundryHostedAgentDeploymentTests pending Azure AI quota
  allocation (#16330)
- Fix AcaCompactNamingUpgradeDeploymentTests: backup/restore now
  includes managed/ and dcp/ directories so the dev bundle layout
  survives the GA CLI install/restore cycle

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Disable AcaCompactNamingUpgradeDeploymentTests (#16332)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Mitch Denny <midenn@microsoft.com>
mitchdenny and others added 4 commits April 20, 2026 22:04
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The scoped NuGet config mapped Aspire* packages exclusively to the PR
hive directory, which broke transitive RID-specific dependencies like
Aspire.Hosting.Orchestration.linux-x64 that aren't included in the
hive. The version selection fix (TryGetCurrentCliVersionMatch) already
ensures the correct PR version is selected without needing the scoped
config.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of scoping to individual package IDs (which missed transitive
RID-specific deps like Aspire.Hosting.Orchestration.linux-x64), pass
the original channel with its Aspire* wildcard mapping. This ensures
all Aspire packages can resolve from the PR hive while still allowing
fallback to NuGet.org via the * mapping.

Also removes the GetScopedPrHivePackageIds method and
CreateScopedChannelForPackages call which are no longer needed, and
updates the test to verify the Aspire* wildcard is present.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny mitchdenny dismissed their stale review April 20, 2026 14:30

Revoking my own approval since I've made a bunch of changes and want a second pair of eyes around intent.

@github-actions
Copy link
Copy Markdown
Contributor

🎬 CLI E2E Test Recordings — 72 recordings uploaded (commit 72f579a)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
Banner_NotDisplayedWithNoLogoFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJavaEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateJavaAppHostWithViteApp ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DashboardRunWithOtelTracesReturnsNoTraces ▶️ View Recording
DeployK8sBasicApiService ▶️ View Recording
DeployK8sWithGarnet ▶️ View Recording
DeployK8sWithMongoDB ▶️ View Recording
DeployK8sWithMySql ▶️ View Recording
DeployK8sWithPostgres ▶️ View Recording
DeployK8sWithRabbitMQ ▶️ View Recording
DeployK8sWithRedis ▶️ View Recording
DeployK8sWithSqlServer ▶️ View Recording
DeployK8sWithValkey ▶️ View Recording
DeployTypeScriptAppToKubernetes ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DetachFormatJsonProducesValidJsonWhenRestartingExistingInstance ▶️ View Recording
DoListStepsShowsPipelineSteps ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
OtelLogsReturnsStructuredLogsFromStarterApp ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RestoreRefreshesGeneratedSdkAfterAddingIntegration ▶️ View Recording
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes ▶️ View Recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View Recording

📹 Recordings uploaded automatically from CI run #24672158800

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants