Skip to content

fix(cli): use matching hive source for guest AppHost restore#17227

Closed
radical wants to merge 7 commits into
microsoft:mainfrom
radical:radical/fix-pr-cli-source-coherence
Closed

fix(cli): use matching hive source for guest AppHost restore#17227
radical wants to merge 7 commits into
microsoft:mainfrom
radical:radical/fix-pr-cli-source-coherence

Conversation

@radical
Copy link
Copy Markdown
Member

@radical radical commented May 18, 2026

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:

aspire new aspire-empty --language typescript

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:

RPC server exception:
System.TypeLoadException: Could not load type 'Aspire.TypeSystem.AtsJsonCodeWriter' from assembly 'Aspire.TypeSystem, Version=42.42.42.42, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.

Root cause: the empty guest-language scaffolding path did not carry the chosen package source into PrebuiltAppHostServer. Explicit --source was parsed by aspire new, and the selected channel could be stored in aspire.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, and PrebuiltAppHostServer uses it as an Aspire-specific package source override. When --source is absent, PrebuiltAppHostServer resolves the requested explicit channel and uses its existing local Aspire* 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 synthesized RestoreConfigFile when an Aspire source override is active, so normal explicit-channel restore continues to compose with the user's ambient nuget.config.

Call-outs: explicit --source still 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

  • 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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 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 -- 17227

Or

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

@davidfowl
Copy link
Copy Markdown
Contributor

cc @danegsta @mitchdenny @eerhardt

radical and others added 6 commits May 19, 2026 00:26
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>
@radical radical force-pushed the radical/fix-pr-cli-source-coherence branch from f6a53d0 to 64ce3cb Compare May 19, 2026 04:36
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>
@radical radical marked this pull request as ready for review May 19, 2026 04:59
@radical radical requested a review from mitchdenny as a code owner May 19, 2026 04:59
Copilot AI review requested due to automatic review settings May 19, 2026 04:59
@radical radical requested review from JamesNK and davidfowl as code owners May 19, 2026 04:59
@radical radical requested a review from joperezr May 19, 2026 04:59
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 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 PackageSourceOverride from aspire new empty-template inputs → scaffolding context → IAppHostServerProject.PrepareAsync.
  • Updates PrebuiltAppHostServer restore 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.

@radical
Copy link
Copy Markdown
Member Author

radical commented May 19, 2026

PR Testing Report

PR Information

CLI Version Verification

  • Expected Commit: 82f36ee
  • Installed Version: 13.4.0-pr.17227.g82f36eef
  • Status: ✅ Verified

Changes Analyzed

Files Changed

  • src/Aspire.Cli/Projects/PrebuiltAppHostServer.cs
  • src/Aspire.Cli/Projects/IAppHostServerProject.cs
  • src/Aspire.Cli/Projects/DotNetBasedAppHostServerProject.cs
  • src/Aspire.Cli/Scaffolding/IScaffoldingService.cs
  • src/Aspire.Cli/Scaffolding/ScaffoldingService.cs
  • src/Aspire.Cli/Templating/CliTemplateFactory.EmptyTemplate.cs
  • tests/Aspire.Cli.Tests/**

Change Categories

  • CLI changes detected — prebuilt guest AppHost restore source selection and scaffolding source propagation
  • Hosting integration changes
  • Dashboard changes
  • Client/component changes
  • Template product changes
  • Test changes detected

Test Scenarios Executed

Scenario 1: TypeScript empty AppHost with explicit PR hive source

Objective: Verify aspire new aspire-empty --language typescript succeeds when --source points to the PR hive package source.
Status: ✅ Passed

Steps:

  1. Ran PR CLI from /workspace/.aspire/dogfood/pr-17227/bin/aspire.
  2. Created ExplicitTsEmpty using --source /workspace/.aspire/hives/pr-17227/packages and --version 13.4.0-pr.17227.g82f36eef.
  3. Checked generated files and logs.

Evidence:

  • Log: report/scenario-explicit-source-new.log
  • File list: report/scenario-explicit-source-files.txt
  • Exit code: 0

Observations:

  • The command reached and completed Preparing Aspire server... without TypeLoadException or RPC mismatch errors.
  • Project files were created successfully.
  • The runner lacks npm, so the CLI emitted the expected warning that npm install could not run; the command still exited successfully.

Scenario 2: TypeScript empty AppHost using implicit PR channel

Objective: Verify aspire new aspire-empty --language typescript succeeds without --source, relying on the PR CLI/channel to use the matching local hive source.
Status: ✅ Passed

Steps:

  1. Ran PR CLI without passing --source.
  2. Created ImplicitTsEmpty.
  3. Checked generated files, aspire.config.json, and logs.

Evidence:

  • Log: report/scenario-implicit-channel-new.log
  • File list: report/scenario-implicit-channel-files.txt
  • Exit code: 0

Observations:

  • The command reached and completed Preparing Aspire server... without TypeLoadException or RPC mismatch errors.
  • Generated aspire.config.json records "channel": "pr-17227" and SDK version 13.4.0-pr.17227.g82f36eef.
  • The runner lacks npm, so the CLI emitted the expected npm install warning; the command still exited successfully.

Scenario 3: TypeScript starter with explicit PR hive source

Objective: Smoke test the current TypeScript starter template path with the PR hive source.
Status: ✅ Passed

Steps:

  1. Discovered the current template command is aspire-ts-starter.
  2. Created StarterExplicit using --source /workspace/.aspire/hives/pr-17227/packages, --version 13.4.0-pr.17227.g82f36eef, --localhost-tld false, --suppress-agent-init, and --non-interactive.
  3. Checked generated files and logs.

Evidence:

  • Help output: report/scenario-starter-explicit-help.log
  • Log: report/scenario-starter-explicit-new.log
  • File list: report/scenario-starter-explicit-files.txt
  • Exit code: 0

Observations:

  • Project files were created successfully.
  • The runner lacks npm, so the CLI emitted the expected npm install warning; the command still exited successfully.

Summary

Scenario Status Notes
TypeScript empty AppHost with explicit PR hive source ✅ Passed No restore/codegen mismatch errors
TypeScript empty AppHost using implicit PR channel ✅ Passed Generated config uses pr-17227; no restore/codegen mismatch errors
TypeScript starter with explicit PR hive source ✅ Passed Template command works with PR hive source; npm warning due runner environment

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 aspire run/start validation was not executed because the minimal repo-local runner image does not include Node/npm; the relevant aspire new scenarios still exercised the Preparing Aspire server... path where the reported TypeLoad/RPC package mismatch occurred.

Artifacts

Copied to: /Users/ankj/.copilot/workspaces/523004cf-85c5-4981-8b61-2f2692eda366/artifacts/pr-17227-testing-20260519T051232Z

Cleanup / Inspection Status

The temporary container workspace was removed after copying the report artifacts.

@radical
Copy link
Copy Markdown
Member Author

radical commented May 19, 2026

Merging changes into #17166 .

@radical radical closed this May 19, 2026
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.4 milestone May 19, 2026
radical added a commit to radical/aspire that referenced this pull request May 19, 2026
…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>
radical added a commit that referenced this pull request May 19, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PR-acquired Aspire CLI should use matching PR package source for empty AppHost templates

3 participants