Skip to content

Commit 3c40701

Browse files
sebastienrosCopilot
andcommitted
Use channel mappings for integration restore
Project-reference integration restores now use the explicit channel's temporary NuGet.config when available so staging/DARC package source mappings are honored even without --source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c59a283 commit 3c40701

2 files changed

Lines changed: 36 additions & 26 deletions

File tree

src/Aspire.Cli/Projects/PrebuiltAppHostServer.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -319,15 +319,10 @@ private async Task<AppHostServerClosureManifest> BuildIntegrationClosureManifest
319319
var restoreDir = Path.Combine(_workingDirectory, "integration-restore");
320320
Directory.CreateDirectory(restoreDir);
321321

322-
// Only synthesize a temp NuGet.config (replacing nuget.config discovery via
323-
// RestoreConfigFile) when an explicit --source or auto-discovered local channel source
324-
// is in play. The explicit-channel-no-override path keeps the user's ambient
325-
// nuget.config in place and contributes channel mappings additively via
326-
// RestoreAdditionalProjectSources so private/internal feeds the user has configured
327-
// remain reachable for non-Aspire transitives during project-ref restore.
328-
using var temporaryNuGetConfig = !string.IsNullOrWhiteSpace(packageSourceOverride)
329-
? await TryCreateTemporaryNuGetConfigAsync(requestedChannel, packageSourceOverride, cancellationToken)
330-
: null;
322+
// Explicit channels need their package source mappings during project-reference restore.
323+
// RestoreAdditionalProjectSources can add the feed URLs, but it cannot override ambient
324+
// packageSourceMapping entries that might route Aspire* packages to another feed.
325+
using var temporaryNuGetConfig = await TryCreateTemporaryNuGetConfigAsync(requestedChannel, packageSourceOverride, cancellationToken);
331326
var channelSources = temporaryNuGetConfig is null
332327
? await GetNuGetSourcesAsync(requestedChannel, packageSourceOverride: null, cancellationToken)
333328
: null;

tests/Aspire.Cli.Tests/Projects/PrebuiltAppHostServerTests.cs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,21 +1614,22 @@ public async Task PrepareAsync_RestoreFailure_WithManyPackages_TruncatesPackageL
16141614
}
16151615

16161616
[Fact]
1617-
public async Task PrepareAsync_WithProjectReferencesAndExplicitChannelButNoOverride_UsesAdditionalSourcesNotRestoreConfigFile()
1617+
public async Task PrepareAsync_WithProjectReferencesAndExplicitChannelButNoOverride_UsesNuGetConfigForChannelMappings()
16181618
{
1619-
// Regression for finding #1 of the 2026-05-19 post-merge review: a project-ref restore
1620-
// with an explicit channel pin (daily/staging/pr-*) and NO --source must not replace the
1621-
// user's ambient nuget.config via <RestoreConfigFile>. The channel sources flow through
1622-
// additively via <RestoreAdditionalProjectSources> so private/internal feeds the user
1623-
// has configured in nuget.config remain reachable for non-Aspire transitives.
1619+
// Regression for https://github.com/microsoft/aspire/issues/17629: a project-ref restore
1620+
// with an explicit channel pin and no --source needs the channel's package source mappings,
1621+
// not only its feed URLs, otherwise ambient packageSourceMapping can route Aspire* packages
1622+
// away from the staging/DARC feed.
16241623
using var workspace = TemporaryWorkspace.Create(outputHelper);
1625-
const string channelSource = "https://pkgs.dev.azure.com/fake/v3/index.json";
1624+
const string channelSource = "https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-microsoft-aspire-abcdef12/nuget/v3/index.json";
16261625
XDocument? generatedProject = null;
1626+
XDocument? generatedRestoreConfig = null;
1627+
bool restoreConfigFileExistedDuringBuild = false;
16271628

16281629
var aspireConfigPath = Path.Combine(workspace.WorkspaceRoot.FullName, AspireConfigFile.FileName);
16291630
await File.WriteAllTextAsync(aspireConfigPath, """
16301631
{
1631-
"channel": "daily"
1632+
"channel": "staging"
16321633
}
16331634
""");
16341635

@@ -1641,20 +1642,32 @@ await File.WriteAllTextAsync(aspireConfigPath, """
16411642
BuildAsyncCallback = (projectFilePath, _, _, _) =>
16421643
{
16431644
generatedProject = XDocument.Load(projectFilePath.FullName);
1645+
var ns = generatedProject.Root!.GetDefaultNamespace();
1646+
var restoreConfigFile = generatedProject.Descendants(ns + "RestoreConfigFile").FirstOrDefault()?.Value;
1647+
restoreConfigFileExistedDuringBuild = restoreConfigFile is not null && File.Exists(restoreConfigFile);
1648+
if (restoreConfigFile is not null)
1649+
{
1650+
generatedRestoreConfig = XDocument.Load(restoreConfigFile);
1651+
}
1652+
16441653
WriteClosureInputs(projectFilePath.Directory!, closureFiles, ["MyIntegration"]);
16451654
return 0;
16461655
}
16471656
};
16481657

1649-
var dailyChannel = PackageChannel.CreateExplicitChannel(
1650-
name: "daily",
1658+
var stagingChannel = PackageChannel.CreateExplicitChannel(
1659+
name: "staging",
16511660
quality: PackageChannelQuality.Both,
1652-
mappings: [new PackageMapping("Aspire*", channelSource)],
1661+
mappings:
1662+
[
1663+
new PackageMapping("Aspire*", channelSource),
1664+
new PackageMapping(PackageMapping.AllPackages, NuGetOrgSource)
1665+
],
16531666
nuGetPackageCache: new FakeNuGetPackageCache(),
16541667
features: new TestFeatures());
16551668
var packagingService = new TestPackagingService
16561669
{
1657-
GetChannelsAsyncCallback = _ => Task.FromResult<IEnumerable<PackageChannel>>([dailyChannel])
1670+
GetChannelsAsyncCallback = _ => Task.FromResult<IEnumerable<PackageChannel>>([stagingChannel])
16581671
};
16591672

16601673
var nugetService = new BundleNuGetService(
@@ -1688,11 +1701,13 @@ await File.WriteAllTextAsync(aspireConfigPath, """
16881701
Assert.NotNull(generatedProject);
16891702

16901703
var ns = generatedProject!.Root!.GetDefaultNamespace();
1691-
Assert.Null(generatedProject.Descendants(ns + "RestoreConfigFile").FirstOrDefault());
1692-
1693-
var restoreSources = generatedProject.Descendants(ns + "RestoreAdditionalProjectSources").FirstOrDefault()?.Value;
1694-
Assert.NotNull(restoreSources);
1695-
Assert.Contains(channelSource, restoreSources!);
1704+
var restoreConfigFile = generatedProject.Descendants(ns + "RestoreConfigFile").FirstOrDefault()?.Value;
1705+
Assert.NotNull(restoreConfigFile);
1706+
Assert.True(restoreConfigFileExistedDuringBuild);
1707+
Assert.NotNull(generatedRestoreConfig);
1708+
Assert.Equal(["Aspire*"], GetPackagePatternsForSource(generatedRestoreConfig!, channelSource));
1709+
Assert.Equal([PackageMapping.AllPackages], GetPackagePatternsForSource(generatedRestoreConfig!, NuGetOrgSource));
1710+
Assert.Null(generatedProject.Descendants(ns + "RestoreAdditionalProjectSources").FirstOrDefault());
16961711

16971712
// Aspire package versions remain in their original (non-pinned) form when no override
16981713
// is in play; the exact-version pinning only fires when a single source is selected.

0 commit comments

Comments
 (0)