fix(cli): use matching hive source for guest AppHost restore#17227
fix(cli): use matching hive source for guest AppHost restore#17227radical wants to merge 7 commits into
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17227Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17227" |
Creating an empty TypeScript AppHost from PR hive packages failed even when the caller explicitly pointed the CLI at that hive with --source and --version. The empty guest-language scaffolding path parsed the source but dropped it before restoring the prebuilt AppHost packages, allowing NuGet to resolve a mismatched Aspire package set. Thread the explicit source through the guest scaffolding path into the prebuilt AppHost restore and use exact package versions when a source is provided so missing PR packages fail clearly instead of floating to a different prerelease. Refs microsoft#17159 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A hive-backed CLI can create a guest-language empty AppHost without an explicit --source. The prebuilt AppHost restore then has no durable source signal for the selected channel and can resolve Aspire packages from a different feed, mixing the running CLI assemblies with mismatched runtime/codegen packages. Resolve the selected channel's local Aspire package mapping inside PrebuiltAppHostServer when no explicit source was provided. The same path now covers PR hives, local hives, and future per-worktree hive channels, while explicit --source still wins and HTTP-backed channels keep their existing non-exact restore behavior. Fixes microsoft#17225 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The staging auto-registration flow depends on callers passing the requested channel name into PackagingService. Tighten the prebuilt AppHost staging regression test so it only exposes the staging channel when requested, preventing this coverage from passing accidentally if the requested channel is dropped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t --source When `--source` is not supplied (and a hive source is not auto-discovered), the synthesized integration project for the closure-manifest build now keeps emitting `<RestoreAdditionalProjectSources>` instead of `<RestoreConfigFile>`. Writing a temporary NuGet.config here was silent collateral from threading `packageSourceOverride` through the project-ref path: the temp config opens with `<clear />`, so any private feed in the user's ambient nuget.config that serves the project's transitive non-Aspire dependencies was being stripped on explicit channels (staging, daily). The override path still gets the cleared temp config so Aspire packages resolve from the chosen source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`ResolveLocalPackageSourceOverrideAsync` and the channel-lookup path inside `TryCreateTemporaryNuGetConfigAsync` both call `IPackagingService.GetChannelsAsync` directly. A transient failure there (e.g. malformed `aspire.config.json`, unexpected feed probe error) was propagating out to `PrepareAsync`'s outer catch and surfacing as a hard "scaffold failed" for the user. Catch unexpected exceptions in both lookups and degrade to "no override discovered" / "no PSM-bearing temp config", matching the long-standing defensive catch in `GetNuGetSourcesAsync`. Cancellation still propagates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Windows CLI job failed because the detached child log filename test checked that the generated filename did not contain the current process ID anywhere in the string. The filename now uses a random GUID suffix, and that suffix can coincidentally start with the same digits as the process ID. Assert the full filename shape instead. This still verifies that the log name uses the detach-child marker and a GUID suffix, while avoiding a false match on random GUID content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f6a53d0 to
64ce3cb
Compare
Restore the detached child log filename assertions so this PR remains focused on matching guest AppHost restore sources. The flaky Windows assertion is now tracked separately. Refs microsoft#17243 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a package-source coherence problem in the Aspire CLI guest-language empty AppHost scaffolding flow: when the running CLI comes from a PR/local hive (or an explicit --source), the prebuilt AppHost restore now uses a matching Aspire package source so runtime/codegen assemblies don’t mismatch (e.g., TypeScript AtsJsonCodeWriter TypeLoadException).
Changes:
- Plumbs an optional
PackageSourceOverridefromaspire newempty-template inputs → scaffolding context →IAppHostServerProject.PrepareAsync. - Updates
PrebuiltAppHostServerrestore behavior to (a) auto-discover a local hive-backed Aspire source when appropriate, and (b) apply an Aspire-only source override while preserving other mappings and NuGet.org fallback. - Adds/updates unit tests covering override propagation, exact-version pinning for Aspire packages under overrides, and graceful degradation when packaging-channel discovery fails.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Cli.Tests/TestServices/TestPackagingService.cs | Extends the packaging-service test fake to allow assertions involving requested channel names. |
| tests/Aspire.Cli.Tests/TestServices/FakeFailingAppHostServerProject.cs | Updates fake AppHost server project to match the updated PrepareAsync signature. |
| tests/Aspire.Cli.Tests/Scaffolding/ChannelReseedTests.cs | Adds a test ensuring scaffolding passes PackageSourceOverride through to PrepareAsync. |
| tests/Aspire.Cli.Tests/Projects/PrebuiltAppHostServerTests.cs | Adds coverage for restore-config behavior, exact-version pinning behavior, override precedence, and defensive fallback behavior. |
| tests/Aspire.Cli.Tests/Projects/AppHostServerSessionTests.cs | Updates test stub to match the updated PrepareAsync signature. |
| src/Aspire.Cli/Templating/CliTemplateFactory.EmptyTemplate.cs | Passes template --source through to scaffolding as PackageSourceOverride. |
| src/Aspire.Cli/Scaffolding/ScaffoldingService.cs | Threads PackageSourceOverride into IAppHostServerProject.PrepareAsync for guest-language scaffolding. |
| src/Aspire.Cli/Scaffolding/IScaffoldingService.cs | Extends ScaffoldContext with PackageSourceOverride. |
| src/Aspire.Cli/Projects/PrebuiltAppHostServer.cs | Implements the Aspire-only source override + local hive source auto-discovery and adjusts restore source/config generation accordingly. |
| src/Aspire.Cli/Projects/IAppHostServerProject.cs | Extends PrepareAsync contract with optional packageSourceOverride. |
| src/Aspire.Cli/Projects/DotNetBasedAppHostServerProject.cs | Updates implementation signature for the new optional PrepareAsync parameter. |
PR Testing ReportPR Information
CLI Version Verification
Changes AnalyzedFiles Changed
Change Categories
Test Scenarios ExecutedScenario 1: TypeScript empty AppHost with explicit PR hive sourceObjective: Verify Steps:
Evidence:
Observations:
Scenario 2: TypeScript empty AppHost using implicit PR channelObjective: Verify Steps:
Evidence:
Observations:
Scenario 3: TypeScript starter with explicit PR hive sourceObjective: Smoke test the current TypeScript starter template path with the PR hive source. Steps:
Evidence:
Observations:
Summary
Overall Result✅ PR VERIFIED for the source-restore scenarios tested. The PR CLI matches the latest PR commit, and the scenarios covering the changed guest AppHost restore paths completed successfully. Full ArtifactsCopied to: Cleanup / Inspection StatusThe temporary container workspace was removed after copying the report artifacts. |
|
Merging changes into #17166 . |
…icrosoft#17227 merge PR microsoft#17227's defensive catch around the channel-lookup helper had two call sites; only the auto-discovery one survived the merge into this branch. The PSM-temp-config no-override path still propagates a transient `IPackagingService.GetChannelsAsync` failure out to `PrepareAsync`'s outer catch, turning a transient packaging-service hiccup (malformed `aspire.config.json`, unexpected feed probe error) into a hard `aspire new` scaffold failure. Mirror the existing defensive catch into the no-override branch of `TryCreateTemporaryNuGetConfigAsync`: cancellation rethrows, anything else logs and returns null so restore falls through to the ambient nuget.config + caller-resolved channel sources path, matching the catch in `ResolveLocalPackageSourceOverrideAsync` and the long-standing catch in `GetNuGetSourcesAsync`. Restore the dropped degrade test (`PrepareAsync_WhenPackagingService- ThrowsDuringAutoDiscovery_DegradesGracefully`) so a future refactor can't silently regress this back. Also add two negative-path tests for `aspire-empty --language <guest>` source-coherence: - `PrepareAsync_WithHiveBackedChannelPointingAtMissingLocalDirectory_- DoesNotApplyOverride` pins that a stale `aspire.config.json` (user deleted the local hive but the channel pin remains) does not pin Aspire packages to a non-existent directory or emit exact-pin / NuGet.org fallback. - Extend `NewCommandWithEmptyTemplateAndSourceOverrideWarnsThatOverride- IsNotPersisted` to also cover python, go, and rust, matching the five guest languages registered in `DefaultLanguageDiscovery`. Refs microsoft#17159 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ource (#17166) * fix(cli): honor source for guest language package restore aspire new aspire-empty --language typescript --source <pr-hive> --version <pr-version> parsed --source, but the empty AppHost TypeScript scaffolding path dropped it before the prebuilt AppHost restored Aspire.Hosting and TypeScript code-generation packages. The bundled restore then searched channel sources, missed the requested PR hive packages, and NuGet floated to a stale preview package set, which later failed with TypeLoadException when the generator loaded against the PR CLI's Aspire.TypeSystem. Flow TemplateInputs.Source into ScaffoldContext, pass it to IAppHostServerProject.PrepareAsync, and have PrebuiltAppHostServer add that source to package and closure restores. When a source override is present, use exact version ranges so restore fails rather than silently resolving a different Aspire prerelease. Fixes #17159 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(cli): remove source restore diff noise Remove whitespace-only changes that are unrelated to the source restore fix, keeping the PR focused on the explicit package source propagation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): constrain source override restore behavior When an explicit package source is passed to guest AppHost restore, keep the exact-version pinning scoped to Aspire packages because that is the source mapping being overridden. Non-Aspire integration packages should retain normal NuGet minimum-version restore semantics. Also apply the temporary NuGet.config to the project-reference closure restore path instead of only adding sources to the synthetic project. That keeps package source mapping and channel-specific restore settings active when package and project integrations are restored together. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): keep --source override exclusive for Aspire packages When `aspire new aspire-empty --source <pr-hive>` ran without an explicit `--channel`, the temp NuGet.config built for restore folded in every explicit channel's `Aspire* -> channelSource` mapping alongside the override's `Aspire* -> packageSourceOverride`. NuGet treats same-pattern mappings on multiple sources as co-eligible, so Aspire packages could still resolve from a channel feed and silently defeat the override's fail-fast intent. Exact-version pinning masked this in practice because the requested PR-hive version was usually unique, but the package source mapping itself was no longer exclusive to the override. In the override branch of `TryCreateTemporaryNuGetConfigAsync`, only fold in mappings from an explicitly-requested, matched channel (skip the catch-all "all explicit channels" fallback baked into `GetExplicitRestoreChannelsAsync`), and drop any `Aspire*`-prefixed mappings from that matched channel before merging. Non-Aspire patterns (`CommunityToolkit*`, catch-all `*`) are preserved so non-Aspire transitives keep their channel feeds. Mirror the same gating in `GetNuGetSourcesAsync` so the bundled NuGet service's `sources` list doesn't broadcast every channel feed when `--source` is the override mechanism. Add seven `TryCreateTemporaryNuGetConfig_*` test cases covering the override-with-channels matrix (no channel, matched channel, channel with `Aspire*` mapping, channel with all-packages mapping, lookup failure, requested-channel threading) plus a `PrepareAsync_*` integration check for the NuGet.org fallback. `TestPackagingService` gains a `LastRequestedChannelName` observable so the new `PassesRequestedChannelToPackagingService` test can assert the override branch threads `requestedChannel` into the packaging service. Touch the comments near `NuGetOrgSource` and the `RestoreConfigFile`/`RestoreAdditionalProjectSources` split so they describe the actual constraint ("cannot float to NuGet.org or any other co-eligible feed"). Refs #17159 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): warn that aspire-empty --source is one-shot at scaffold Running `aspire new aspire-empty --language <non-csharp> --source <X>` succeeds at scaffold time but the override is consumed only for the initial restore inside `PrebuiltAppHostServer`. The scaffolded project persists only the channel and SDK version, so a follow-up `aspire add` or `aspire restore` in the same project resolves Aspire packages from the channel feeds in `aspire.config.json` rather than `<X>` — and silently produces a different package set, or fails when the channel does not carry the requested version. Emit a yellow warning immediately after the scaffold succeeds (when `inputs.Source` is non-empty on the non-C# branch) so users supplying `--source <pr-hive>/packages` are not surprised when subsequent commands miss the override. Persisting the feed into a generated `nuget.config` (and also honoring `--source` on the C# empty path, which silently drops it today) is left as a follow-up. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): include --source/channel context in scaffold restore failures When the prebuilt AppHost scaffold restore fails, the displayed output is the only debugging surface most users see. Previously it carried only the raw NuGet stderr ("Failed to prepare: Package restore failed: ..."), with no record of which `--source`, channel, or package versions had been in play. Reproducing the failure required a verbose re-run with diagnostic logging just to recover the inputs. Append the override source, the requested channel, and a short preview of the package list to the `OutputCollector` from both of `PrepareAsync`'s catch blocks (`AppHostServerPrepareFailedException` and the catch-all wrapper around `RestoreNuGetPackagesAsync`). When neither `--source` nor a channel was specified the helper is a no-op, so existing failure messages without these inputs are unchanged. Add an end-to-end `PrepareAsync` test that wires `--source` together with a channel whose `Aspire*` mapping conflicts with the override and asserts the temp `nuget.config` actually passed to the restore invocation drops the channel's `Aspire*` mapping, pinning that the override is authoritative for `Aspire*` packages end-to-end (and not only at the temp-config generator unit boundary). Add a `PrepareAsync_RestoreFailure_OutputIncludesSourceAndChannelContext` test that fails the restore via a non-zero exit and asserts the override path, channel name, and package id are present in the returned output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): honor --source override for guest-language starter templates `aspire new aspire-{ts,py,go}-starter --source <pr-hive> --version <pr>` hit the same TypeLoadException class of failure as `aspire-empty` did before the fix landed in this branch: the override was plumbed into PrebuiltAppHostServer.PrepareAsync for the empty-template path only, while starter templates went through GuestAppHostProject.BuildAndGenerateSdkAsync → PrepareAppHostServerAsync without forwarding the override, so Aspire packages restored from channel feeds rather than the requested source. Thread `packageSourceOverride` through IGuestAppHostSdkGenerator.BuildAndGenerateSdkAsync and the GuestAppHostProject prepare helper, then pass `inputs.Source` from all three guest starter templates. Hoist the "override is not persisted" warning into a shared helper on CliTemplateFactory so the empty and starter paths emit the same message; the warning fires only after a successful scaffold so it doesn't add noise behind a more prominent restore failure. Tests: - Expand the empty-template warning test to a [Theory] covering TypeScript and Java (the latter behind the experimental polyglot flag). - Add starter-template coverage for both the warning+plumb-through happy path and the failed-restore-suppresses-warning path. - Pin the restore-failure context footer shape (`--source:`, `channel:`, `packages:` labels) and the >5-package truncation behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(cli): rename EmptySourceOverrideNotPersistedWarning resource The shared `DisplaySourceOverrideNotPersistedWarningIfNeeded` helper on `CliTemplateFactory` is invoked by both `aspire-empty` and the three guest-language starter templates (TypeScript, Python, Go), so the `Empty*` prefix on the resource key is stale. Drop the prefix while the string is still pre-release and re-translation has not yet been triggered for translators. Renames the resource in `.resx`, `.Designer.cs`, the single production call site in `CliTemplateFactory.cs`, and four references in `NewCommandTests.cs` (empty and starter happy-path + suppression cases). `dotnet build /t:UpdateXlf src/Aspire.Cli/Aspire.Cli.csproj` regenerates the 13 `*.xlf` files to pick up the new `trans-unit id`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): align --source argument list with temp NuGet.config in PrebuiltAppHostServer `TryCreateTemporaryNuGetConfigAsync` already drops the matched channel's `Aspire*` mapping in the override branch, pinning Aspire package restoration to `--source` exclusively. But `GetNuGetSourcesAsync` — which builds the `--source` CLI argument list passed alongside the temp config — was still iterating every mapping in the matched channel and adding each mapping URL, including the channel's Aspire feed. The bundled NuGet tool treats `--source` CLI args as co-eligible with config mappings (which is why the original "don't fold in every explicit channel" comment exists in this method), so re-adding the channel's Aspire feed silently undoes the temp config's PSM drop and lets Aspire packages still resolve from the channel feed. A second, smaller divergence: when the matched channel had no `*` (AllPackages) mapping, the temp config added `* -> NuGet.org` as a catch-all but the sources list's `sources.Count == 1` heuristic only added NuGet.org in the no-channel case, leaving a mismatched catch-all whenever a matched channel contributed any non-Aspire mapping (e.g. `CommunityToolkit*`, `Microsoft.*`). In the matched-channel loop, skip mappings whose `PackageFilter` starts with "Aspire" when an override is set, and observe whether the matched channel supplied its own AllPackages mapping. After the loop, fall back to NuGet.org only when no AllPackages mapping was seen — the same rule the temp config uses for its catch-all. Tests: - `GetNuGetSources_WithPackageSourceOverrideAndMatchedChannel_OmitsChannelAspireFeedFromSources` pins that the channel's Aspire feed URL does NOT appear in the `--source` argument list, even though the channel maps `Aspire*` to it. This is the inverse assertion of the existing `TryCreateTemporaryNuGetConfig_WithPackageSourceOverride_DropsRequestedChannelAspireMappings` test on the config side. - `..._KeepsChannelSourceAndAddsNuGetOrgFallback` covers the `CommunityToolkit*` case: non-Aspire channel mapping stays, and NuGet.org is added because the matched channel has no AllPackages mapping. - `..._OmitsNuGetOrgFallback` covers a channel that already supplies `* -> channelSource`: NuGet.org should NOT be added, because the channel's own AllPackages mapping is the catch-all in both the temp config and the sources list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): redact credentials from --source in restore-failure output The new restore-failure context block introduced earlier in this branch echoes `--source: <user-supplied URL>` into the OutputCollector that ScaffoldingService displays on a failed scaffold. NuGet feed URLs routinely carry credentials — `https://name:pat@host/...` for token-auth feeds or SAS-style `?sv=...&sig=...` query tokens for blob-storage feeds — and that block is exactly the text users copy verbatim into GitHub issues, Teams chats, and CI failure transcripts. Add a `RedactSourceForDisplay` helper that strips UserInfo, Query, and Fragment from http/https URIs before display, and route the override through it from `AppendRestoreContextOnFailure`. Plain URLs without credentials/query are detected via early-return and pass through unchanged; local paths and `file://`-style sources bypass the URI branch and are emitted as-is. The redaction is only for the display copy — the actual restore invocation still receives the original source string. Cover the helper with a `[Theory]` exercising the no-redaction path (plain URL, Unix path, Windows path), the userinfo-only case, the query-only case, the combined userinfo+query case, and the fragment case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): auto-discover local Aspire source from requested channel When --source isn't supplied, PrebuiltAppHostServer now resolves the requested channel and, if it has a hive-backed Aspire* mapping pointing at an existing local directory, uses that as the package source override for both package-only and project-reference restore. That closes the dogfood gap where `aspire new aspire-empty --language typescript` from a PR/local CLI would resolve Aspire packages through the ambient channel feed instead of the CLI's own hive, surfacing as TypeLoadException during code generation. Channel-lookup failures are swallowed-and-logged (mirroring the existing defensive catches in TryCreateTemporaryNuGetConfigAsync and GetNuGetSourcesAsync); OperationCanceledException is re-thrown. Tests cover the explicit-channel-only path, the explicit-source-wins path, and that http-backed channels keep their existing non-exact restore behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): close 5 findings from PR #17166 post-merge review A post-merge review of #17166 (saved under .squad/log) flagged five issues in the prebuilt + DotNet-based AppHost restore paths that survived the `origin/main` merge. This addresses them. **#1 (HIGH) — Project-ref restore replaced ambient nuget.config for any non-Local explicit channel.** `BuildIntegrationClosureManifestAsync` called `TryCreateTemporaryNuGetConfigAsync` for every explicit channel, which emitted `<RestoreConfigFile>` and replaced nuget.config discovery wholesale. A user with a private/internal feed in their ambient nuget.config and a `daily` or `pr-*` channel pin would silently lose that feed during project-ref restore. Now only synthesize a temp nuget.config when `--source` is set; otherwise add channel sources via `<RestoreAdditionalProjectSources>` so the ambient nuget.config is preserved. **#2 (HIGH) — DotNetBasedAppHostServerProject accepted `packageSourceOverride` but ignored it.** The in-repo / dogfood path (selected whenever `AspireRepositoryDetector.DetectRepositoryRoot` returns non-null) declared the parameter to satisfy `IAppHostServerProject` but never threaded it into restore. The template factory was unconditionally telling users `--source was used for the initial scaffold restore only…` even when the override had been silently dropped. Thread the override through `CreateProjectFilesAsync` and prepend it to the `<RestoreAdditionalProjectSources>` list so the hive is the first source NuGet evaluates. This path does not use Package Source Mappings (PSM) like `PrebuiltAppHostServer` does — in dev mode most Aspire.* dependencies come from `ProjectReference` and the override is best- effort for the rare `PackageReference` fallback. Documented inline. **#3 (MED) — Restore-failure footer showed the original `--source`, not the auto-discovered effective one.** When `--source` was not passed but `ResolveLocalPackageSourceOverrideAsync` auto-discovered a local hive, the catches in `PrepareAsync` passed the original (unset) `packageSourceOverride` argument to `AppendRestoreContextOnFailure`. The user saw only the channel name and had no signal that a local hive participated in the failed restore. Lift `effectivePackageSourceOverride` to outer scope and pass it to the catches. **#4 (MED) — `BundleNuGetService` logged raw `--source` to the debug log.** The full restore args (including credentialed feed URLs) were emitted as a single debug line that downstream `RedactSourceForDisplay` never touched. Now build a redacted copy of the args specifically for the log line — the verbatim args still go to the process. Handles repeated `--source` flags and a missing trailing value defensively. **#5 (MED) — `RedactSourceForDisplay` failed open on malformed credentialed URLs.** `Uri.TryCreate` returns false for `https://user:p@ss@host/path` and `https://user:p#word@host/` (confirmed empirically), and the redactor's parse-failure branch returned the raw input. Such inputs were guaranteed to leak credentials into the failure footer that ships in bug reports. Fail closed for HTTP-shaped inputs by detecting `http://` / `https://` prefix before parsing and returning `<unparseable http source>` when the parse fails. Plain non-HTTP inputs (local paths, file://, etc.) still pass through unchanged. Refactor: extract `RedactSourceForDisplay` into a shared `PackageSourceRedactor` utility so the same redaction is applied wherever sources appear in user-visible output. `PrebuiltAppHostServer` keeps the internal static alias for back-compat with existing tests. Tests added: - `PrepareAsync_WithProjectReferencesAndExplicitChannelButNoOverride_UsesAdditionalSourcesNotRestoreConfigFile` - `PrepareAsync_RestoreFailure_WithAutoDiscoveredLocalSource_FooterShowsEffectiveSource` - `RedactSourceForDisplay_FailsClosedForMalformedHttpButPassesThroughLocalPaths` (5 inline cases) - `CreateProjectFiles_WithPackageSourceOverride_PrependsOverrideToRestoreAdditionalProjectSources` - `CreateProjectFiles_WithoutPackageSourceOverride_DoesNotInjectExtraSource` All 3297 tests in Aspire.Cli.Tests pass (0 failures, 20 platform skips). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): degrade restore on channel-lookup failure + cover gaps from #17227 merge PR #17227's defensive catch around the channel-lookup helper had two call sites; only the auto-discovery one survived the merge into this branch. The PSM-temp-config no-override path still propagates a transient `IPackagingService.GetChannelsAsync` failure out to `PrepareAsync`'s outer catch, turning a transient packaging-service hiccup (malformed `aspire.config.json`, unexpected feed probe error) into a hard `aspire new` scaffold failure. Mirror the existing defensive catch into the no-override branch of `TryCreateTemporaryNuGetConfigAsync`: cancellation rethrows, anything else logs and returns null so restore falls through to the ambient nuget.config + caller-resolved channel sources path, matching the catch in `ResolveLocalPackageSourceOverrideAsync` and the long-standing catch in `GetNuGetSourcesAsync`. Restore the dropped degrade test (`PrepareAsync_WhenPackagingService- ThrowsDuringAutoDiscovery_DegradesGracefully`) so a future refactor can't silently regress this back. Also add two negative-path tests for `aspire-empty --language <guest>` source-coherence: - `PrepareAsync_WithHiveBackedChannelPointingAtMissingLocalDirectory_- DoesNotApplyOverride` pins that a stale `aspire.config.json` (user deleted the local hive but the channel pin remains) does not pin Aspire packages to a non-existent directory or emit exact-pin / NuGet.org fallback. - Extend `NewCommandWithEmptyTemplateAndSourceOverrideWarnsThatOverride- IsNotPersisted` to also cover python, go, and rust, matching the five guest languages registered in `DefaultLanguageDiscovery`. Refs #17159 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): address source-restore review feedback Keep cancellation tokens last on the guest AppHost prepare/source APIs now that both requested channel and source override are threaded through the same calls. Move the staging-unavailable guard before temporary NuGet.config creation so the source-override project-reference restore path cannot silently fall back to NuGet.org when staging cannot be synthesized. Also update the project-reference restore comment to describe both explicit --source and auto-discovered local channel sources. Add direct PackageSourceRedactor coverage for happy paths, malformed HTTP inputs, whitespace-prefixed HTTP sources, and non-HTTP source forms. Trim HTTP inputs before detection/parsing so indented feed URLs are still redacted or fail closed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): persist aspire new source overrides An explicit `aspire new --source <source>` previously only affected the initial scaffold restore. The generated project did not record that source, so later `aspire add` or `aspire restore` could fall back to channel or ambient NuGet configuration and lose the Aspire package source selected at creation time. Persist source overrides into the generated project's NuGet.config by mapping `Aspire*` to the explicit source and keeping non-Aspire fallback sources from the resolved channel, or NuGet.org when no channel fallback is available. The persisted config remains self-contained: it does not import parent, user, or global NuGet sources, mappings, disabled sources, or credentials; only an existing project-local NuGet.config is merged. Remove the stale warning that source overrides are not persisted, share the source-override mapping logic with the prebuilt restore path, and add tests covering empty templates, starter templates, .NET templates, existing config merge behavior, and ambient-config non-absorption. Refs #17159 Refs #17225 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): reject credentialed new sources before persistence Persisting `aspire new --source` into a project NuGet.config makes the source durable project state. Credential-bearing HTTP URLs should not be written there because the generated file can be committed accidentally. Reject HTTP(S) sources that contain user info, query strings, or fragments before project creation starts, and keep the lower-level mapping helper from persisting those sources if it is called directly. The error points users at NuGet credential providers or user-level NuGet configuration instead of embedding secrets in the feed URL. Refs #17159 Refs #17225 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(cli): update PR-hive NuGet config snapshots The NuGet config merger no longer maps wildcard package resolution to the PR hive when a separate fallback source already owns `*`. Update the PR-hive snapshots so CI expects Aspire packages only from the hive and keeps the fallback mapping on the appropriate source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Description
A PR-acquired or local-hive Aspire CLI can create a guest-language empty AppHost while restoring Aspire runtime/codegen packages from a different package source than the CLI build.
The failing dogfood path is:
When the prebuilt guest AppHost restore resolves Aspire packages from a different channel than the running CLI, TypeScript code generation can fail with a binary-mismatch error:
Root cause: the empty guest-language scaffolding path did not carry the chosen package source into
PrebuiltAppHostServer. Explicit--sourcewas parsed byaspire new, and the selected channel could be stored inaspire.config.json, but the prebuilt restore still resolved Aspire packages through the normal channel/ambient restore path.Fix: the selected source now flows through empty-template scaffolding into
IAppHostServerProject.PrepareAsync, andPrebuiltAppHostServeruses it as an Aspire-specific package source override. When--sourceis absent,PrebuiltAppHostServerresolves the requested explicit channel and uses its existing localAspire*mapping when it points at a local package directory.The source override is intentionally limited to Aspire packages. Non-Aspire package mappings from the selected channel are preserved, and NuGet.org is kept available as a fallback for transitive dependencies outside the
Aspire*mapping. Project-reference restore only gets a synthesizedRestoreConfigFilewhen an Aspire source override is active, so normal explicit-channel restore continues to compose with the user's ambientnuget.config.Call-outs: explicit
--sourcestill wins over channel auto-discovery, HTTP-backed channels keep their existing non-exact restore behavior, and requested channel names are passed through to package-channel resolution so staging-channel auto-registration still works when staging is requested.Fixes #17225
Checklist
<remarks />and<code />elements on your triple slash comments?