Support default NuGet sources in Aspire configuration - #19392
Support default NuGet sources in Aspire configuration#19392Adam Ratzman (adamint) wants to merge 40 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Apply NuGet-aware package matching, secure extended-drive handling, and selecting-config precedence for out-of-tree AppHosts. Narrow credential guidance to supported provider authentication and refresh localization resources. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Repair malformed configuration before bootstrap, preserve source origin semantics, validate persistent sources, and keep explicit source compatibility. Add focused CLI regressions for templates, path safety, package IDs, and out-of-tree AppHosts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Normalize configuration in memory during bootstrap, keep unsafe sources ephemeral, and complete source-aware template restores. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Repair literal configuration keys, make source-backed template restores explicit, and carry configured-source routing policy through guest AppHost restores. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Carry required source routing policy through template, scaffolding, guest, and restore paths; enforce deterministic source-backed template restore and temporary-config cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Use the restore option names exposed by each Aspire template and verify them through the actual template engine. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Keep the no-restore description in template.json without adding an untranslated placeholder to protected localization files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Terminate template-engine subprocesses when the real alias regression test exceeds the repository test timeout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Merge disjoint duplicate objects during bootstrap normalization while retaining last-value-wins for type conflicts, and fully reap timed-out template test processes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Integrate the latest unrelated playground dependency security update before opening the pull request. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
…-cli-nuget-source
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
There was a problem hiding this comment.
Review details
Suppressed comments (7)
src/Aspire.Cli/Commands/IntegrationPackageSearchService.cs:149
- When no AppHost is found, this returns the raw
IConfigurationvalue and later resolves it against the invocation working directory. A relative source declared in a parent workspace or the global settings file therefore points at the wrong directory, contrary to the origin-aware behavior used when an AppHost is present. Resolve the value usingGetConfigurationFromDirectoryWithOriginAsyncbefore returning it.
return (
executionContext.WorkingDirectory,
ConfiguredChannel: null,
ConfiguredSource: NormalizeSource(invocationConfiguredSource),
LanguageId: null,
src/Aspire.Cli/Configuration/ConfigurationService.cs:401
- For legacy
.aspire/settings.json, the declaring directory is.aspire, but this records the workspace root as the origin. Consequently a relativenugetSourceis resolved one directory too high for read-only/non-migrating command paths. Use the legacy settings file's actual directory asBaseDirectory.
return Task.FromResult<ConfigurationValueWithOrigin?>(new(legacySettingsValue, searchDirectory, IsGlobal: false));
src/Aspire.Cli/Configuration/AspireConfigFile.cs:425
- This copies a relative legacy
nugetSourceunchanged while moving it from.aspire/settings.jsonto the parentaspire.config.json, changing the directory it resolves against. The migration already rebasesappHostPathfor this same layout change;nugetSourceneeds equivalent rebasing so migration does not silently redirect the feed.
config.NuGetSource = settings.NuGetSource;
src/Aspire.Cli/Packaging/PackageSourceOverrideMappings.cs:47
- This case-insensitive fast path bypasses the platform-specific path comparer below. On Linux, distinct enabled/configured feeds such as
/feeds/Releaseand/feeds/releaseare treated as the same source, causingaspire addto drop the configured override and install from the wrong ambient path. Keep only an ordinal exact-match fast path.
if (string.Equals(left, right, StringComparison.OrdinalIgnoreCase))
src/Aspire.Cli/Packaging/PackageSourceOverrideMappings.cs:65
- The URI comparison makes
PathAndQuerycase-insensitive, although HTTP path and query components can be case-sensitive. Two feeds on the same host that differ only by path casing are therefore considered enabled-equivalent, so the configured override is removed and package installation can hit a different endpoint. Compare only scheme/server case-insensitively and compare path/query ordinally.
leftUri,
rightUri,
UriComponents.SchemeAndServer | UriComponents.PathAndQuery,
UriFormat.Unescaped,
StringComparison.OrdinalIgnoreCase) == 0;
src/Aspire.Cli/Projects/GuestAppHostProject.cs:1373
- This always forwards
context.Sourceas a force override, even when it came from configuration (IsSourceExplicit == false).PrebuiltAppHostServertreats any non-null override as an exclusive temporary mapping, so guest AppHosts do not preserve ambient fallback sources/package-source mappings when the configured source is already enabled, unlike the documentedaspire addbehavior and the .NET project path. Apply the same configured-vs-explicit effective-source decision beforePrepareAsync.
var regenerateSuccess = await BuildAndGenerateSdkAsync(directory, config, context.Source, cancellationToken);
src/Aspire.Cli/Commands/NewCommand.cs:503
- This now places inherited local/global settings into the same
sourcevariable used for an explicit--source, and that value is later assigned toTemplateInputs.Source. Every template factory persistsTemplateInputs.SourcethroughCreateOrUpdateNuGetConfigForSourceOverrideAsync, so inherited sources are copied into generatednuget.configfiles; #19396 explicitly says this work must not do that because local paths become machine-specific and proxy rotation is blocked. Keep the configured source for discovery/installation, but distinguish it from an explicit source before generated-project persistence.
var source = await GetResolvedEffectiveSourceAsync(parseResult, cancellationToken);
- Files reviewed: 34/34 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
There was a problem hiding this comment.
Review details
Suppressed comments (3)
src/Aspire.Cli/Packaging/PackageSourceOverrideMappings.cs:65
- HTTP path and query components are not generally case-insensitive. This makes sources such as
https://feed.example/NuGetandhttps://feed.example/nugetmatch, causing the configured override to be removed even though they can route to different repositories. Compare only the scheme/server case-insensitively and comparePathAndQueryordinally.
StringComparison.OrdinalIgnoreCase) == 0;
src/Aspire.Cli/Commands/IntegrationPackageSearchService.cs:148
- When no AppHost is found, this returns the raw startup value and later resolves it against the invocation directory. A relative
nugetSourcedeclared in a parent workspace or global settings therefore points at the wrong directory forintegration list/search, contrary to the origin-based behavior used when an AppHost exists. Resolve the value using its declaring directory before returning this branch.
ConfiguredSource: NormalizeSource(invocationConfiguredSource),
src/Aspire.Cli/Commands/NewCommand.cs:503
- This effective source is forwarded as
TemplateInputs.Source, and the existing template factories persist every non-null source into the generated project'sNuGet.config. Consequently a source inherited from global or parent configuration is copied into each new project, including machine-specific absolute paths. Issue #19396 explicitly says this change must not copy inherited sources because doing so prevents later proxy rotation; track whether the source was explicit and only persist explicit overrides while still using configured sources for discovery/install/restore.
var source = await GetResolvedEffectiveSourceAsync(parseResult, cancellationToken);
- Files reviewed: 34/34 changed files
- Comments generated: 1
- Review effort level: Balanced
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa # Conflicts: # src/Aspire.Cli/Packaging/PackageChannel.cs # tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs # tests/Aspire.Cli.Tests/Packaging/PackageChannelTests.cs
There was a problem hiding this comment.
Review details
Suppressed comments (4)
src/Aspire.Cli/Packaging/PackageSourceOverrideMappings.cs:47
- This case-insensitive fast path runs before the source kind and platform are known. On Linux,
/Feedand/feedcan be distinct local sources; treating them as equal makesaspire adddrop the configured override and install from the wrong or unavailable feed. Use an exact fast path and let the platform-specific path comparison below handle casing.
if (string.Equals(left, right, StringComparison.OrdinalIgnoreCase))
src/Aspire.Cli/Packaging/PackageSourceOverrideMappings.cs:65
- This folds the entire HTTP path and query even though only the scheme and host are case-insensitive. Distinct feeds such as
/TenantA/v3/index.jsonand/tenanta/v3/index.jsoncan therefore match, causingaspire addto omit the configured source and use a different ambient feed. CompareSchemeAndServercase-insensitively andPathAndQueryordinally.
UriComponents.SchemeAndServer | UriComponents.PathAndQuery,
UriFormat.Unescaped,
StringComparison.OrdinalIgnoreCase) == 0;
src/Aspire.Cli/Projects/GuestAppHostProject.cs:1373
- This forwards every configured source as an override for guest AppHosts.
PrebuiltAppHostServertreats any non-null override like explicit--sourceand creates a<clear/>temporary NuGet.config, so an already-enabled configured source loses ambient fallback sources and package-source mappings. Mirror the effective-source check used byDotNetAppHostProjectso only explicit, disabled, rebound, or unavailable sources are forwarded.
var regenerateSuccess = await BuildAndGenerateSdkAsync(directory, config, context.Source, cancellationToken);
src/Aspire.Cli/Commands/IntegrationPackageSearchService.cs:148
- When
integration list/searchruns without an AppHost, this returns the startup value without its declaring directory. A relative source from a parent workspace or global settings is then resolved against the invocation directory inGetSearchChannelsAsync, contrary to the configuration-file-relative contract. Resolve the value throughIConfigurationServicein this branch as well.
ConfiguredSource: NormalizeSource(invocationConfiguredSource),
- Files reviewed: 34/34 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f541574-f58e-4d6d-8b84-16927f933faa
There was a problem hiding this comment.
Review details
Suppressed comments (2)
src/Aspire.Cli/Commands/IntegrationPackageSearchService.cs:148
- When no AppHost is selected, this returns the raw
IConfigurationvalue andGetSearchChannelsAsynclater resolves it against the invocation directory. A relativenugetSourcedeclared in an ancestor or global config therefore points at the wrong directory foraspire integration list/search—the AppHost-present path correctly usesBaseDirectory. Resolve the origin here too, and add a no-AppHost nested-directory regression case.
ConfiguredSource: NormalizeSource(invocationConfiguredSource),
src/Aspire.Cli/Commands/NewCommand.cs:503
- This collapses an inherited configured source and an explicit
--sourceinto the same value; downstreamTemplateInputs.Sourcethen callsCreateOrUpdateNuGetConfigForSourceOverrideAsync, soaspire newcopies the inherited source into the generated project's NuGet.config. The linked #19396 explicitly says this work must not automatically copy inherited sources because absolute local paths become machine-specific and proxies may rotate. Keep the configured source for discovery/install/initial restore, but only persist it when--sourcewas explicit.
var source = await GetResolvedEffectiveSourceAsync(parseResult, cancellationToken);
- Files reviewed: 34/34 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Description
--sourceworks for one invocation, but enterprise users need the same approved NuGet source applied by later CLI commands and editor integrations.This adds a persistent
nugetSourcesetting to local and global Aspire configuration. It is used by:aspire newaspire addaspire integration listaspire integration searchExplicit
--sourcestill wins. Relative sources resolve from the configuration file that declares them, including nested workspaces, out-of-tree AppHosts, and global settings.For
aspire add, explicit sources retain their existing force-override behavior. Configured sources use NuGet's effective source list: when the source is already enabled, Aspire preserves ambient fallback sources and package-source mappings; when it is disabled, rebound, removed, or otherwise unavailable, Aspire passes it directly to package installation.The setting is included in the modern and legacy configuration models, all committed editor schemas, CLI documentation, template prefetch behavior, integration discovery, and guest AppHost SDK generation. Additional credential and filesystem-source policy is tracked separately by #19396.
Usage
aspire config set nugetSource https://packagefeedproxy.microsoft.io/nuget/v3/index.jsonUse
--globalto make it the default outside the current workspace:aspire config set --global nugetSource https://packagefeedproxy.microsoft.io/nuget/v3/index.jsonFixes #19340
Validation
Aspire.Cli.Tests: 5,056 total; 5,022 passed; 34 platform skips; 0 failed4500a8d570541d7f573a4be746aff9dc7dd6f3ff: 84 passed, 0 failed, 4 explicitly omittedaspire newandaspire addworkflows against local feedsE/EM/EO/EMOtokens, and source-casing regressionsPlaywright proof
Checklist
<remarks />and<code />elements on your triple slash comments?