Skip to content

Prefer current CLI template version for aspire new#17564

Merged
davidfowl merged 2 commits into
release/13.4from
sebros/fix-aspire-new-version-pinning
May 27, 2026
Merged

Prefer current CLI template version for aspire new#17564
davidfowl merged 2 commits into
release/13.4from
sebros/fix-aspire-new-version-pinning

Conversation

@sebastienros
Copy link
Copy Markdown
Contributor

@sebastienros sebastienros commented May 27, 2026

Description

Fixes a release/13.4 aspire new version selection issue where a shipped 13.4 CLI using an explicit channel such as --channel daily could float to the newest Aspire.ProjectTemplates package from that channel, for example a 13.5 preview. That caused the bundled 13.4 AppHost server to restore/load 13.5 TypeScript codegen dependencies and fail with Aspire.TypeSystem, Version=13.5.0.0 loader errors followed by No language support found for: typescript/nodejs.

The fix keeps CLI-runtime templates pinned to the current bundled CLI/SDK version after a channel is selected. If the channel search returns the exact current version, that package is selected. If prerelease channel search filters out the shipped stable-shaped version, aspire new still passes the current CLI/SDK version through so restore uses packages that match the bundled server. Local/PR hive behavior remains on the existing exact-match path.

User-facing usage

A shipped 13.4 CLI now keeps generated TypeScript AppHosts on 13.4 instead of resolving newer daily packages:

aspire new aspire-ts-empty -n ExplicitTsEmptyFull -o ExplicitTsEmptyFull --channel daily --non-interactive

Tests

  • dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-restore -- --filter-class Aspire.Cli.Tests.Commands.NewCommandChannelResolutionTests --filter-class Aspire.Cli.Tests.Commands.NewCommandTests --filter-class Aspire.Cli.Tests.Utils.VersionHelperTests --no-progress
  • dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-restore -- --no-progress

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

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 27, 2026 22:52
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 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 -- 17564

Or

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

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 updates Aspire CLI template resolution so aspire new can prefer the installed CLI/SDK version when resolving templates from selected channels, avoiding mismatches with newer daily/staging template packages.

Changes:

  • Broadened current CLI version matching when a channel name is available.
  • Adjusted prerelease template package filtering to retain the current CLI/SDK version.
  • Added regression tests for named-channel version selection in aspire new and VersionHelper.

Reviewed changes

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

File Description
src/Aspire.Cli/Utils/VersionHelper.cs Updates current-version matching eligibility for selected channels.
src/Aspire.Cli/Packaging/PackageChannel.cs Keeps the current CLI/SDK version in prerelease template package results.
tests/Aspire.Cli.Tests/Utils/VersionHelperTests.cs Adds unit coverage for named-channel matching behavior.
tests/Aspire.Cli.Tests/Commands/NewCommandChannelResolutionTests.cs Adds CLI channel-resolution regression coverage for daily/staging selection.

{ Quality: PackageChannelQuality.Both } => true,
{ Quality: PackageChannelQuality.Stable, SemVer: { IsPrerelease: false } } => true,
{ Quality: PackageChannelQuality.Prerelease, SemVer: { IsPrerelease: true } } => true,
{ Quality: PackageChannelQuality.Prerelease, SemVer: { IsPrerelease: false } } when string.Equals(p.Version, currentCliVersion, StringComparison.OrdinalIgnoreCase) => true,
Ensure CLI-runtime templates selected from explicit package channels use the current bundled CLI/SDK version instead of floating to newer channel packages that can mismatch the bundled AppHost server.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidfowl davidfowl merged commit 3f0998a into release/13.4 May 27, 2026
307 of 310 checks passed
@davidfowl davidfowl deleted the sebros/fix-aspire-new-version-pinning branch May 27, 2026 23:30
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.4 milestone May 27, 2026
aspire-repo-bot Bot added a commit to microsoft/aspire.dev that referenced this pull request May 27, 2026
When --channel is specified, aspire new now pins the template version
to the current installed CLI/SDK version, preventing version mismatches
where pre-release channel feeds could otherwise float to a newer
template package (e.g. 13.5 preview when the CLI is 13.4).

Documents changes from microsoft/aspire#17564.

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

Pull request created: #1106

Generated by PR Documentation Check

@aspire-repo-bot
Copy link
Copy Markdown
Contributor

📝 Documentation has been drafted in microsoft/aspire.dev#1106 targeting release/13.4.

Updated src/frontend/src/content/docs/reference/cli/commands/aspire-new.mdx to expand the --channel option description. The update explains that aspire new pins template packages to the current installed CLI/SDK version when a channel is specified, preventing version mismatches where pre-release channels could otherwise float to newer template packages (e.g. a 13.4 CLI pulling 13.5 preview templates via --channel daily).

Note

This draft PR needs human review before merging.

danegsta added a commit that referenced this pull request May 28, 2026
PR #17564 introduced a new fallback in TryGetCurrentCliTemplateVersionPackage
that pins the template package to the running CLI's SDK version whenever the
selected channel is explicit and not local-build. That preserved the intended
fix for prerelease channels (daily, staging) where the channel feed filters
the running CLI's package out of search results even though the feed can
still restore it.

But it also fired for the stable channel, where the filter does not apply.
For a non-stable-shape CLI (PR build like '13.4.0-pr.X.gY' or a daily-shape
preview) invoked with '--channel stable', it forced an unpublishable version
into the generated apphost.cs, breaking the SmokeTests
LatestCliCanStartStableChannelAppHost and
LatestCliCanStartStableChannelTypeScriptAppHost.

Exclude the stable channel from the new fallback so that case falls through
to the OrderByDescending picker and the user gets the highest shipped stable
package they explicitly asked for. The original prerelease-channel motivation
('Aspire.TypeSystem version mismatch when 13.4 CLI floats templates to a
13.5 daily preview') is preserved unchanged.

Test updates:
 - NewCommandChannelResolutionTests.NewCommand_NoChannelArg_ResolvesTemplateFromIdentityChannel
   gets an expectedVersion theory parameter so the daily case still pins to
   the CLI version while the stable case asserts the highest shipped stable.
 - NewCommandChannelResolutionTests.NewCommand_ExplicitChannelArg_OverridesIdentityChannel
   asserts highest shipped stable (matching the SmokeTest contract).
 - New NewCommand_ExplicitStableChannel_NonStableCliVersion_FallsBackToHighestShippedStable
   regression test covers both daily-shape and PR-shape CLI identities.
 - NewCommandTests.NewCommandWithTypeScriptEmptyTemplatePassesResolvedVersionAndChannelToScaffolding
   reverts to its pre-#17564 assertion of '9.2.0' from the stable feed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
joperezr pushed a commit that referenced this pull request May 28, 2026
…Folder path (#17573)

* Stabilize PrebuiltAppHostServer staging globalPackagesFolder path

PackagingService creates the staging channel with
ConfigureGlobalPackagesFolder=true so each darc/override feed restore
lands in an isolated cache (two staging builds of the same release
branch ship as 13.4.0 but from different feeds, and NuGet keys by
(id,version) only). PrebuiltAppHostServer was wiring that flag into a
TemporaryNuGetConfig whose default globalPackagesFolder value is the
relative '.nugetpackages' -- NuGet resolved it under the temp config's
own directory, BundleNuGetService baked those temp paths into
integration-package-probe-manifest.json, and TemporaryNuGetConfig.Dispose
then recursively deleted the cache out from under the manifest. On macOS
osx-arm64 polyglot staging builds this surfaced as a hang during DI /
assembly loading in aspire-managed.

Preserve the per-feed cache isolation behavior and anchor the override
at a stable absolute path instead:

  <ASPIRE_HOME>/.nugetpackages/<first-8-of-CLI-commit-sha>

Keying by the truncated commit hash matches the existing
darc-pub-microsoft-aspire-<hash> feed URL convention in
PackagingService.GetStagingFeedUrl, so the cache key and feed key stay
aligned at 8 hex chars. 8 chars is short enough to avoid Windows
MAX_PATH blow-ups on deep integration cache trees while keeping SHA
collisions negligible. The cache lives under ASPIRE_HOME (not the
per-AppHost working directory) so multiple AppHosts on the same machine
running against the same staging build can share a single restore --
the unit of isolation here is the staging build, not the individual
restore command.

Mechanics:
- TemporaryNuGetConfig.CreateAsync now accepts an optional
  globalPackagesFolderValue and propagates it through
  AddGlobalPackagesFolderToConfigAsync into the merger.
- NuGetConfigMerger.AddGlobalPackagesFolderConfiguration takes the
  optional override and falls back to the workspace-relative default
  ('.nugetpackages') for the non-temp workspace-merge path.
- PrebuiltAppHostServer.ResolveStableGlobalPackagesFolder routes both
  temp config branches (channel and package-source-override) through
  the new helper.
- VersionHelper.TryGetCurrentCommitHashShort surfaces the truncated
  SHA from the running CLI's AssemblyInformationalVersion (returns
  null on clean release builds with no '+sha' suffix; callers fall
  back to 'default').
- CliPathHelper centralizes the '<ASPIRE_HOME>/.nugetpackages' path so
  the producer (PrebuiltAppHostServer) and consumer (CacheCommand)
  can't drift.
- CacheCommand.ClearCommand now wipes <ASPIRE_HOME>/.nugetpackages so
  a wedged staging restore is recoverable through the same UX as every
  other CLI cache.

Tests:
- Updated existing PrebuiltAppHostServerTests staging cases to assert
  the globalPackagesFolder value is absolute and lives outside the
  temp config directory.
- New PrebuiltAppHostServerTests case wires a real PackagingService
  with overrideStagingFeed on a stable-shaped CLI and pins the same
  invariant end-to-end.
- New TemporaryNuGetConfigTests for the override propagation and for
  the no-override-when-disabled invariant.
- New CacheCommandTests covering the staging cache wipe and the
  missing-cache no-op path.

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

* Key staging globalPackagesFolder cache by feed URL hash

The staging globalPackagesFolder fix in the previous commit keyed the
\`.nugetpackages\` subdirectory off the CLI's own commit SHA (first 8
hex chars of \`AssemblyInformationalVersionAttribute\`). That handles
two darc-shipped staging builds of the same release branch, but it
breaks the local-dev case where one CLI is repeatedly retargeted at
different \`overrideStagingFeed\` values: the URL changes but the SHA
doesn't, so every override silently shares one cache bucket and the
second restore reuses the first feed's now-stale \`13.4.0\` assemblies.

Switch the cache key to the first 8 hex chars of \`XxHash3\` over the
trimmed, lower-cased resolved feed URL:

- Override branch passes the explicit \`--source\` URL.
- Channel branch passes the channel's \`Aspire*\` mapping source (or
  the first mapping for forward compatibility).

Trim + lower-case before hashing so a stray whitespace from a config
file or a hostname-case change doesn't fragment the cache.
Non-cryptographic hashing is fine here — the key is a directory name,
not a security boundary — and 8 hex chars keep deep integration cache
paths well under Windows MAX_PATH while giving ~4 billion buckets, so
collisions are negligible across the handful of staging feeds any user
ever sees.

Removes the now-unused \`VersionHelper.TryGetCurrentCommitHashShort\`
helper (added in the previous commit, no remaining callers).

Tests:
- New \`CliPathHelperTests.ComputeStagingFeedCacheKey_*\` cover
  determinism, normalization, length defaults, and null/empty input.
- Existing \`PrebuiltAppHostServerTests\` strengthened to assert the
  emitted globalPackagesFolder equals \`<aspireHome>/.nugetpackages/<hash(feed)>\`.

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

* Narrow #17564 CLI-pin fallback to prerelease channels

PR #17564 introduced a new fallback in TryGetCurrentCliTemplateVersionPackage
that pins the template package to the running CLI's SDK version whenever the
selected channel is explicit and not local-build. That preserved the intended
fix for prerelease channels (daily, staging) where the channel feed filters
the running CLI's package out of search results even though the feed can
still restore it.

But it also fired for the stable channel, where the filter does not apply.
For a non-stable-shape CLI (PR build like '13.4.0-pr.X.gY' or a daily-shape
preview) invoked with '--channel stable', it forced an unpublishable version
into the generated apphost.cs, breaking the SmokeTests
LatestCliCanStartStableChannelAppHost and
LatestCliCanStartStableChannelTypeScriptAppHost.

Exclude the stable channel from the new fallback so that case falls through
to the OrderByDescending picker and the user gets the highest shipped stable
package they explicitly asked for. The original prerelease-channel motivation
('Aspire.TypeSystem version mismatch when 13.4 CLI floats templates to a
13.5 daily preview') is preserved unchanged.

Test updates:
 - NewCommandChannelResolutionTests.NewCommand_NoChannelArg_ResolvesTemplateFromIdentityChannel
   gets an expectedVersion theory parameter so the daily case still pins to
   the CLI version while the stable case asserts the highest shipped stable.
 - NewCommandChannelResolutionTests.NewCommand_ExplicitChannelArg_OverridesIdentityChannel
   asserts highest shipped stable (matching the SmokeTest contract).
 - New NewCommand_ExplicitStableChannel_NonStableCliVersion_FallsBackToHighestShippedStable
   regression test covers both daily-shape and PR-shape CLI identities.
 - NewCommandTests.NewCommandWithTypeScriptEmptyTemplatePassesResolvedVersionAndChannelToScaffolding
   reverts to its pre-#17564 assertion of '9.2.0' from the stable feed.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mitchdenny pushed a commit that referenced this pull request May 28, 2026
* Prefer current CLI template version

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

* Pin aspire new template version to CLI

Ensure CLI-runtime templates selected from explicit package channels use the current bundled CLI/SDK version instead of floating to newer channel packages that can mismatch the bundled AppHost server.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mitchdenny pushed a commit that referenced this pull request May 28, 2026
…Folder path (#17573)

* Stabilize PrebuiltAppHostServer staging globalPackagesFolder path

PackagingService creates the staging channel with
ConfigureGlobalPackagesFolder=true so each darc/override feed restore
lands in an isolated cache (two staging builds of the same release
branch ship as 13.4.0 but from different feeds, and NuGet keys by
(id,version) only). PrebuiltAppHostServer was wiring that flag into a
TemporaryNuGetConfig whose default globalPackagesFolder value is the
relative '.nugetpackages' -- NuGet resolved it under the temp config's
own directory, BundleNuGetService baked those temp paths into
integration-package-probe-manifest.json, and TemporaryNuGetConfig.Dispose
then recursively deleted the cache out from under the manifest. On macOS
osx-arm64 polyglot staging builds this surfaced as a hang during DI /
assembly loading in aspire-managed.

Preserve the per-feed cache isolation behavior and anchor the override
at a stable absolute path instead:

  <ASPIRE_HOME>/.nugetpackages/<first-8-of-CLI-commit-sha>

Keying by the truncated commit hash matches the existing
darc-pub-microsoft-aspire-<hash> feed URL convention in
PackagingService.GetStagingFeedUrl, so the cache key and feed key stay
aligned at 8 hex chars. 8 chars is short enough to avoid Windows
MAX_PATH blow-ups on deep integration cache trees while keeping SHA
collisions negligible. The cache lives under ASPIRE_HOME (not the
per-AppHost working directory) so multiple AppHosts on the same machine
running against the same staging build can share a single restore --
the unit of isolation here is the staging build, not the individual
restore command.

Mechanics:
- TemporaryNuGetConfig.CreateAsync now accepts an optional
  globalPackagesFolderValue and propagates it through
  AddGlobalPackagesFolderToConfigAsync into the merger.
- NuGetConfigMerger.AddGlobalPackagesFolderConfiguration takes the
  optional override and falls back to the workspace-relative default
  ('.nugetpackages') for the non-temp workspace-merge path.
- PrebuiltAppHostServer.ResolveStableGlobalPackagesFolder routes both
  temp config branches (channel and package-source-override) through
  the new helper.
- VersionHelper.TryGetCurrentCommitHashShort surfaces the truncated
  SHA from the running CLI's AssemblyInformationalVersion (returns
  null on clean release builds with no '+sha' suffix; callers fall
  back to 'default').
- CliPathHelper centralizes the '<ASPIRE_HOME>/.nugetpackages' path so
  the producer (PrebuiltAppHostServer) and consumer (CacheCommand)
  can't drift.
- CacheCommand.ClearCommand now wipes <ASPIRE_HOME>/.nugetpackages so
  a wedged staging restore is recoverable through the same UX as every
  other CLI cache.

Tests:
- Updated existing PrebuiltAppHostServerTests staging cases to assert
  the globalPackagesFolder value is absolute and lives outside the
  temp config directory.
- New PrebuiltAppHostServerTests case wires a real PackagingService
  with overrideStagingFeed on a stable-shaped CLI and pins the same
  invariant end-to-end.
- New TemporaryNuGetConfigTests for the override propagation and for
  the no-override-when-disabled invariant.
- New CacheCommandTests covering the staging cache wipe and the
  missing-cache no-op path.

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

* Key staging globalPackagesFolder cache by feed URL hash

The staging globalPackagesFolder fix in the previous commit keyed the
\`.nugetpackages\` subdirectory off the CLI's own commit SHA (first 8
hex chars of \`AssemblyInformationalVersionAttribute\`). That handles
two darc-shipped staging builds of the same release branch, but it
breaks the local-dev case where one CLI is repeatedly retargeted at
different \`overrideStagingFeed\` values: the URL changes but the SHA
doesn't, so every override silently shares one cache bucket and the
second restore reuses the first feed's now-stale \`13.4.0\` assemblies.

Switch the cache key to the first 8 hex chars of \`XxHash3\` over the
trimmed, lower-cased resolved feed URL:

- Override branch passes the explicit \`--source\` URL.
- Channel branch passes the channel's \`Aspire*\` mapping source (or
  the first mapping for forward compatibility).

Trim + lower-case before hashing so a stray whitespace from a config
file or a hostname-case change doesn't fragment the cache.
Non-cryptographic hashing is fine here — the key is a directory name,
not a security boundary — and 8 hex chars keep deep integration cache
paths well under Windows MAX_PATH while giving ~4 billion buckets, so
collisions are negligible across the handful of staging feeds any user
ever sees.

Removes the now-unused \`VersionHelper.TryGetCurrentCommitHashShort\`
helper (added in the previous commit, no remaining callers).

Tests:
- New \`CliPathHelperTests.ComputeStagingFeedCacheKey_*\` cover
  determinism, normalization, length defaults, and null/empty input.
- Existing \`PrebuiltAppHostServerTests\` strengthened to assert the
  emitted globalPackagesFolder equals \`<aspireHome>/.nugetpackages/<hash(feed)>\`.

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

* Narrow #17564 CLI-pin fallback to prerelease channels

PR #17564 introduced a new fallback in TryGetCurrentCliTemplateVersionPackage
that pins the template package to the running CLI's SDK version whenever the
selected channel is explicit and not local-build. That preserved the intended
fix for prerelease channels (daily, staging) where the channel feed filters
the running CLI's package out of search results even though the feed can
still restore it.

But it also fired for the stable channel, where the filter does not apply.
For a non-stable-shape CLI (PR build like '13.4.0-pr.X.gY' or a daily-shape
preview) invoked with '--channel stable', it forced an unpublishable version
into the generated apphost.cs, breaking the SmokeTests
LatestCliCanStartStableChannelAppHost and
LatestCliCanStartStableChannelTypeScriptAppHost.

Exclude the stable channel from the new fallback so that case falls through
to the OrderByDescending picker and the user gets the highest shipped stable
package they explicitly asked for. The original prerelease-channel motivation
('Aspire.TypeSystem version mismatch when 13.4 CLI floats templates to a
13.5 daily preview') is preserved unchanged.

Test updates:
 - NewCommandChannelResolutionTests.NewCommand_NoChannelArg_ResolvesTemplateFromIdentityChannel
   gets an expectedVersion theory parameter so the daily case still pins to
   the CLI version while the stable case asserts the highest shipped stable.
 - NewCommandChannelResolutionTests.NewCommand_ExplicitChannelArg_OverridesIdentityChannel
   asserts highest shipped stable (matching the SmokeTest contract).
 - New NewCommand_ExplicitStableChannel_NonStableCliVersion_FallsBackToHighestShippedStable
   regression test covers both daily-shape and PR-shape CLI identities.
 - NewCommandTests.NewCommandWithTypeScriptEmptyTemplatePassesResolvedVersionAndChannelToScaffolding
   reverts to its pre-#17564 assertion of '9.2.0' from the stable feed.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mitchdenny added a commit that referenced this pull request May 28, 2026
The cherry-picks of #17564 and #17573 alone do not address the
`aspire new` + C# Blazor scenario from #17596 because that path
runs through `TemplateNuGetConfigService.ResolveTemplatePackageAsync`
(DotNet templates), not `NewCommand.ResolveCliTemplateVersionAsync`
(CLI-runtime templates) where #17564's `TryGetCurrentCliTemplateVersionPackage`
synthesis lives.

Teach `ResolveTemplatePackageAsync` to mirror the identity-channel
preference that `ResolveCliTemplateVersionAsync` (NewCommand.cs:376-389)
already does for CLI templates: when no `--channel` is supplied and no
PR hives exist, prefer the channel whose name matches the running CLI's
`CliExecutionContext.IdentityChannel` before falling back to Implicit.

Without this, a daily 13.5 CLI on a clean `~/.aspire` silently restricts
the template search to nuget.org and returns the shipped stable
`Aspire.ProjectTemplates 13.3.5` instead of the matching
`13.5.0-preview.1.*` daily prerelease from the `dotnet9` feed.

The Implicit fallback preserves prior behavior when the identity channel
isn't a registered channel (e.g. a "local" CLI without a corresponding
local-build hive).

See #17596 for full repro.

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