fix(cli): aspire new fails to scaffold aspire-ts-starter on daily / staging CLI#17120
Conversation
…taging CLI
`aspire new aspire-ts-starter` on a daily-channel CLI (and likewise on
staging / stable identities) fails the automatic restore that follows
project creation, leaving the user with a half-scaffolded project that
cannot build:
$ aspire --version
13.4.0-preview.1.26264.16+642f3dfc
$ aspire new aspire-ts-starter --name TsApp --output . --non-interactive
...
Package source mapping matches found for package ID 'Aspire.Hosting' are:
'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json'.
...
ERROR: Unable to find a stable package Aspire.Hosting with version (>= 13.3.2)
- Found 2793 version(s) in https://pkgs.dev.azure.com/dnceng/.../dotnet9/...
[ Nearest version: 13.4.0-preview.1.26229.5 ]
- Versions from https://api.nuget.org/v3/index.json were not considered
...
❌ Automatic 'aspire restore' failed for the new TypeScript starter project.
The generated `aspire.config.json` shows the underlying mismatch:
{
"sdk": { "version": "13.3.2" },
"channel": "daily",
"packages": { "Aspire.Hosting.JavaScript": "13.3.2" }
}
The channel is pinned to `daily` (so PSM routes `Aspire.*` to the dnceng
daily feed, which only hosts prereleases), but the SDK / package
versions are stable `13.3.2` — which only exists on nuget.org. The two
pins are not mutually satisfiable, so restore fails.
The two halves of TypeScript-starter scaffolding were resolving the
channel inconsistently:
* `NewCommand` only consulted `CliExecutionContext.IdentityChannel` to
pick the resolution channel when the identity was a local-build name
(`pr-*`, `run-*`, `local`) — gated on `VersionHelper.IsLocalBuildChannel`.
For a daily binary the identity is `daily`, which the gate excluded,
so resolution fell through to the Implicit (nuget.org) channel and
yielded the latest stable template package version.
* The TypeScript starter factory then pinned `aspire.config.json`'s
`channel` to `IdentityChannel` regardless of what `NewCommand` had
actually selected. Subsequent `aspire restore` routed `Aspire.*` to
the channel-specific feed via Package Source Mapping, but the version
had already been chosen against Implicit, so the daily feed (which
only contains the prerelease) rejected the stable version that landed
in the project.
PR-built CLIs hid the bug because `pr-<N>` is a local-build channel
and the gate fired — so the resolved channel and the pinned channel
happened to agree. Daily and staging never took that branch, so the
gate's omission only surfaced through the daily smoke workflow.
The Go and Python starters were *not* affected in practice: their
factories tried to persist the channel into `.aspire/settings.json`
via `AspireJsonConfiguration`, but those templates ship
`aspire.config.json` instead and never carried a `.aspire/settings.json`
— so `AspireJsonConfiguration.Load` returned `null` and the channel
was never written. They worked by accident because the resulting
project had no channel pin at all, leaving restore to use ambient
NuGet sources via `PrebuiltAppHostServer`'s channel aggregation.
The fix aligns NewCommand and the TypeScript factory on a single
resolution policy, and drops the dead channel-write code from the Go
and Python factories:
* `NewCommand` prefers any channel whose name matches
`ExecutionContext.IdentityChannel` (stable, staging, daily, local,
`pr-<N>`) over the Implicit channel when `--channel` is not passed.
Falls back to Implicit when the identity name isn't a registered
channel (typo, future name).
* The TypeScript factory only persists `config.Channel` when an
Explicit channel was resolved (either `--channel` or a registered
match for the running CLI's identity). Implicit (nuget.org)
selections leave the channel unwritten so `aspire add` /
`aspire restore` use the user's ambient NuGet config without a
stale per-project pin.
* The Go and Python factories no longer touch the project's config
file. `PrebuiltAppHostServer` aggregates package sources from every
registered channel when the project doesn't pin one, so daily-feed
packages remain reachable on a daily CLI without each project
carrying a channel name across machines.
This makes the build → execution context → feed → restore pipeline
mutually satisfiable on every channel: a daily CLI scaffolds a
daily-channel project whose prerelease SDK version is reachable
through the daily feed's Package Source Mapping; a stable CLI
scaffolds a stable project whose version is reachable through
nuget.org; a PR CLI scaffolds a `pr-<N>`-channel project against the
PR hive; and so on.
Note on `staging`: `PackagingService.GetChannelsAsync` only registers
a `staging` channel when `KnownFeatures.IsStagingChannelEnabled` is
true (feature flag or `configuration["channel"]="staging"`). A
default staging-identity CLI therefore still falls back to Implicit
here — auto-registering staging from the identity is tracked
separately.
Tests:
* New `NewCommandChannelResolutionTests` cover the resolution matrix:
identity matches a registered channel (daily / stable / staging-
when-enabled / pr-<N>) / identity not registered / staging not
registered → fallback to Implicit / explicit `--channel` overrides
identity.
* `TypeScriptStarterTemplateTests` renamed to
`TypeScriptStarterSmokeTests` so the daily smoke workflow picks it
up via its `*SmokeTests` filter — this is the regression coverage
that catches identity-channel resolution bugs invisible on PR
builds. Expanded to assert `aspire.config.json` has no channel pin
when invoked without `--channel`, and is pinned when invoked with
one. The pin ↔ SDK-version invariant skips the `staging` channel,
whose feed can legitimately host stable versions.
* `ChannelReseedTests` doc-comments updated for the new "persist
only on Explicit" semantic in the TypeScript factory.
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 -- 17120Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17120" |
There was a problem hiding this comment.
Pull request overview
Fixes a regression where aspire new aspire-ts-starter can scaffold a project whose pinned sdk.version and pinned channel are mutually unsatisfiable (notably on daily/staging identities), by making channel resolution and channel persistence consistently follow the running CLI’s identity and explicit channel selection rules.
Changes:
- Update
NewCommandchannel selection to prefer a registered channel matchingCliExecutionContext.IdentityChannelwhen--channelis omitted, falling back to Implicit when there’s no match. - Persist
aspire.config.json#channelfor the TypeScript starter only when an Explicit channel was actually resolved (leave Implicit unpinned). - Remove no-op channel seeding from Python/Go starter template factories and adjust/rename tests so daily smoke runs cover the TS starter path.
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/Scaffolding/ChannelReseedTests.cs | Updates unit-test documentation to clarify where channel persistence is/ isn’t expected to happen. |
| tests/Aspire.Cli.Tests/Commands/NewCommandChannelResolutionTests.cs | Adds coverage for identity-channel matching vs Implicit fallback, plus PR/staging edge cases. |
| tests/Aspire.Cli.EndToEnd.Tests/TypeScriptStarterTemplateTests.cs | Removes the prior TS starter E2E test in favor of a smoke-test-class target. |
| tests/Aspire.Cli.EndToEnd.Tests/TypeScriptStarterSmokeTests.cs | Adds a daily-smoke-pickup test validating scaffold + restore/run + channel/sdk consistency. |
| src/Aspire.Cli/Templating/CliTemplateFactory.TypeScriptStarterTemplate.cs | Only writes channel into aspire.config.json when TemplateInputs.Channel is explicitly set. |
| src/Aspire.Cli/Templating/CliTemplateFactory.PythonStarterTemplate.cs | Removes per-project channel writing behavior (previously attempted legacy settings write). |
| src/Aspire.Cli/Templating/CliTemplateFactory.GoStarterTemplate.cs | Removes per-project channel writing behavior (previously attempted legacy settings write). |
| src/Aspire.Cli/Commands/NewCommand.cs | Changes template-version resolution to prefer identity-matching registered channel over Implicit when --channel is omitted. |
PR Testing Report — dogfood runResult: ✅ Verified end-to-end
Environment
Scenarios
Key evidence — TS-starter
|
…e the test workspace The other three directories produced by BuildExecutionContextWithIdentity (hivesDirectory / cacheDirectory / logsDirectory) all live under the caller's TemporaryWorkspace.WorkspaceRoot. Only sdksDirectory escaped to Path.GetTempPath(), which is inconsistent within the same method and goes against the AGENTS.md guidance to use the test workspace or Directory.CreateTempSubdirectory() rather than a deterministic name under Path.GetTempPath() that can collide across concurrent test runs. Mirror the shape used by CreateExecutionContextWithHives in TemplateNuGetConfigServiceTests (and CreateExecutionContext in InitCommandTests) and keep the placeholder sdks directory under the workspace root. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
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.
|
|
I tested #17120 with the dogfood CLI against both TypeScript templates.
"channel": "pr-17120"That causes follow-up package discovery to fail in a clean HOME. Repro from the generated aspire add Aspire.Hosting.RedisResult: The remaining fix should be in the empty/scaffolding path: |
… registered channel Follow-up to 8c4ad6b. The TypeScript starter factory was patched to persist `aspire.config.json#channel` only when NewCommand resolved an Explicit channel (`--channel`, or a registered identity-match). `ScaffoldingService` — the writer used by every empty template (TS / Python / Go / Java / Rust) and by `aspire init` polyglot — still had the original identity-fallback shape: var seedChannel = string.IsNullOrWhiteSpace(context.Channel) ? _cliExecutionContext.IdentityChannel : context.Channel; When NewCommand's resolution found no registered channel matching the running CLI's identity (e.g. a CLI whose `CliExecutionContext.IdentityChannel` is `pr-17120` on a machine where the matching hive isn't present, or a staging-identity CLI without the staging feature flag), it correctly fell back to the Implicit channel and passed `context.Channel = null` down to the scaffolder. `ScaffoldingService` then silently substituted the CLI's identity name — so the generated `aspire.config.json` carried a channel pin that no Package Source Mapping rule could satisfy: $ aspire new aspire-ts-empty --name TsEmpty ... $ cat TsEmpty/aspire.config.json { "channel": "pr-17120", // identity-fallback wrote this "sdk": { "version": "13.3.0" } // stable, from Implicit } $ aspire add Aspire.Hosting.Redis ❌ No integration packages were found. Drop the identity-fallback so `ScaffoldingService` persists `ScaffoldContext.Channel` verbatim and only when set. When unset, `PrebuiltAppHostServer` aggregates package sources from every registered channel — daily-feed packages stay reachable on a daily CLI without each project carrying a stale channel name. This brings the empty-template + polyglot-init path in line with the TS / Python / Go starter factories, all of which already had this shape. The now-unused `CliExecutionContext` ctor parameter is removed; the test fixture (`CliTestHelper.ScaffoldingServiceFactory`) is updated to match. `ChannelReseedTests` is rewritten so its scenarios assert the new contract: `ScaffoldContext.Channel` flows through verbatim, and a deliberately non-matching `IdentityChannel` is used as a tripwire that fails if anyone re-introduces the identity-fallback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…late that emits aspire.config.json The starter-side bug (8c4ad6b) had a `*SmokeTests` E2E test on the daily smoke workflow only — no PR-CI coverage. The scaffolding-side follow-up had unit- level coverage on `ScaffoldingService` only. Neither caught the symmetric bug on the empty-template path before it had to be flagged manually. Close the gap with `NewCommandTemplateConfigPersistenceTests`, a single integration test that drives `NewCommand` end-to-end through the real `CliTemplateFactory` and the real writer for each template. Only `IAppHostServerProjectFactory` is swapped for a fake whose `PrepareAsync` returns failure, so the early on-disk channel write is captured without touching the network, the dotnet CLI, npm, or RPC. Five parameterised methods cover 21 cases across every CLI template that ships an `aspire.config.json` writer: * `aspire-ts-empty`, `aspire-py-empty`, `aspire-go-empty`, `aspire-java-empty` (writer: `ScaffoldingService`) — identity-not-registered → no pin; identity-matches → pin; `--channel` → pin; staging-without-flag → no pin. * `aspire-ts-starter` (writer: `CliTemplateFactory.TypeScriptStarterTemplate`) — same matrix; this is the PR-CI counterpart to the existing daily smoke test. * `aspire-go-starter`, `aspire-py-starter` (writer: none) — tripwires asserting the persisted channel stays null regardless of identity or `--channel`, so future code that re-introduces the dead `config.Channel = inputs.Channel` line is caught. Locally validated by simulating the pre-fix shape against the new tests: a `ScaffoldingService`-side identity-fallback fails the four empty-template "identity not registered" cases plus the staging variant; a starter-side identity-fallback fails the two ts-starter cases. Each failure points at the writer code path that drifted. `FakeFailingAppHostServerProject` is promoted from `GuestAppHostProjectTests` into `TestServices/` so both test classes share it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PR Testing ReportDogfooded the PR CLI on macOS arm64. CLI install verified —
Template sweep — identity-channel path (no
For every template that persists Headline scenario — The bug report quoted: On the PR CLI against the scaffolded Coverage gap — Result✅ Verified. TS starter scaffolds cleanly, |
| config.Channel = !string.IsNullOrEmpty(inputs.Channel) | ||
| ? inputs.Channel | ||
| : _executionContext.IdentityChannel; | ||
| if (!string.IsNullOrEmpty(inputs.Channel)) |
There was a problem hiding this comment.
IMO it is odd that TypeScript is special behavior here. It doesn't do the same thing as the Go or Python abovel
|
Retested the latest dogfood CLI for #17120 in the repo container runner. CLI version verified: matches PR head Scenarios covered:
The clean-HOME Artifacts from the run are under: |
| // or NewCommand's identity-match against a registered Explicit channel — see | ||
| // `CliTemplateFactory.EmptyTemplate.cs` for how `ScaffoldContext.Channel` is sourced). | ||
| // Do NOT fall back to `CliExecutionContext.IdentityChannel`: an identity that isn't a | ||
| // registered channel (e.g. `daily` on a CLI without the staging feature flag, or `pr-<N>` |
There was a problem hiding this comment.
This example should be staging, not daily. The daily channel is registered unconditionally in PackagingService, while staging is the one gated by the staging feature flag. As written, the comment contradicts the actual channel registration behavior.
…taging CLI (microsoft#17120) * fix(cli): aspire new fails to scaffold aspire-ts-starter on daily / staging CLI `aspire new aspire-ts-starter` on a daily-channel CLI (and likewise on staging / stable identities) fails the automatic restore that follows project creation, leaving the user with a half-scaffolded project that cannot build: $ aspire --version 13.4.0-preview.1.26264.16+642f3dfc $ aspire new aspire-ts-starter --name TsApp --output . --non-interactive ... Package source mapping matches found for package ID 'Aspire.Hosting' are: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json'. ... ERROR: Unable to find a stable package Aspire.Hosting with version (>= 13.3.2) - Found 2793 version(s) in https://pkgs.dev.azure.com/dnceng/.../dotnet9/... [ Nearest version: 13.4.0-preview.1.26229.5 ] - Versions from https://api.nuget.org/v3/index.json were not considered ... ❌ Automatic 'aspire restore' failed for the new TypeScript starter project. The generated `aspire.config.json` shows the underlying mismatch: { "sdk": { "version": "13.3.2" }, "channel": "daily", "packages": { "Aspire.Hosting.JavaScript": "13.3.2" } } The channel is pinned to `daily` (so PSM routes `Aspire.*` to the dnceng daily feed, which only hosts prereleases), but the SDK / package versions are stable `13.3.2` — which only exists on nuget.org. The two pins are not mutually satisfiable, so restore fails. The two halves of TypeScript-starter scaffolding were resolving the channel inconsistently: * `NewCommand` only consulted `CliExecutionContext.IdentityChannel` to pick the resolution channel when the identity was a local-build name (`pr-*`, `run-*`, `local`) — gated on `VersionHelper.IsLocalBuildChannel`. For a daily binary the identity is `daily`, which the gate excluded, so resolution fell through to the Implicit (nuget.org) channel and yielded the latest stable template package version. * The TypeScript starter factory then pinned `aspire.config.json`'s `channel` to `IdentityChannel` regardless of what `NewCommand` had actually selected. Subsequent `aspire restore` routed `Aspire.*` to the channel-specific feed via Package Source Mapping, but the version had already been chosen against Implicit, so the daily feed (which only contains the prerelease) rejected the stable version that landed in the project. PR-built CLIs hid the bug because `pr-<N>` is a local-build channel and the gate fired — so the resolved channel and the pinned channel happened to agree. Daily and staging never took that branch, so the gate's omission only surfaced through the daily smoke workflow. The Go and Python starters were *not* affected in practice: their factories tried to persist the channel into `.aspire/settings.json` via `AspireJsonConfiguration`, but those templates ship `aspire.config.json` instead and never carried a `.aspire/settings.json` — so `AspireJsonConfiguration.Load` returned `null` and the channel was never written. They worked by accident because the resulting project had no channel pin at all, leaving restore to use ambient NuGet sources via `PrebuiltAppHostServer`'s channel aggregation. The fix aligns NewCommand and the TypeScript factory on a single resolution policy, and drops the dead channel-write code from the Go and Python factories: * `NewCommand` prefers any channel whose name matches `ExecutionContext.IdentityChannel` (stable, staging, daily, local, `pr-<N>`) over the Implicit channel when `--channel` is not passed. Falls back to Implicit when the identity name isn't a registered channel (typo, future name). * The TypeScript factory only persists `config.Channel` when an Explicit channel was resolved (either `--channel` or a registered match for the running CLI's identity). Implicit (nuget.org) selections leave the channel unwritten so `aspire add` / `aspire restore` use the user's ambient NuGet config without a stale per-project pin. * The Go and Python factories no longer touch the project's config file. `PrebuiltAppHostServer` aggregates package sources from every registered channel when the project doesn't pin one, so daily-feed packages remain reachable on a daily CLI without each project carrying a channel name across machines. This makes the build → execution context → feed → restore pipeline mutually satisfiable on every channel: a daily CLI scaffolds a daily-channel project whose prerelease SDK version is reachable through the daily feed's Package Source Mapping; a stable CLI scaffolds a stable project whose version is reachable through nuget.org; a PR CLI scaffolds a `pr-<N>`-channel project against the PR hive; and so on. Note on `staging`: `PackagingService.GetChannelsAsync` only registers a `staging` channel when `KnownFeatures.IsStagingChannelEnabled` is true (feature flag or `configuration["channel"]="staging"`). A default staging-identity CLI therefore still falls back to Implicit here — auto-registering staging from the identity is tracked separately. Tests: * New `NewCommandChannelResolutionTests` cover the resolution matrix: identity matches a registered channel (daily / stable / staging- when-enabled / pr-<N>) / identity not registered / staging not registered → fallback to Implicit / explicit `--channel` overrides identity. * `TypeScriptStarterTemplateTests` renamed to `TypeScriptStarterSmokeTests` so the daily smoke workflow picks it up via its `*SmokeTests` filter — this is the regression coverage that catches identity-channel resolution bugs invisible on PR builds. Expanded to assert `aspire.config.json` has no channel pin when invoked without `--channel`, and is pinned when invoked with one. The pin ↔ SDK-version invariant skips the `staging` channel, whose feed can legitimately host stable versions. * `ChannelReseedTests` doc-comments updated for the new "persist only on Explicit" semantic in the TypeScript factory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(cli): keep BuildExecutionContextWithIdentity sdksDirectory inside the test workspace The other three directories produced by BuildExecutionContextWithIdentity (hivesDirectory / cacheDirectory / logsDirectory) all live under the caller's TemporaryWorkspace.WorkspaceRoot. Only sdksDirectory escaped to Path.GetTempPath(), which is inconsistent within the same method and goes against the AGENTS.md guidance to use the test workspace or Directory.CreateTempSubdirectory() rather than a deterministic name under Path.GetTempPath() that can collide across concurrent test runs. Mirror the shape used by CreateExecutionContextWithHives in TemplateNuGetConfigServiceTests (and CreateExecutionContext in InitCommandTests) and keep the placeholder sdks directory under the workspace root. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): aspire new aspire-ts-empty pins a channel that may not be a registered channel Follow-up to 8c4ad6b. The TypeScript starter factory was patched to persist `aspire.config.json#channel` only when NewCommand resolved an Explicit channel (`--channel`, or a registered identity-match). `ScaffoldingService` — the writer used by every empty template (TS / Python / Go / Java / Rust) and by `aspire init` polyglot — still had the original identity-fallback shape: var seedChannel = string.IsNullOrWhiteSpace(context.Channel) ? _cliExecutionContext.IdentityChannel : context.Channel; When NewCommand's resolution found no registered channel matching the running CLI's identity (e.g. a CLI whose `CliExecutionContext.IdentityChannel` is `pr-17120` on a machine where the matching hive isn't present, or a staging-identity CLI without the staging feature flag), it correctly fell back to the Implicit channel and passed `context.Channel = null` down to the scaffolder. `ScaffoldingService` then silently substituted the CLI's identity name — so the generated `aspire.config.json` carried a channel pin that no Package Source Mapping rule could satisfy: $ aspire new aspire-ts-empty --name TsEmpty ... $ cat TsEmpty/aspire.config.json { "channel": "pr-17120", // identity-fallback wrote this "sdk": { "version": "13.3.0" } // stable, from Implicit } $ aspire add Aspire.Hosting.Redis ❌ No integration packages were found. Drop the identity-fallback so `ScaffoldingService` persists `ScaffoldContext.Channel` verbatim and only when set. When unset, `PrebuiltAppHostServer` aggregates package sources from every registered channel — daily-feed packages stay reachable on a daily CLI without each project carrying a stale channel name. This brings the empty-template + polyglot-init path in line with the TS / Python / Go starter factories, all of which already had this shape. The now-unused `CliExecutionContext` ctor parameter is removed; the test fixture (`CliTestHelper.ScaffoldingServiceFactory`) is updated to match. `ChannelReseedTests` is rewritten so its scenarios assert the new contract: `ScaffoldContext.Channel` flows through verbatim, and a deliberately non-matching `IdentityChannel` is used as a tripwire that fails if anyone re-introduces the identity-fallback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(cli): add channel-persistence coverage for every aspire-new template that emits aspire.config.json The starter-side bug (8c4ad6b) had a `*SmokeTests` E2E test on the daily smoke workflow only — no PR-CI coverage. The scaffolding-side follow-up had unit- level coverage on `ScaffoldingService` only. Neither caught the symmetric bug on the empty-template path before it had to be flagged manually. Close the gap with `NewCommandTemplateConfigPersistenceTests`, a single integration test that drives `NewCommand` end-to-end through the real `CliTemplateFactory` and the real writer for each template. Only `IAppHostServerProjectFactory` is swapped for a fake whose `PrepareAsync` returns failure, so the early on-disk channel write is captured without touching the network, the dotnet CLI, npm, or RPC. Five parameterised methods cover 21 cases across every CLI template that ships an `aspire.config.json` writer: * `aspire-ts-empty`, `aspire-py-empty`, `aspire-go-empty`, `aspire-java-empty` (writer: `ScaffoldingService`) — identity-not-registered → no pin; identity-matches → pin; `--channel` → pin; staging-without-flag → no pin. * `aspire-ts-starter` (writer: `CliTemplateFactory.TypeScriptStarterTemplate`) — same matrix; this is the PR-CI counterpart to the existing daily smoke test. * `aspire-go-starter`, `aspire-py-starter` (writer: none) — tripwires asserting the persisted channel stays null regardless of identity or `--channel`, so future code that re-introduces the dead `config.Channel = inputs.Channel` line is caught. Locally validated by simulating the pre-fix shape against the new tests: a `ScaffoldingService`-side identity-fallback fails the four empty-template "identity not registered" cases plus the staging variant; a starter-side identity-fallback fails the two ts-starter cases. Each failure points at the writer code path that drifted. `FakeFailingAppHostServerProject` is promoted from `GuestAppHostProjectTests` into `TestServices/` so both test classes share it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
What users see
aspire new aspire-ts-starterfails with a daily CLI:Core issue
The generated
aspire.config.jsonpins two things that disagree:{ "sdk": { "version": "13.3.2" }, "channel": "daily" }channel: "daily"routesAspire.*to the dnceng daily feed (prereleases only)sdk.version: "13.3.2"is stable, which only exists on nuget.orgRestore can't satisfy both pins, so it fails.
Why the two values disagreed:
NewCommandpicked the template version from the Implicit (nuget.org) channel — because its identity-channel preference was gated onIsLocalBuildChannel, which excludesdaily/staging/stable— while the TypeScript template factory wrotechannelfromIdentityChannelregardless.Rule this PR enforces
The channel pinned into a new project matches the CLI's source identity.
NewCommandprefers any registered channel whose name matchesCliExecutionContext.IdentityChannel(stable,staging,daily,local,pr-<N>) over the Implicit channel when--channelis omitted. Falls back to Implicit when the identity isn't a registered channel.channelwhen an Explicit channel was resolved. Implicit selections leave the pin unwritten soaspire add/aspire restoreuse the user's ambient NuGet config..aspire/settings.json, which those templates don't ship) —PrebuiltAppHostServeralready aggregates sources from every registered channel when no pin is present, so daily-feed packages stay reachable.Result: SDK version and channel pin are always satisfiable by the same feed.
Notes
staging-identity CLI still falls back to Implicit becausePackagingServiceonly registers thestagingchannel behind a feature flag — tracked separately in aspire new: staging-identity CLI should auto-register the staging channel #17121.*SmokeTestsso the daily smoke workflow picks it up — that's the regression coverage that catches identity-channel bugs invisible on PR builds.