Polyglot aspire add parity with C# (project-local NuGet.config + auto-select)#17768
Polyglot aspire add parity with C# (project-local NuGet.config + auto-select)#17768mitchdenny wants to merge 12 commits into
aspire add parity with C# (project-local NuGet.config + auto-select)#17768Conversation
… resolved When 'aspire new' resolves an Explicit channel (--channel, or a CLI-identity channel that PackagingService registered), the C# starter falls back to PromptToCreateOrUpdateNuGetConfigAsync which writes a project-local NuGet.config pinning Aspire.* to the channel's feed. The polyglot starters (TypeScript / Python / Go and the non-csharp branch of the empty template) only called CreateOrUpdateNuGetConfigForSourceOverrideAsync, which is a no-op when no --source is supplied. This left polyglot projects with no project-local NuGet.config, so the implicit channel that IntegrationPackageSearchService always queries (intentionally, so prerelease-only packages stay reachable on Stable pins — see #17724 / #17725) resolved Aspire.Hosting.* from ambient nuget.org and surfaced a stale version alongside the staging darc-feed match. PR #17743 routed the staging channel's Aspire.* mapping to the correct darc feed but did not close this gap because the implicit channel still went to nuget.org for polyglot apphosts. Mirror the C# fallback by chaining CreateOrUpdateNuGetConfigWithoutPromptAsync (the same helper InitCommand uses for polyglot init) when no source override is supplied. The helper is a no-op for Implicit / unregistered channels so nothing changes when the resolved channel isn't Explicit. Adds a regression test exercising the TS starter with a registered staging channel and no --source, asserting the channel-derived NuGet.config lands on disk with the Aspire* → channel-feed mapping. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17768Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17768" |
Polyglot apphosts (TypeScript / Python / Go) that pin a channel via `aspire.config.json` were still presenting a multi-row version picker on `aspire add` (e.g. Implicit 13.4.0-preview / staging 13.4.0-preview / daily 13.3.5-preview) while the equivalent C# flow auto-selected the pinned channel's version. Two root causes working together: 1. `IntegrationPackageSearchService.GetIntegrationPackagesWithChannelsAsync` included EVERY explicit channel (stable, daily, staging, ...) whenever the apphost had a pinned channel. That dragged unrelated channels (e.g. daily) and their stale versions into the result set. 2. `AddCommand.GetPackageByInteractiveFlow` did not deduplicate by (PackageId, Version) before handing the list to the version prompter. With layer 1 in place, polyglot starters that drop a project-local NuGet.config (PR #17768) get the same package+version from both Implicit (via the project-local feed map) and the pinned Explicit channel, producing two identical rows that prevented the auto-select path in `PromptForChannelPackagesAsync` from firing. Fixes: - IPSS narrows the Explicit channel set to the apphost-pinned channel only (Implicit always stays so prerelease-only packages remain reachable when the pin is Stable-quality — preserving the #17724/#17725 guarantee). - AddCommand dedupes `orderedPackageVersions` by (Id, Version) keeping the first occurrence (Implicit-channel entry wins due to existing ordering). Net effect: for a TS apphost pinned to staging, only the Implicit Foundry entry remains, which trips the `explicitGroups.Length == 0 && implicitGroup is not null` auto-select branch and skips the version menu — exactly matching the C# flow. Tests: - New regression test `AddCommandWithTypeScriptAppHostPinnedToStagingAutoSelectsImplicitVersionWithoutPromptingForVersion` asserts no IInteractionService selection prompt is raised and that the correct staging version is added. Verified fails-without / passes-with. - Updated existing `IntegrationSearchCommandTypeScriptAppHostPersistedChannelExpandsDiscoveryWithoutChangingPreferredResult` theory data to reflect the new channel-name-aware narrowing (staging-pin no longer searches an unrelated daily channel). - Extended TestTypeScriptStarterProject with an `AddPackageAsyncCallback` so end-to-end `aspire add` flows can be exercised in tests. All 3928 Aspire.Cli.Tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
aspire add parity with C# (project-local NuGet.config + auto-select)
…r rc files The --shell subshell sets PATH="$cli_dir:$PATH" before exec'ing the user's interactive shell. But `zsh -i` (and `bash -i`) source the user's startup files AFTER our environment is in place, and a very common line in those files is: export PATH="$HOME/.aspire/bin:$PATH" That re-prepend pushes the installed `~/.aspire/bin/aspire` ahead of our locally built CLI in the subshell, so `which aspire` (and every `aspire` invocation) silently picks up the wrong binary -- breaking validation. Fix: drop a tiny shim (`<tempdir>/bin/aspire` exec'ing the absolute --cli path) and start the subshell with rc files that source the user's real rc and THEN prepend the shim dir. zsh uses ZDOTDIR; bash uses --rcfile; other shells fall back to the previous env-only PATH export with a warning pointing at the shim path so the developer can invoke it directly. The shim dir is cleaned up when the subshell exits. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The overrideCliIdentityChannel and overrideCliInformationalVersion diagnostic
env vars (set by eng/scripts/debug-staging.sh to validate staging feed routing
from a locally built CLI) were previously honored only inside PackagingService:
the synthesized staging channel was correctly routed at the SHA-specific darc
feed, but the rest of the CLI still saw the baked 'local' identity and the
local assembly version. The two half-emulations combined to produce two visible
bugs in 'aspire new':
1. NewCommand auto-selected the Implicit (nuget.org) channel instead of the
synthesized staging channel because ExecutionContext.IdentityChannel
returned 'local' (no registered channel). The Implicit channel only sees
the previous shipped stable Aspire.ProjectTemplates (e.g. 13.3.5) so a
local CLI emulating staging stamped 13.3.5 into the new project's
aspire.config.json.
2. The 'CLI version differs from configured SDK version' warning fired even
when the user had aligned both via the override, because
VersionHelper.GetDefaultTemplateVersion read the raw assembly attribute.
Fixes:
- IPackagingService.GetEffectiveIdentityChannel exposes the existing
override-aware identity lookup. NewCommand consults it (with a fallback to
ExecutionContext.IdentityChannel) when picking the default channel.
- VersionHelper.GetDefaultTemplateVersion honors overrideCliInformationalVersion
so template selection, SDK pinning, and the CLI banner all report the
emulated version.
- debug-aspire-channel.sh auto-detects the actual Aspire.ProjectTemplates
version published to the SHA's darc feed and uses that as the default
when --version is not supplied. Without this the user has to hand-pick a
version that matches what darc published for that SHA (which depends on
whether the build was stabilized), and a mismatch silently degrades
template selection to the highest nuget.org version (13.3.5).
Tests:
- Added GetEffectiveIdentityChannelCallback to TestPackagingService; the
default returns null/empty so existing channel-resolution tests fall
through to ExecutionContext.IdentityChannel unchanged.
- Verified end-to-end via 'debug-staging.sh --shell' that 'aspire new
aspire-ts-starter' now writes 13.4.0 and 'aspire add foundry' auto-selects
the staging-feed prerelease without prompting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review feedback: instead of calling Environment.GetEnvironmentVariable directly inside VersionHelper, plumb CliExecutionContext through so the overrideCliInformationalVersion diagnostic override is read via context.GetEnvironmentVariable, matching how other staging-validation env vars flow through the CLI. - VersionHelper.GetDefaultTemplateVersion / GetDefaultSdkVersion / TryGetCurrentCliVersionMatch now take an optional CliExecutionContext? parameter. When supplied, env reads route through context; otherwise fall back to process env so the ~30 unrelated callers don't churn. - PackageChannel constructor and Create*Channel factories accept an optional CliExecutionContext? and pass it to VersionHelper. - PackagingService passes _executionContext to all PackageChannel factory calls and to its own VersionHelper.GetDefault* calls. - NewCommand passes ExecutionContext into TryGetCurrentCliTemplateVersionPackage so VersionHelper sees the staging override. - GuestAppHostProject CLI/SDK skew check passes _executionContext. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On a stabilizing release branch (e.g. release/13.4) the same SHA's darc feed
can publish core packages stable-shaped ("13.4.0") and integration packages
prerelease-shaped ("13.4.0-preview.1.X.Y"). The script stamps the CLI's
informational version from the templates package, so the resulting stable
shape makes PackagingService's _isStableShapedCliVersion() return true, which
auto-selects staging-channel quality=Stable. That quality filter then hides
the prerelease integration packages, so `aspire add foundry` only sees
nuget.org's previous shipped version and the staging channel silently
disappears from the version-selection menu.
Exporting overrideStagingQuality=Both bypasses the auto-derived quality and
keeps all matching versions on the darc feed eligible -- which is what
end-to-end validation actually wants.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Polyglot apphosts (TS/Python/Go) deliberately don't carry a persistent NuGet.config — that file is a .NET-ism. Restore is done via on-the-fly `TemporaryNuGetConfig`s built from `aspire.config.json#channel` (see `PrebuiltAppHostServer.CreatePackageSourceOverrideNuGetConfigAsync`). The IntegrationPackageSearchService was unconditionally including the Implicit channel whenever the apphost had a pin, even for polyglot. Because polyglot has no project-local NuGet.config, Implicit's `dotnet package search` ran against the user's ambient (typically nuget.org) and surfaced stale/wrong-feed versions alongside the pinned channel's versions. This produced the spurious 'select version' prompt the user reported on TS apphosts pinned to staging (#17743), and also added 'staging' / 'daily' rows to stable packages like azure-appcontainers. Fix: when a polyglot apphost has a pin, search ONLY the pinned channel. The pinned channel materializes its own TemporaryNuGetConfig from its PSM mappings, so it's correctly scoped. This matches C# end-to-end: C#'s `GetConfiguredChannel` returns null, so IPSS runs Implicit against the C# template's project-local NuGet.config (which IS correctly scoped because the C# template writes one). Trade-off knowingly accepted: a stable-pinned polyglot apphost cannot reach prerelease-only packages (e.g. Foundry). This matches C# behavior — a stable-pinned C# project also wouldn't see them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Re-running the failed jobs in the CI workflow for this pull request because 3 jobs were identified as retry-safe transient failures in the CI run attempt.
Matched test failure patterns (26 tests)
|
A shipped (identity=stable) CLI running 'aspire new' was writing
aspire.config.json#channel = "stable", which routed subsequent
'aspire add' calls through the Stable PackageChannel
(PackageChannelQuality.Stable, * -> nuget.org). That filter hides
prerelease packages on nuget.org, so a community 13.4.0-preview
integration the team hadn't yet shipped as stable became invisible
after scaffolding — for both the TypeScript polyglot starter (cell 7)
and the C# aspire-starter family (cell 8).
'aspire init' already avoided the trap via
PackageChannel.ShouldPersistChannelName (Type == Explicit AND name !=
"stable"). 'aspire new' had two persistence sites that only checked
Type == Explicit:
* ResolveCliTemplateVersionAsync (CLI-runtime polyglot path)
* ResolveIdentityChannelNameAsync (DotNet-runtime path consumed by
DotNetTemplateFactory)
Both now use ShouldPersistChannelName, matching aspire init. Explicit
'--channel stable' still wins via the channel-arg leg of the resolution
coalesce — only the identity-fallback case is changed.
Two pre-existing InlineData rows in NewCommandChannelResolutionTests
codified the buggy behavior (one in #17573 alongside daily stabilization
work, one in #17637 alongside the DotNet-runtime forwarding fix); both
were rolled in defensively rather than as a deliberate stable-pin
invariant. Removed and replaced with two dedicated negative tests
(_DoesNotPersistStablePin_Cli / _DoesNotPersistStablePin_DotNet) that
were red before the production fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`get-aspire-cli-pr.{sh,ps1}` installs PR builds to
`$INSTALL_PREFIX/dogfood/pr-<N>/bin/aspire` (see compute_cli_install_dir /
Get-CliInstallDir), not `$INSTALL_PREFIX/bin/aspire`. The PATH
auto-discovery fallback in debug-aspire-channel.{sh,ps1} only knew about
`~/.aspire/bin/aspire`, so when `--pr` was supplied it would install the
PR build into the dogfood path, then pick whatever existing `aspire` was
already first on PATH (typically the user's stable install at
`~/.aspire/bin/aspire`) and shim that one instead. Net result: `--pr`
appeared to do nothing.
When `--pr` is supplied, pin cli_path / $Cli to the dogfood install
location and only fall back to PATH discovery if that file isn't found
(in which case warn loudly — the install script's layout has likely
shifted). An explicit `--cli` / `-Cli` still overrides everything.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Follow-up to #17743 that closes the polyglot (TS/Python/Go/Java/Rust) gap so aspire new + aspire add matches the C# experience on staging-identity CLIs. Without the fix, polyglot starters got no project-local NuGet.config, and aspire add could surface stale stable versions alongside the staging match (or render a spurious version picker that C# never showed).
Changes:
- Polyglot starter templates (TS, Python, Go, Empty's non-csharp branch) now fall back to a channel-derived
NuGet.config(CreateOrUpdateNuGetConfigWithoutPromptAsync) when no--sourceis supplied, mirroringDotNetTemplateFactory. IntegrationPackageSearchServicenarrows the channel set to only the apphost-pinned channel when one is configured (Implicit and other explicit channels are excluded), restoring C# end-to-end parity foraspire add.- Adds
IPackagingService.GetEffectiveIdentityChannel()and threads it throughNewCommandso theoverrideCliIdentityChanneldiagnostic env var (used byeng/scripts/debug-staging.sh) is honored end-to-end;VersionHelper.GetDefault{Template,Sdk}VersionandPackageChannelare correspondingly plumbed withCliExecutionContextso the paralleloverrideCliInformationalVersionoverride flows through; persist-channel decisions switch fromType is Explicitto the existingShouldPersistChannelName()(which also excludes"stable").
Show a summary per file
| File | Description |
|---|---|
| src/Aspire.Cli/Templating/CliTemplateFactory.TypeScriptStarterTemplate.cs | Fallback to channel-derived NuGet.config when no --source. |
| src/Aspire.Cli/Templating/CliTemplateFactory.PythonStarterTemplate.cs | Same fallback for Python starter. |
| src/Aspire.Cli/Templating/CliTemplateFactory.GoStarterTemplate.cs | Same fallback for Go starter. |
| src/Aspire.Cli/Templating/CliTemplateFactory.EmptyTemplate.cs | Same fallback in the non-csharp branch. |
| src/Aspire.Cli/Commands/IntegrationPackageSearchService.cs | Narrow channel set to pinned channel when configured. |
| src/Aspire.Cli/Commands/AddCommand.cs | Materialize orderedPackageVersions to array for multiple enumeration. |
| src/Aspire.Cli/Commands/NewCommand.cs | Use GetEffectiveIdentityChannel + ShouldPersistChannelName() gating. |
| src/Aspire.Cli/Packaging/PackagingService.cs | Expose GetEffectiveIdentityChannel on the interface; thread CliExecutionContext into channels. |
| src/Aspire.Cli/Packaging/PackageChannel.cs | Accept optional CliExecutionContext for version-override plumbing. |
| src/Aspire.Cli/Utils/VersionHelper.cs | Honor overrideCliInformationalVersion env var via execution context. |
| src/Aspire.Cli/Projects/GuestAppHostProject.cs | Pass execution context to GetDefaultSdkVersion. |
| eng/scripts/debug-aspire-channel.{sh,ps1} | Auto-detect feed version, install-path pinning for --pr, shim PATH for interactive shells, set overrideStagingQuality. |
| tests/Aspire.Cli.Tests/Commands/AddCommandTests.cs | New staging auto-select test; rewrite existing tests to assert the narrowed-channel contract. |
| tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs | New regression test asserting channel-derived NuGet.config is written for TS starter. |
| tests/Aspire.Cli.Tests/Commands/NewCommandChannelResolutionTests.cs | Add stable-identity-does-not-pin tests; drop stable cases from existing theory data. |
| tests/Aspire.Cli.Tests/TestServices/TestPackagingService.cs | Implement new GetEffectiveIdentityChannel member. |
| tests/Aspire.Cli.Tests/TestServices/TestTypeScriptStarterProjectFactory.cs | Add AddPackageAsyncCallback hook for aspire add tests. |
Copilot's findings
- Files reviewed: 18/18 changed files
- Comments generated: 0
aspire init was reading _executionContext.IdentityChannel directly for
three feed-routing decisions:
1. C# single-file: NuGet.config drop next to apphost.cs
2. C# project: NuGet.config drop in the solution directory
3. Polyglot (and C# single-file): channel persisted into
aspire.config.json and forwarded to ScaffoldContext.Channel, which
the prebuilt apphost server uses to scope the dynamically generated
NuGet.config sources
That bypassed PackagingService.GetEffectiveIdentityChannel(), which is
the only place that consults the 'overrideCliIdentityChannel' diagnostic
env var used by eng/scripts/debug-staging.sh / debug-stable.sh. As a
result, a CLI binary baked as 'stable' (or 'local') being emulated as
'staging' would drop a NuGet.config pinned to the wrong channel, and the
follow-up 'aspire add foundry' (which DOES go through PackagingService)
would resolve against feeds that init had never written, so it found
only 13.3.5 instead of the 13.4.0 staging prerelease from the DARC feed.
Also route the template-package query's RequestedChannel through the
same helper so the C# project flow asks dotnet new install against the
effective channel's source.
Hive-lookup / ChannelNotFoundException paths still read the baked
identity directly: those are physical-binary properties, not feed
routing.
Adds three regression tests that bake identity=local, register both
'local' and 'staging' channels, set the effective-channel override to
'staging', and assert init now drops a NuGet.config scoped to the
staging feed (with packageSourceMapping for Aspire*) and hands
'staging' to the polyglot scaffolder.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
I don't think we should be dropping a nuget.config file in the TypeScript apphost. I think a reasonable alternative would be to add a new section to aspire.config.json with this information. |
Counterpart fix to the InitCommand change in e836a5b: NewCommand's ResolveIdentityChannelNameAsync was still reading ExecutionContext.IdentityChannel directly, so on the DotNet-runtime template path (C# aspire-starter, including the Blazor starter) under the eng/scripts/debug-staging.sh emulator the chain ran: raw identity = 'local' -> no Explicit channel named 'local' registered -> ResolveIdentityChannelNameAsync returns null -> TemplateInputs.Channel = null -> DotNetTemplateFactory's TemplatePackageQuery.RequestedChannel = null -> TemplateNuGetConfigService walks Implicit (nuget.org) -> Aspire.ProjectTemplates resolves to last-shipped stable (13.3.5) instead of the staging build's prerelease on the darc feed. PackagingService.GetEffectiveIdentityChannel already accounts for the overrideCliIdentityChannel diagnostic env var; route through it (with the same baked-identity fallback used by ResolveCliTemplateVersionAsync) so the DotNet path matches the CLI-runtime path's behavior the staging fix originally established. Mirrors the parallel InitCommand.GetEffectiveFeedRoutingChannel helper. Adds two regression tests in NewCommandChannelResolutionTests: * NewCommand_LocalIdentityWithStagingOverride_DotNet_PropagatesEffectiveChannel is the bug-asserting test (red before this commit). * NewCommand_LocalIdentityWithStagingOverride_Cli_PropagatesEffectiveChannel is a no-regression test for the CLI-runtime path that was already override-aware. Both go through an extended BuildPackagingService that wires TestPackagingService.GetEffectiveIdentityChannelCallback and registers the override channel rather than the baked identity, matching the PackagingService shape under debug-staging.sh. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
13.4.0 is closed now, let's look at this one for 13.4.1 especially since it seems to be staging specific. |
The shell-mode emulator redirects NUGET_PACKAGES to a per-sha isolated directory so a simulated staging build's Aspire.* restore can't contaminate the developer's real global cache. But NuGet only reads from NUGET_PACKAGES on lookup, so transitive non-Aspire dependencies that are version-stable across feeds — most notably Microsoft.DeveloperControlPlane.<rid> (DCP) and .NET runtime packs — have nowhere to come from. The polyglot apphost server then fails to launch with: Could not invoke 'run': The Aspire orchestration component is not installed at "$NUGET_PACKAGES/microsoft.developercontrolplane.<rid>/<ver>/tools/dcp". Wire NUGET_FALLBACK_PACKAGES at the developer's real cache (~/.nuget/packages by default, overridable via the same env var pre-set). NuGet treats fallback folders as read-only on lookup after NUGET_PACKAGES, so DCP and runtime packs resolve from the existing cache while any staging-shaped Aspire.* package still lands ONLY in the isolated dir — keeping the contamination guarantee intact. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Are we confident that stable is going to work? If we don't need this then I don't want to merge it - in 13.5 I want to rip most of the guts out of this stuff becuase its completely untestable right now. |
|
❓ CLI E2E Tests unknown — 110 passed, 0 failed, 2 unknown (commit View all recordings
📹 Recordings uploaded automatically from CI run #26734047521 |
Description
Follow-up to #17743 closing the user-visible gap for polyglot (TS/Python/Go/Java/Rust) apphosts. With a staging-identity CLI:
…still offered the older
13.3.5-previewalongside the matching staging version, and when only the staging version was found it still presented a version picker prompt instead of auto-selecting. C# starters did the right thing in both cases.Root causes
This PR fixes two related-but-distinct bugs that combined to produce the symptom.
1. Polyglot starters didn't drop a project-local
NuGet.configIntegrationPackageSearchService.GetIntegrationPackagesWithChannelsAsyncintentionally always includes the Implicit channel so prerelease-only packages (e.g. Foundry) stay reachable when the project is Stable-pinned (#17724 / #17725). The Implicit channel callsdotnet package searchwith no explicit NuGet config, so it walks the ambient hierarchy.The C# starter falls back to
PromptToCreateOrUpdateNuGetConfigAsync(DotNetTemplateFactory.csline ~549-552) when no--sourcewas supplied — that writes a project-localNuGet.configpinningAspire.*to the resolved channel's feed via a package-source mapping. The 4 polyglot template paths only calledCreateOrUpdateNuGetConfigForSourceOverrideAsync, which is a no-op without--source. So polyglot projects got no project-localNuGet.config, and the Implicit channel resolvedAspire.Hosting.*from nuget.org → surfaced the stale 13.3.5 alongside the staging match.2.
aspire addshowed a version picker on polyglot when C# auto-selectedTwo cooperating mistakes in the add flow:
IntegrationPackageSearchServiceincluded every Explicit channel (stable, daily, staging, ...) whenever the apphost had a pinned channel. That dragged unrelated channels (e.g. daily) and their stale versions into the result set.AddCommand.GetPackageByInteractiveFlowdidn't deduplicate by (PackageId, Version) before handing the list to the version prompter. With the project-localNuGet.configfrom layer 1 in place, the same package+version got surfaced by both Implicit (via the project-local feed map) and the matching pinned Explicit channel, producing two identical rows that prevented the auto-select path inPromptForChannelPackagesAsyncfrom firing.C# never hit either:
IntegrationPackageSearchService.GetConfiguredChannelreturnsnullfor C# unconditionally, so the Explicit channel set is never queried for C# add flows.Fixes
Layer 1 — polyglot starters
Mirror the C# fallback. After the source-override write (which returns false when no
--sourcewas supplied), chainCreateOrUpdateNuGetConfigWithoutPromptAsync(the same helperInitCommanduses for polyglot init flows). The helper is a no-op for Implicit / non-Explicit / unregistered channels, so Stable / no-explicit-channel runs are unchanged.Applied to all 4 affected templates:
CliTemplateFactory.TypeScriptStarterTemplate.cs(canonical fix site with full justification comment)CliTemplateFactory.PythonStarterTemplate.csCliTemplateFactory.GoStarterTemplate.csCliTemplateFactory.EmptyTemplate.cs(non-csharp branch; the csharp branch was already correct)Layer 2 —
aspire addauto-select parityIntegrationPackageSearchServicenarrows the Explicit channel set to the apphost-pinned channel only. Implicit always stays so prerelease-only packages remain reachable when the pin is Stable-quality — preserving the aspire add kube adds first matching integration instead of failing #17724/Prerelease-only integrations like Aspire.Hosting.Foundry are filtered out of search #17725 guarantee.AddCommanddedupesorderedPackageVersionsby (Id, Version) keeping the first occurrence (Implicit-channel entry wins due to existing ordering).Net effect: for a TS apphost pinned to staging, only the Implicit Foundry entry remains after narrowing+dedupe, which trips the
explicitGroups.Length == 0 && implicitGroup is not nullauto-select branch and skips the version menu — matching the C# flow exactly.Tests
NewCommandWithTypeScriptStarterAndExplicitChannelWritesChannelDerivedNuGetConfig— drivesaspire new aspire-ts-starter --channel staging(no--source) with a registered staging channel containing anAspire* → darc feedmapping, and asserts a project-localnuget.configlands on disk with the correct package source mapping.AddCommandWithTypeScriptAppHostPinnedToStagingAutoSelectsImplicitVersionWithoutPromptingForVersion— drivesaspire add foundryagainst a TS apphost pinned to staging, with a fake PackagingService returning Implicit+Stable+Staging+Daily where Implicit and Staging both surface Foundry@13.4.0-preview.1.staging and Daily surfaces the stale Foundry@13.3.5-preview.1.daily. Asserts noIInteractionServiceselection prompt is raised and that 13.4.0-preview.1.staging is what gets added. Verified fails-without / passes-with both fixes.IntegrationSearchCommandTypeScriptAppHostPersistedChannelExpandsDiscoveryWithoutChangingPreferredResulttheory data to reflect the new channel-name-aware narrowing (staging-pin no longer searches an unrelated daily channel).TestTypeScriptStarterProjectwith anAddPackageAsyncCallbackso end-to-endaspire addflows can be exercised in tests.All 3928 Aspire.Cli.Tests pass.
Validation
Built locally; can be exercised end-to-end with the helper scripts added in #17743:
Fixes
Follow-up to #17743 for issue #17744 (polyglot leg).
Targeting
release/13.4since #17743 was merged there; needs forward-port tomain.