Skip to content

Fix aspire init template install for non-stable CLI builds (#16654) - #16690

Merged
Mitch Denny (mitchdenny) merged 3 commits into
mainfrom
maddy/forward-port-init-channel-template-install-16654
May 2, 2026
Merged

Fix aspire init template install for non-stable CLI builds (#16654)#16690
Mitch Denny (mitchdenny) merged 3 commits into
mainfrom
maddy/forward-port-init-channel-template-install-16654

Conversation

@mitchdenny

Copy link
Copy Markdown
Member

Description

Forward-ports #16672 (the release/13.3 fix) to main. Fixes #16654aspire init was failing with exit code 103 (Aspire.ProjectTemplates::<version>+<sha> could not be installed, the package does not exist) on any C# repo containing a .sln/.slnx file when the CLI was a non-stable build (staging/daily/PR).

The bug exists on main as well as release/13.3. #16672 is the parallel PR targeting the release branch (CI green, awaiting review).

Root cause

InitCommand.DropCSharpProjectSkeletonAsync was running:

dotnet new install Aspire.ProjectTemplates@<cliVersion+sha> --force

…with nugetConfigFile: null and nugetSource: null, bypassing the channel feed wiring that aspire new uses. For non-stable builds, Aspire.ProjectTemplates@<cliVersion+sha> is published only to a per-commit darc feed (e.g. darc-pub-microsoft-aspire-<sha8>), which was never queried.

Fix

Extracted the channel-aware template package resolution and install logic out of DotNetTemplateFactory.ApplyTemplateAsync and into TemplateNuGetConfigService (the existing "NuGet glue for templates" service). Two new methods:

  • ResolveTemplatePackageAsync(TemplatePackageQuery, CancellationToken) — picks the right (NuGetPackage, PackageChannel) from IPackagingService. Verbatim move of the old private GetProjectTemplatesVersionAsync with one new opt-in flag (IncludePrHives).
  • InstallTemplatePackageAsync(TemplatePackageSelection, IDotNetCliRunner, ...) — generates the TemporaryNuGetConfig from the channel mappings (when explicit) and runs dotnet new install with the right --nuget-source and --configfile. Verbatim move of the install block from ApplyTemplateAsync.

Both DotNetTemplateFactory.ApplyTemplateAsync and InitCommand.DropCSharpProjectSkeletonAsync now call these helpers.

Behavior preservation for aspire new

  • extraArgsCallback still runs before template install but after channel resolution (preserves prompt/error precedence).
  • IncludePrHives: true keeps PR-hive widening on for aspire new.
  • KnownEmojis.Ice status emoji retained for aspire new.
  • All existing DotNetTemplateFactory and NewCommand tests pass unchanged.

Behavior changes for aspire init (all intentional improvements)

  • The version sent to dotnet new install is now the channel-resolved version (e.g. 13.4.0) instead of <cliVersion+sha>.
  • aspire init now honors the global channel configuration setting, matching aspire new.
  • On install failure, captured stdout/stderr is displayed before the error header.
  • ChannelNotFoundException/EmptyChoicesException/NuGetPackageCacheException produce friendly errors (returns FailedToInstallTemplates) instead of bubbling to the top-level "unexpected error" handler.
  • aspire init does NOT include PR hives (IncludePrHives: false) so a developer with stale ~/.aspire/hives/* doesn't get a different template than they would on a clean machine.

Implementation notes

  • TemplateNuGetConfigService is a singleton; IDotNetCliRunner is transient, so it's passed as a method parameter to InstallTemplatePackageAsync rather than injected (avoids the singleton-captures-transient lifetime trap).
  • The extraction is mechanical — channel resolution and install logic are byte-for-byte equivalent except for the IncludePrHives opt-in flag, which is an additive change gated to aspire init.

Forward-port notes

The two commits from #16672 cherry-picked cleanly onto main with one trivial conflict in TemplateNuGetConfigService.cs: main already gained CreateOrUpdateNuGetConfigWithoutPromptAsync from #16636 (single-file aspire init nuget.config drop). Resolved by keeping that method and appending the new ResolveTemplatePackageAsync / InstallTemplatePackageAsync methods alongside it. InitCommand.cs, NewCommandTests.cs, and CliTestHelper.cs auto-merged because the constructor field/registration changes from #16636 happened to be additive.

Tests added

In tests/Aspire.Cli.Tests/Commands/InitCommandTests.cs:

  1. InitCommand_WhenSolutionExistsAndChannelIsExplicit_PassesTemporaryNuGetConfigToTemplateInstall — repros the bug scenario (global channel = staging) and asserts the temp NuGet config is generated with the channel's mappings and passed to InstallTemplateAsync.
  2. InitCommand_WhenSolutionExistsAndChannelIsImplicit_LeavesNuGetConfigNull — asserts no temp NuGet config is generated for the implicit channel.
  3. InitCommand_WhenSolutionExistsAndPrHivesPresent_DoesNotWidenToAllChannels — asserts that stale PR hive directories are ignored by init (covers blocking finding from rubber-duck review).
  4. InitCommand_WhenChannelResolutionThrowsChannelNotFound_DisplaysFriendlyError — asserts a misconfigured channel surfaces a friendly error rather than an unexpected-error stack trace.
  5. InitCommand_WhenChannelTemplateSearchFails_DisplaysFriendlyError — covers the NuGetPackageCacheException catch.

Verification

Built the CLI from this branch and ran the targeted Aspire.Cli.Tests suite locally:

Aspire.Cli.Tests --filter-class *.InitCommandTests --filter-class *.NewCommandTests --filter-class *.DotNetTemplateFactoryTests
  total: 73, failed: 0, succeeded: 73, skipped: 0

Maddy Montaquila (@maddymontaquila) will verify locally on the original bitwarden/server repro once CI is green.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
  • Did you add public API?
    • No (only internal types — new records and methods on TemplateNuGetConfigService).
  • Does the change make any security assumptions or guarantees?
    • No
  • Does the change require an update in our Aspire docs?
    • No

…ilds (#16654)

�spire init ran `dotnet new install Aspire.ProjectTemplates@<cliVersion+sha>`
with `nugetConfigFile: null` and `nugetSource: null`, bypassing the channel
feed wiring used by `aspire new`. For non-stable CLI builds (staging/daily/PR),
`Aspire.ProjectTemplates@<cliVersion+sha>` is only available on a per-commit
darc feed (e.g. `darc-pub-microsoft-aspire-<sha8>`), so install failed with
exit code 103 in any C# repo containing a `.sln`.

Extract the channel-aware template package resolution and install logic out of
`DotNetTemplateFactory.ApplyTemplateAsync` and into `TemplateNuGetConfigService`
as `ResolveTemplatePackageAsync` and `InstallTemplatePackageAsync`. Both
`DotNetTemplateFactory` and `InitCommand` now consume the helper. The
existing `aspire new` install path is preserved bit-for-bit (extraction is
mechanical; `IncludePrHives: true` keeps PR-hive widening behavior).

For `aspire init` this means:
- The version sent to `dotnet new install` is now the channel-resolved one
  (e.g. `13.3.0`), not the raw `+sha` build metadata.
- Init now honors the global `channel` configuration, matching `aspire new`.
- On install failure, captured stdout/stderr is displayed before the error.
- `ChannelNotFoundException` and `EmptyChoicesException` produce friendly
  errors instead of bubbling to the top-level "unexpected error" handler.
- PR hives are intentionally NOT included in init's channel discovery so a
  developer with stale `~/.aspire/hives/*` doesn't get a different template
  than they'd get on a clean machine.

Notes:
- `TemplateNuGetConfigService` is a singleton; `IDotNetCliRunner` is
  transient and is therefore passed as a method parameter to
  `InstallTemplatePackageAsync` instead of being injected.
- New regression tests cover: explicit channel passes the temp NuGet config,
  implicit channel leaves it null, PR hives don't widen init, and channel
  resolution failures produce friendly errors.

Fixes #16654

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Multi-model code review caught the following issues:

1. Restore original order of operations in DotNetTemplateFactory.ApplyTemplateAsync.
   The first refactor moved extraArgsCallback ahead of template package resolution,
   which changed prompt/error precedence for `aspire new` (extra-args prompts
   like Redis-cache, test-framework, xUnit-version would now run before channel
   lookup, and answers would be discarded if resolution failed afterward).
   Restored the BEFORE order from release/13.3: ResolveTemplatePackageAsync
   first, then extraArgsCallback, then InstallTemplatePackageAsync. Updated the
   in-source comment to be accurate.

2. Catch NuGetPackageCacheException in InitCommand.DropCSharpProjectSkeletonAsync.
   The pre-extraction init code went straight to `dotnet new install` and
   never invoked a NuGet search, so feed search failures (offline, inaccessible
   feed, etc.) couldn't bubble up. After the extraction init now performs the
   search and was missing the catch, surfacing the failure as an unhandled
   "unexpected error". Added the catch with the same friendly-error treatment
   as ChannelNotFoundException / EmptyChoicesException.

3. Use TemplatingStrings.TemplateInstallationFailed in InitCommand for parity
   with `aspire new`. The previous ad-hoc string omitted the log file path,
   making post-mortem diagnosis harder.

4. Added a comment in InstallTemplatePackageAsync clarifying that the temporary
   NuGet config is intentionally disposed at the end of the install (only
   `dotnet new install` consumes it; the subsequent `dotnet new <template>`
   call uses the already-installed template hive and ambient NuGet config).

5. Added regression test InitCommand_WhenChannelTemplateSearchFails_DisplaysFriendlyError
   covering the new NuGetPackageCacheException catch.

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

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

🚀 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 -- 16690

Or

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 forward-ports the release/13.3 fix for #16654 to main, ensuring aspire init can install Aspire.ProjectTemplates for non-stable CLI builds by reusing the same channel-aware NuGet feed resolution logic as aspire new.

Changes:

  • Extracts template package channel/version resolution + dotnet new install invocation into TemplateNuGetConfigService, and updates both DotNetTemplateFactory and InitCommand to use it.
  • Improves aspire init error handling for channel resolution / package search failures and surfaces install output on failure.
  • Updates CLI test infrastructure and adds init-focused unit tests covering explicit/implicit channels, PR-hive suppression, and friendly errors.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs Registers TemplateNuGetConfigService for tests and updates DotNetTemplateFactory wiring to use DI-resolved instance.
tests/Aspire.Cli.Tests/TestServices/TestDotNetCliRunner.cs Extends install-template test callback signature to include nugetConfigFile.
tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs Updates factory construction to match new TemplateNuGetConfigService constructor.
tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs Updates template install callbacks for the new runner signature.
tests/Aspire.Cli.Tests/Commands/InitCommandTests.cs Adds coverage for init’s channel-aware template install behavior + friendly error paths.
src/Aspire.Cli/Templating/TemplateNuGetConfigService.cs Adds ResolveTemplatePackageAsync + InstallTemplatePackageAsync and supporting records for channel-aware template installation.
src/Aspire.Cli/Templating/DotNetTemplateFactory.cs Removes inlined channel/template install logic and delegates to TemplateNuGetConfigService.
src/Aspire.Cli/Commands/InitCommand.cs Switches init’s project-mode template install to channel-aware resolution + improved error reporting.

services.AddTransient(options.DotNetCliExecutionFactoryFactory);
services.AddTransient(options.DotNetCliRunnerFactory);
services.AddTransient(options.NuGetPackageCacheFactory);
services.AddSingleton<TemplateNuGetConfigService>();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — this is a forward-port artifact. The cherry-pick from #16672 added the registration on a new line at the bottom of the block, but main already had the same registration from #16636 a few lines up. Removed the duplicate in 6d08933 (kept the original main registration so the diff in this PR is purely additive elsewhere).

using Aspire.Cli.Scaffolding;
using Aspire.Cli.Tests.TestServices;
using Aspire.Cli.Tests.Utils;
using Aspire.Shared;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

False positive — Aspire.Shared is needed here. NuGetPackageCli (used at lines 34, 35, 435, 436, 554, 555, 560, 561, 627, 628 of this file) is declared in the Aspire.Shared namespace. The local build of this PR with TreatWarningsAsErrors=true succeeds with 0 warnings, which confirms it.

Cherry-pick from #16672 added services.AddSingleton<TemplateNuGetConfigService>()
to CliTestHelper.cs, but main already had the same registration from #16636. Drop the
duplicate (the cherry-picked one) so the service is only registered once.

Caught by Copilot PR review on #16690.

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

github-actions Bot commented May 2, 2026

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.

@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

🎬 CLI E2E Test Recordings — 76 recordings uploaded (commit 6d08933)

View all recordings
Status 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_UsesConfiguredToolchain ▶️ 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
DocsCommand_RendersInteractiveMarkdownFromLocalSource ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_TypeScriptAppHostReportsMissingConfiguredToolchain ▶️ 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
InteractiveCSharpInitCreatesExpectedFiles ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LatestCliCanStartStableChannelAppHost ▶️ View Recording
LatestCliCanStartStableChannelTypeScriptAppHost ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
OtelLogsReturnsStructuredLogsFromStarterAppCore ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RestoreGeneratesSdkFiles_WithConfiguredToolchain ▶️ 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
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View Recording

📹 Recordings uploaded automatically from CI run #25239849198

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.

Thanks Mitch!

@mitchdenny

Copy link
Copy Markdown
Member Author

/deployment-test

@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot had a problem deploying to deployment-testing May 2, 2026 06:34 Failure
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot had a problem deploying to deployment-testing May 2, 2026 06:34 Failure
@github-actions
github-actions Bot had a problem deploying to deployment-testing May 2, 2026 06:34 Failure
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot had a problem deploying to deployment-testing May 2, 2026 06:34 Failure
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot had a problem deploying to deployment-testing May 2, 2026 06:34 Failure
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot had a problem deploying to deployment-testing May 2, 2026 06:34 Failure
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 2, 2026 06:34 Inactive
@mitchdenny
Mitch Denny (mitchdenny) merged commit 0471f52 into main May 2, 2026
596 of 609 checks passed
@github-actions github-actions Bot added this to the 13.4 milestone May 2, 2026
@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Deployment E2E Tests failed — 27 passed, 6 failed, 0 cancelled

View test results and recordings

View workflow run

Test Result Recording
Deployment.EndToEnd-TypeScriptVnetSqlServerInfraDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-VnetSqlServerConnectivityDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-VnetSqlServerInfraDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AzureKeyVaultDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AzureStorageDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AzureLogAnalyticsDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaCompactNamingDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-TypeScriptExpressDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AuthenticationTests ✅ Passed
Deployment.EndToEnd-FrontDoorDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksVnetWithAzureResourcesDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksMultipleNodePoolsDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksStarterWithRedisHelmDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksBlazorRedisDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-KubernetesGatewayTlsDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaDeploymentErrorOutputTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaCustomRegistryDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaExistingRegistryDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AppServiceReactDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksVnetInfraDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-VnetStorageBlobInfraDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-NspStorageKeyVaultDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaManagedRedisDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksWithAzureResourcesDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AcaStarterDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AksStarterDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-AzureServiceBusDeploymentTests ✅ Passed ▶️ View Recording
Deployment.EndToEnd-VnetKeyVaultConnectivityDeploymentTests ❌ Failed ▶️ View Recording
Deployment.EndToEnd-AzureContainerRegistryDeploymentTests ❌ Failed ▶️ View Recording
Deployment.EndToEnd-AzureEventHubsDeploymentTests ❌ Failed ▶️ View Recording
Deployment.EndToEnd-VnetStorageBlobConnectivityDeploymentTests ❌ Failed ▶️ View Recording
Deployment.EndToEnd-VnetKeyVaultInfraDeploymentTests ❌ Failed ▶️ View Recording
Deployment.EndToEnd-AzureAppConfigDeploymentTests ❌ Failed ▶️ View Recording

@aspire-repo-bot

Copy link
Copy Markdown
Contributor

No documentation PR is required for this change.

This is an internal bug fix that corrects aspire init template installation for non-stable CLI builds (staging/daily/PR). From a user perspective on stable builds, behavior is unchanged. The fix involves only internal types (TemplateNuGetConfigService, TemplatePackageQuery, TemplatePackageSelection) with no new public APIs, no new CLI commands, and no configuration changes that stable-channel users would need to know about. The PR author also confirmed this in the checklist ("Does the change require an update in our Aspire docs? No").

Generated by PR Documentation Check for issue #16690 · ● 109.6K ·

Nell Shamrell-Harrington (nellshamrell) pushed a commit to nellshamrell/aspire that referenced this pull request May 18, 2026
…#16654) (microsoft#16690)

* [release/13.3] Fix aspire init template install for non-stable CLI builds (microsoft#16654)

�spire init ran `dotnet new install Aspire.ProjectTemplates@<cliVersion+sha>`
with `nugetConfigFile: null` and `nugetSource: null`, bypassing the channel
feed wiring used by `aspire new`. For non-stable CLI builds (staging/daily/PR),
`Aspire.ProjectTemplates@<cliVersion+sha>` is only available on a per-commit
darc feed (e.g. `darc-pub-microsoft-aspire-<sha8>`), so install failed with
exit code 103 in any C# repo containing a `.sln`.

Extract the channel-aware template package resolution and install logic out of
`DotNetTemplateFactory.ApplyTemplateAsync` and into `TemplateNuGetConfigService`
as `ResolveTemplatePackageAsync` and `InstallTemplatePackageAsync`. Both
`DotNetTemplateFactory` and `InitCommand` now consume the helper. The
existing `aspire new` install path is preserved bit-for-bit (extraction is
mechanical; `IncludePrHives: true` keeps PR-hive widening behavior).

For `aspire init` this means:
- The version sent to `dotnet new install` is now the channel-resolved one
  (e.g. `13.3.0`), not the raw `+sha` build metadata.
- Init now honors the global `channel` configuration, matching `aspire new`.
- On install failure, captured stdout/stderr is displayed before the error.
- `ChannelNotFoundException` and `EmptyChoicesException` produce friendly
  errors instead of bubbling to the top-level "unexpected error" handler.
- PR hives are intentionally NOT included in init's channel discovery so a
  developer with stale `~/.aspire/hives/*` doesn't get a different template
  than they'd get on a clean machine.

Notes:
- `TemplateNuGetConfigService` is a singleton; `IDotNetCliRunner` is
  transient and is therefore passed as a method parameter to
  `InstallTemplatePackageAsync` instead of being injected.
- New regression tests cover: explicit channel passes the temp NuGet config,
  implicit channel leaves it null, PR hives don't widen init, and channel
  resolution failures produce friendly errors.

Fixes microsoft#16654

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

* Address PR review feedback (microsoft#16672)

Multi-model code review caught the following issues:

1. Restore original order of operations in DotNetTemplateFactory.ApplyTemplateAsync.
   The first refactor moved extraArgsCallback ahead of template package resolution,
   which changed prompt/error precedence for `aspire new` (extra-args prompts
   like Redis-cache, test-framework, xUnit-version would now run before channel
   lookup, and answers would be discarded if resolution failed afterward).
   Restored the BEFORE order from release/13.3: ResolveTemplatePackageAsync
   first, then extraArgsCallback, then InstallTemplatePackageAsync. Updated the
   in-source comment to be accurate.

2. Catch NuGetPackageCacheException in InitCommand.DropCSharpProjectSkeletonAsync.
   The pre-extraction init code went straight to `dotnet new install` and
   never invoked a NuGet search, so feed search failures (offline, inaccessible
   feed, etc.) couldn't bubble up. After the extraction init now performs the
   search and was missing the catch, surfacing the failure as an unhandled
   "unexpected error". Added the catch with the same friendly-error treatment
   as ChannelNotFoundException / EmptyChoicesException.

3. Use TemplatingStrings.TemplateInstallationFailed in InitCommand for parity
   with `aspire new`. The previous ad-hoc string omitted the log file path,
   making post-mortem diagnosis harder.

4. Added a comment in InstallTemplatePackageAsync clarifying that the temporary
   NuGet config is intentionally disposed at the end of the install (only
   `dotnet new install` consumes it; the subsequent `dotnet new <template>`
   call uses the already-installed template hive and ambient NuGet config).

5. Added regression test InitCommand_WhenChannelTemplateSearchFails_DisplaysFriendlyError
   covering the new NuGetPackageCacheException catch.

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

* Remove duplicate TemplateNuGetConfigService DI registration

Cherry-pick from microsoft#16672 added services.AddSingleton<TemplateNuGetConfigService>()
to CliTestHelper.cs, but main already had the same registration from microsoft#16636. Drop the
duplicate (the cherry-picked one) so the service is only registered once.

Caught by Copilot PR review on microsoft#16690.

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

---------

Co-authored-by: Jose Perez Rodriguez <joperezr@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 1, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aspire init fails on repos with a .sln when CLI is a non-stable build (staging/daily/PR) — InitCommand bypasses channel feed wiring

3 participants