Skip to content

Make template discovery async in aspire new command#14870

Merged
mitchdenny merged 2 commits intorelease/13.2from
fix/async-template-discovery-14805
Mar 4, 2026
Merged

Make template discovery async in aspire new command#14870
mitchdenny merged 2 commits intorelease/13.2from
fix/async-template-discovery-14805

Conversation

@mitchdenny
Copy link
Member

@mitchdenny mitchdenny commented Mar 3, 2026

Description

Make template discovery in aspire new asynchronous instead of blocking at CLI startup.

Problem: NewCommand's constructor called templateProvider.GetTemplatesAsync(CancellationToken.None).GetAwaiter().GetResult() which synchronously blocked on async template discovery (including a .NET SDK availability check) every time the command was instantiated.

Solution: Separate template definition (sync) from template availability (async):

  • Add a synchronous GetTemplates() method to ITemplateFactory / ITemplateProvider that returns template definitions without performing any I/O or async work
  • NewCommand constructor uses GetTemplates() for subcommand registration — no blocking
  • NewCommand.ExecuteAsync uses the existing GetTemplatesAsync() to resolve which templates are actually available at runtime (e.g. .NET SDK check)
  • If a user selects a template subcommand that isn't available at runtime, a clear error is displayed
  • Template subcommands and per-template help output are fully preserved

Validation: All 1,395 Aspire.Cli.Tests pass (0 failures, 2 skipped).

Fixes #14805

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
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
    • No
  • Does the change require an update in our Aspire docs?
    • Yes
    • No

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 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/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14870

Or

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

Copy link
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 updates the aspire new command so template discovery happens asynchronously at execution time (instead of synchronously during CLI construction), avoiding startup-time blocking and .NET SDK checks when aspire new isn’t invoked.

Changes:

  • Replaces template subcommands with a template-name positional argument and loads templates in NewCommand.ExecuteAsync.
  • Consolidates shared template-related options into TemplateOptions and registers them on NewCommand.
  • Removes the now-unneeded TemplateCommand type and updates tests/resources for the new CLI shape.

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/Aspire.Cli.Tests/Commands/SdkInstallerTests.cs Updates expectations/comments to reflect template-name argument behavior.
tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs Updates tests to assert no subcommands and presence of template-name argument.
src/Aspire.Cli/Templating/TemplateOptions.cs Introduces shared Option definitions for template-specific flags/options.
src/Aspire.Cli/Templating/DotNetTemplateFactory.cs Switches to shared options from TemplateOptions.
src/Aspire.Cli/Templating/CliTemplateFactory.cs Switches to shared options from TemplateOptions.
src/Aspire.Cli/Resources/xlf/NewCommandStrings.*.xlf Adds localized resource entry for the new template-name argument description.
src/Aspire.Cli/Resources/NewCommandStrings.resx Adds TemplateNameArgumentDescription string resource.
src/Aspire.Cli/Resources/NewCommandStrings.Designer.cs Adds strongly-typed accessor for TemplateNameArgumentDescription.
src/Aspire.Cli/Commands/TemplateCommand.cs Removes template-subcommand implementation.
src/Aspire.Cli/Commands/NewCommand.cs Moves template discovery to async execution; adds template-name argument and shared options.
Files not reviewed (1)
  • src/Aspire.Cli/Resources/NewCommandStrings.Designer.cs: Language not supported

Separate template definition (sync) from template availability
(async) so that NewCommand can register template subcommands without
blocking on async I/O in the constructor.

- Add sync GetTemplates() to ITemplateFactory/ITemplateProvider that
  returns template definitions without performing runtime checks
- Keep GetTemplatesAsync() for execution-time availability filtering
  (e.g. .NET SDK availability check)
- NewCommand constructor uses GetTemplates() for subcommand registration
- NewCommand.ExecuteAsync uses GetTemplatesAsync() to resolve which
  templates are actually available at runtime
- Template subcommands and per-template help output are preserved

Fixes #14805

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny mitchdenny force-pushed the fix/async-template-discovery-14805 branch from b5f52d9 to 92223da Compare March 3, 2026 03:49
@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

🎬 CLI E2E Test Recordings

The following terminal recordings are available for commit 2177294:

Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AgentInitCommand_WithMalformedMcpJson_ShowsErrorAndExitsNonZero ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
CreateAndDeployToDockerCompose ▶️ View Recording
CreateAndDeployToDockerComposeInteractive ▶️ View Recording
CreateAndPublishToKubernetes ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateEmptyAppHostProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateStartWaitAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording

📹 Recordings uploaded automatically from CI run #22653991422

@mitchdenny
Copy link
Member Author

@davidfowl This is one area where aspire template refresh would help. I know we punted the aspire template * and git templates work to 13.3 in Monday's meeting, but it might be worth pulling aspire template refresh in early — that way sync calls to get templates always just work off the locally cached template information.

Description = TemplatingStrings.EnterXUnitVersion_Description
};

public IEnumerable<ITemplate> GetTemplates()
Copy link
Contributor

Choose a reason for hiding this comment

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

These template show up as subcommands even when they are not applicable?

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in 2177294GetTemplates() now checks IsDotNetOnPath() first. If dotnet isn't found on the system PATH or in the private SDK installation, it returns empty and the .NET templates won't be registered as subcommands.

@davidfowl
Copy link
Contributor

PR Testing Results

I tested this PR build (13.2.0-pr.14870.g92223dae) on a Linux environment without the .NET SDK installed, which is the interesting case for this change.

What works well

  • aspire new --help correctly lists all 5 template subcommands (sync registration ✅)
  • aspire new aspire-starter gives a clear error: ❌ Template 'aspire-starter' is not available. Ensure the required runtime is installed. (exit code 1 ✅)
  • aspire new interactive prompt correctly shows only the 2 TypeScript templates that are actually available (async filtering ✅)

Concern: all templates registered as subcommands even when unavailable

The core tradeoff of this PR is that aspire new --help now advertises subcommands that may not work. On my environment (no .NET SDK), help shows:

Commands:
  aspire-starter        Starter App (ASP.NET Core/Blazor)
  aspire-ts-cs-starter  Starter App (ASP.NET Core/React)
  aspire-py-starter     Starter App (FastAPI/React)
  aspire-ts-starter     Starter App (Express/React)
  aspire-empty          Empty AppHost

A user could see aspire-starter in help, run aspire new aspire-starter, and hit the error. The error message is reasonable, but the UX of listing commands that don't work is a bit surprising. Worth considering whether that's acceptable or if the help output should indicate which templates require additional runtimes (e.g. a note or grouping).

Add IsDotNetOnPath() sync check to DotNetTemplateFactory.GetTemplates()
so .NET templates are only registered as subcommands when dotnet is
found on PATH or via the private SDK installation. This addresses the
UX concern where 'aspire new --help' advertised .NET templates even on
systems without the .NET SDK installed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny
Copy link
Member Author

Updated in 2177294DotNetTemplateFactory.GetTemplates() now does a fast sync filesystem check (IsDotNetOnPath()) before returning .NET templates. If dotnet isn't found on PATH or in the private SDK installation, .NET templates are excluded from subcommand registration.

Tested locally on Linux:

  • With dotnet on PATH: all 5 templates appear in aspire new --help
  • With dotnet removed from PATH: only TypeScript templates appear (aspire-ts-starter, aspire-empty) ✅
  • aspire new aspire-starter subcommand creates project successfully ✅

The full async SDK version check in GetTemplatesAsync() is preserved for execution time — so if dotnet is on PATH but the required SDK version isn't installed, the user gets a clear error at runtime.

@mitchdenny mitchdenny merged commit e870f47 into release/13.2 Mar 4, 2026
758 of 761 checks passed
@mitchdenny mitchdenny deleted the fix/async-template-discovery-14805 branch March 4, 2026 04:31
@dotnet-policy-service dotnet-policy-service bot added this to the 13.2 milestone Mar 4, 2026
Copilot AI pushed a commit that referenced this pull request Mar 10, 2026
* Make template discovery async in aspire new command

Separate template definition (sync) from template availability
(async) so that NewCommand can register template subcommands without
blocking on async I/O in the constructor.

- Add sync GetTemplates() to ITemplateFactory/ITemplateProvider that
  returns template definitions without performing runtime checks
- Keep GetTemplatesAsync() for execution-time availability filtering
  (e.g. .NET SDK availability check)
- NewCommand constructor uses GetTemplates() for subcommand registration
- NewCommand.ExecuteAsync uses GetTemplatesAsync() to resolve which
  templates are actually available at runtime
- Template subcommands and per-template help output are preserved

Fixes #14805

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

* Filter .NET templates from subcommands when dotnet is not available

Add IsDotNetOnPath() sync check to DotNetTemplateFactory.GetTemplates()
so .NET templates are only registered as subcommands when dotnet is
found on PATH or via the private SDK installation. This addresses the
UX concern where 'aspire new --help' advertised .NET templates even on
systems without the .NET SDK installed.

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

---------

Co-authored-by: Mitch Denny <mitch@mitchdeny.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

4 participants