fix(cli): keep guest AppHost restore on the selected Aspire package source#17166
Conversation
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 microsoft#17159 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 -- 17166Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17166" |
|
There is a second part of the fix that improves this when |
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>
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Pull request overview
This PR fixes aspire new aspire-empty --language typescript failures when callers supply --source (e.g., a PR hive) by threading the source override through the empty-template scaffolding path into AppHost server preparation, and ensuring the prebuilt AppHost restore uses that override (including exact-version restores when an explicit source is provided) to avoid mixed-binary package sets.
Changes:
- Thread
TemplateInputs.SourcethroughScaffoldContextintoIAppHostServerProject.PrepareAsync(...)aspackageSourceOverride. - Update
PrebuiltAppHostServerrestore logic to include the explicit source for both package-only restore and project-closure restore, and restore exact versions when an explicit source is provided. - Add/adjust tests to validate the override is passed through and that restore is invoked with the expected sources/version constraints.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Cli.Tests/TestServices/FakeFailingAppHostServerProject.cs | Updates fake to match new PrepareAsync signature with source override. |
| tests/Aspire.Cli.Tests/Scaffolding/ChannelReseedTests.cs | Adds coverage that scaffolding passes PackageSourceOverride into PrepareAsync. |
| tests/Aspire.Cli.Tests/Projects/PrebuiltAppHostServerTests.cs | Adds coverage that bundled restore uses --source override and exact-version package args. |
| tests/Aspire.Cli.Tests/Projects/AppHostServerSessionTests.cs | Updates test fake to match the updated PrepareAsync signature. |
| src/Aspire.Cli/Templating/CliTemplateFactory.EmptyTemplate.cs | Passes inputs.Source into ScaffoldContext.PackageSourceOverride for empty templates. |
| src/Aspire.Cli/Scaffolding/ScaffoldingService.cs | Forwards PackageSourceOverride into IAppHostServerProject.PrepareAsync. |
| src/Aspire.Cli/Scaffolding/IScaffoldingService.cs | Extends ScaffoldContext with PackageSourceOverride. |
| src/Aspire.Cli/Projects/PrebuiltAppHostServer.cs | Applies source override to restore paths; enforces exact version restore when override is set. |
| src/Aspire.Cli/Projects/IAppHostServerProject.cs | Extends PrepareAsync contract to accept an optional packageSourceOverride. |
| src/Aspire.Cli/Projects/DotNetBasedAppHostServerProject.cs | Updates signature to satisfy the extended IAppHostServerProject contract. |
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>
| /// </summary> | ||
| internal sealed class PrebuiltAppHostServer : IAppHostServerProject | ||
| { | ||
| private const string NuGetOrgSource = "https://api.nuget.org/v3/index.json"; |
There was a problem hiding this comment.
Why the constant? It is being used in a few places.
Resolve the PrebuiltAppHostServerTests conflict by keeping both the new main coverage for CLI log path propagation and the PR helper used by source override tests. Also address PR feedback on the NuGet.org fallback by documenting why the explicit source override keeps NuGet.org available for non-Aspire packages and transitive dependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…spire-empty-typescript-hive Two conflicts resolved manually: - src/Aspire.Cli/Projects/PrebuiltAppHostServer.cs: adopted origin/main's new IPackagingService.GetChannelsAsync signature (the extra requestedChannel filter argument) inside the PR's extracted GetExplicitRestoreChannelsAsync helper. - tests/Aspire.Cli.Tests/Projects/PrebuiltAppHostServerTests.cs: kept both branches' new tests (PrepareAsync_WithPackageReferences_UsesPackageSourceOverride and PrepareAsync_WithProjectReferencesAndPackageSourceOverride_UsesNuGetConfig from this branch, plus PrepareAsync_WithStagingPinnedProjectOutsideLaunchDirectory_UsesStagingSourcesAndNuGetConfig from origin/main). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…spire-empty-typescript-hive
…spire-empty-typescript-hive
danegsta
left a comment
There was a problem hiding this comment.
Found 3 issues around source/channel/feed resolution.
| var packages = packageRefs | ||
| .Select(r => (r.Name, Version: GetRestoreVersion(r.Name, r.Version!, useExactPackageVersions))) | ||
| .ToList(); | ||
| using var temporaryNuGetConfig = await TryCreateTemporaryNuGetConfigAsync(requestedChannel, packageSourceOverride, cancellationToken); |
There was a problem hiding this comment.
When --source is provided this temp config is generated with <clear />, and then passed to restore, so we stop honoring any nuget.config in the app directory. That breaks cases where the app config contains credentials, private feeds, or package source mappings for non-Aspire integration packages. Can we merge the source override into the effective config (or use an additive source/mapping approach) so the user's config remains in play, and include the requested source/channel/config in restore failures?
There was a problem hiding this comment.
Thanks — looked into the blast radius before changing this.
Today packageSourceOverride only reaches PrebuiltAppHostServer.PrepareAsync from one call site: ScaffoldingService.PrepareAsync (the aspire new aspire-empty --language <non-csharp> path). The other four callers — SdkGenerateCommand, SdkDumpCommand, GuestAppHostProject (used by aspire run/aspire restore/aspire add), and AppHostServerSession — all pass the default null. Plus, of the two ScaffoldContext construction sites, only CliTemplateFactory.EmptyTemplate forwards inputs.Source; InitCommand does not.
So the <clear />-emitting temp config kicks in at exactly one moment in the user's flow: the initial scaffold restore for aspire new --source. At that point the project directory is being freshly created — there is no project-local nuget.config to clobber. The pre-existing channel-driven <clear /> behavior on later commands is unchanged by this PR.
That makes the credentials / private-feeds / non-Aspire-PSM concern theoretical for the surface this PR introduces. Restore-failure context is a separate ask — that part is fair, and I've taken it on (see below).
If I've missed a path that flows --source into a context where a user-owned nuget.config is present, happy to fix it — please point me at it.
For the restore-failure-context piece of your comment, the next push enriches the PrepareAsync failure output with the --source, channel, and package context, so a failed aspire new --source <X> no longer requires a verbose re-run to see which inputs were in play. New test: PrepareAsync_RestoreFailure_OutputIncludesSourceAndChannelContext.
There was a problem hiding this comment.
Do we have any scenarios where our restore would interact with global nuget.config (or a nuget.config in a parent folder) after new? I'm mainly wondering if there's the potential for surprise if aspire new behaves differently from something like aspire add due to new using a specific config?
There was a problem hiding this comment.
new is supposed to write a nuget.config file based on the default or specified channel then add should use that nuget.config or channel.
There was a problem hiding this comment.
Thanks, this is the behavior the branch now moves toward: aspire new --source writes a project nuget.config so later aspire add/aspire restore can consume the same source state instead of treating --source as one-shot. It deliberately does not import parent/user/global config into the generated project.
One auth detail remains worth calling out: we reject embedded credentials, but sanitized URLs may not bind to user-level packageSourceCredentials if those credentials are keyed by a named source. We should decide whether to adopt an existing ambient source key for the same URL, rely on credential providers, or use another NuGet-native pattern.
danegsta
left a comment
There was a problem hiding this comment.
Additional test coverage request.
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 microsoft#17159
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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>
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>
…spire-empty-typescript-hive
`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>
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>
…uiltAppHostServer `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>
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>
…spire-empty-typescript-hive Resolved conflicts in PrebuiltAppHostServer.cs and its tests: - `GetNuGetSourcesAsync` and `TryCreateTemporaryNuGetConfigAsync` visibility widened to `internal` (from microsoft#17235) while keeping this branch's `packageSourceOverride` parameter. - Channel-name comparisons updated to `StringComparisons.ChannelName` to match the shared comparer introduced on main. - Kept both sets of tests in the overlap region: this branch's `--source` override tests plus microsoft#17235's staging-unavailable refusal tests. Adjusted the two new staging tests that call `GetNuGetSourcesAsync` directly to pass `packageSourceOverride: null` for the widened signature. Verified: `dotnet build tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj` succeeds with 0 errors / 0 warnings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A post-merge review of microsoft#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>
…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>
|
Since the last pushed revision, this branch now covers the broader guest AppHost source-coherence scope:
Validation rerun after the latest branch shape: dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-launch-profile -- --filter-class "*.Projects.PrebuiltAppHostServerTests" --filter-class "*.Commands.NewCommandTests" --filter-class "*.Commands.AddCommandTests" --filter-class "*.Projects.AppHostServerProjectTests" --filter-class "*.Scaffolding.ChannelReseedTests" --filter-class "*.Packaging.PackagingServiceTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"Result: 250 passed. I also validated the #17225 claim with the focused path tests: dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-launch-profile -- --filter-method "*.NewCommand_NoChannelArg_PrChannelIdentity_ResolvesTemplateFromPrChannel" --filter-method "*.ChannelPinningTemplate_IdentityMatchesRegisteredChannel_PinsThatChannel" --filter-method "*.PrepareAsync_WithHiveBackedChannel_UsesLocalAspireSourceAsOverride" --filter-method "*.PrepareAsync_RestoreFailure_WithAutoDiscoveredLocalSource_FooterShowsEffectiveSource" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"Result: 12 passed. |
|
Open for re-review. |
PR Testing ReportPR Information
CLI Version Verification
Changes AnalyzedPure CLI change. All edits are in Test Scenarios ExecutedEach scaffold scenario ran with the PR CLI binary and confirmed no Scaffold (container, linux-arm64)
Each scaffold produced the expected template artifacts (e.g. for TS empty: End-to-end
|
| # | Scenario | What was exercised | Result |
|---|---|---|---|
| 7 | TS empty repro + aspire run |
Scaffold + auto npm install → Connecting to AppHost… → dashboard up at https://localhost:17005/ (HTTP 302 login redirect). Deep log: no failure signature, no error/exception/fatal lines anywhere. |
✅ PASS |
| 8 | Py starter + aspire run |
Scaffold + auto npm install for frontend + uv venv for backend. Deep log: GuestAppHostProject Running TypeScript (Node.js) AppHost: …/py-starter-source/apphost.ts → Distributed application started. Full stack live and visible in ps: node tsx apphost.ts (TS guest AppHost) + python uvicorn main:app --ssl-keyfile … --ssl-certfile … (FastAPI over HTTPS) + vite --port 63475 (React frontend). Dashboard at https://localhost:17000/ (HTTP 302). |
✅ PASS |
Observations
aspire new aspire-empty --language typescriptfails with TypeLoadException when using PR hive packages #17159 explicit repro is fixed. Scenarios 1 and 7 are the exact failing command from the PR body. They now succeed with noTypeLoadException, and the new warning⚠️ --source was used for the initial scaffold restore only and is not persisted…(newTemplatingStringsentry) is emitted.- PR-acquired Aspire CLI should use matching PR package source for empty AppHost templates #17225 auto-discovery path works. Scenarios 2, 4, 6 use only
aspire new <template>(no--source), relying on the PR hive's channel registration carrying a localAspire*package source through restore. All succeed. - Cross-template coverage at scaffold time. Both TS empty and TS starter, plus the Python starter (which uses a TypeScript guest AppHost internally — the exact code path being fixed), all succeed in the container against this hive.
- Cross-language coverage at runtime. Scenario 8 is the strongest signal for "languages other than TypeScript": with no manual setup beyond
aspire new+aspire run, the Python starter brought up a real multi-process distributed app on osx-arm64 — Python FastAPI bound to its HTTPS port and the React vite dev server bound to its port — orchestrated by the TS guest AppHost that the PR's restore changes feed. The deep log'sDistributed application started.line is the canonical "AppHost RPC bootstrapped and codegen succeeded" signal that the originalAtsJsonCodeWriterfailure would have blocked. - Failure signature absent everywhere.
Aspire.TypeSystem.AtsJsonCodeWriter,TypeLoadException, andCould not load typedo not appear in any scaffold log (6 container scenarios), anyaspire runconsole output (2 host scenarios), or any deep~/.aspire/logs/cli_*.logfor those runs. - Benign warnings / log noise (not regressions):
- Container scaffolds:
❌ npm is not installed or not found in PATH— environmental (no Node.js in the runner image); the Aspire scaffold and Aspire-package restore itself completed before this point in every case. - Host
aspire run: one Kestrel SSL exception (System.IO.IOException: The encryption operation failed, innerBad address) appeared in the deep log aftercurl --max-time 5 https://localhost:17000/aborted a TLS handshake mid-flight. Pure client-disconnect artifact in the dashboard's Kestrel pipeline, unrelated to the fix.
- Container scaffolds:
Limitations
- Go starter not exposed in this hive.
aspire new --helplists onlyaspire-starter,aspire-ts-cs-starter,aspire-ts-starter,aspire-empty,aspire-ts-empty,aspire-py-starteron both linux-arm64 and osx-arm64 RIDs. The Go-side changes inCliTemplateFactory.GoStarterTemplate.cstherefore could not be exercised end-to-end against PR fix(cli): keep guest AppHost restore on the selected Aspire package source #17166's published artifacts — worth flagging whether the registration is gated, deliberately deferred, or just not yet shipped.
Overall Result
✅ PR VERIFIED on both container (linux-arm64) and host (osx-arm64), at both scaffold and aspire run levels. The originally failing TypeScript scaffolding path now succeeds with both explicit --source and auto-discovered local Aspire source, and a non-trivial multi-process distributed app (Python FastAPI + React/vite + TypeScript guest AppHost) starts end-to-end against the PR build.
IEvangelist
left a comment
There was a problem hiding this comment.
Two findings on the new --source / auto-discovery plumbing in PrebuiltAppHostServer.cs. Both are inline. The first is a behavioral inconsistency I think is worth fixing in this PR; the second is an accuracy nit on a comment that became stale after the auto-discovery commit landed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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>
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 microsoft#17159 Refs microsoft#17225 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…spire-empty-typescript-hive
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 microsoft#17159 Refs microsoft#17225 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…spire-empty-typescript-hive
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>
Description
What broke
Guest-language
aspire newscaffolding could restore AppHost/codegen packages from a different Aspire feed than the one selected by the CLI invocation.The explicit failure was:
Even though the user supplied the PR hive, restore could float to a different preview package set and TypeScript code generation failed with:
The same mismatch could happen in the normal PR dogfood flow from #17225:
when the running CLI was acquired from a PR hive but the prebuilt guest AppHost restore did not use the matching local Aspire package source.
Root cause
aspire newresolved the template version/channel, and it parsed--source, but those source/channel decisions were not consistently carried into the prebuilt guest AppHost restore.That left NuGet free to consider normal channel feeds for
Aspire*packages. Because prerelease versions are minimum ranges unless exact-pinned, restore could pick a different Aspire package set than the CLI assemblies doing codegen.Fix
The guest AppHost restore path now keeps
Aspire*packages on the selected Aspire source:--sourceis threaded through empty and starter guest-language scaffolding into prebuilt AppHost restoreAspire*mappings are auto-discovered as an effective source override when no--sourcewas suppliedAspire*packages are exact-pinned when using an explicit or auto-discovered local source, while non-Aspire dependencies keep normal restore semantics--sourceURLs that carry credentials (userinfo, query string, or fragment) are rejected up front with a dedicated error rather than being silently written into the project'sNuGet.configAspire*-source mismatches diagnosable without leaking secretsCall-outs
--sourceis still a one-shot scaffold restore input. It is not written into the generated project, so lateraspire restore/aspire adduse the project's channel configuration.The #17225 fix depends on the PR/local acquisition flow registering a matching channel with an existing local
Aspire*package source. If that hive/channel is absent, the CLI still falls back to the existing channel/ambient restore behavior.Fixes #17159
Refs #17225
Validation
dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-launch-profile -- --filter-class "*.Projects.PrebuiltAppHostServerTests" --filter-class "*.Commands.NewCommandTests" --filter-class "*.Commands.AddCommandTests" --filter-class "*.Projects.AppHostServerProjectTests" --filter-class "*.Scaffolding.ChannelReseedTests" --filter-class "*.Packaging.PackagingServiceTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-launch-profile -- --filter-method "*.NewCommand_NoChannelArg_PrChannelIdentity_ResolvesTemplateFromPrChannel" --filter-method "*.ChannelPinningTemplate_IdentityMatchesRegisteredChannel_PinsThatChannel" --filter-method "*.PrepareAsync_WithHiveBackedChannel_UsesLocalAspireSourceAsOverride" --filter-method "*.PrepareAsync_RestoreFailure_WithAutoDiscoveredLocalSource_FooterShowsEffectiveSource" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"Checklist
<remarks />and<code />elements on your triple slash comments?