Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Aspire.Cli/Commands/InitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,14 @@ ServiceDefaults project contains helper code to make it easier

// Get template version/channel selection using the same logic as NewCommand
var selectedTemplateDetails = await GetProjectTemplatesVersionAsync(parseResult, cancellationToken);

// Create or update NuGet.config for explicit channels in the solution directory
// This matches the behavior of 'aspire new' when creating in-place
var nugetConfigPrompter = new NuGetConfigPrompter(InteractionService);
await nugetConfigPrompter.PromptToCreateOrUpdateAsync(
ExecutionContext.WorkingDirectory,
selectedTemplateDetails.Channel,
cancellationToken);

// Create a temporary directory for the template output
var tempProjectDir = Path.Combine(Path.GetTempPath(), $"aspire-init-{Guid.NewGuid()}");
Expand Down
105 changes: 105 additions & 0 deletions src/Aspire.Cli/Packaging/NuGetConfigPrompter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Cli.Interaction;
using Aspire.Cli.Resources;

namespace Aspire.Cli.Packaging;

/// <summary>
/// Handles prompting users to create or update NuGet.config files for explicit package channels.
/// </summary>
internal class NuGetConfigPrompter
{
private readonly IInteractionService _interactionService;

public NuGetConfigPrompter(IInteractionService interactionService)
{
ArgumentNullException.ThrowIfNull(interactionService);
_interactionService = interactionService;
}

/// <summary>
/// Prompts to create or update a NuGet.config for explicit channels.
/// Always prompts the user before creating or updating the file.
/// </summary>
/// <param name="targetDirectory">The directory where the NuGet.config should be created or updated.</param>
/// <param name="channel">The package channel providing mapping information.</param>
/// <param name="cancellationToken">A cancellation token to observe while waiting for the task to complete.</param>
public async Task PromptToCreateOrUpdateAsync(DirectoryInfo targetDirectory, PackageChannel channel, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(targetDirectory);
ArgumentNullException.ThrowIfNull(channel);

if (channel.Type is not PackageChannelType.Explicit)
{
return;
}

var mappings = channel.Mappings;
if (mappings is null || mappings.Length == 0)
{
return;
}

var hasConfigInTargetDir = NuGetConfigMerger.TryFindNuGetConfigInDirectory(targetDirectory, out var nugetConfigFile);
var hasMissingSources = hasConfigInTargetDir && NuGetConfigMerger.HasMissingSources(targetDirectory, channel);

if (!hasConfigInTargetDir)
{
// Ask for confirmation before creating the file
var choice = await _interactionService.PromptForSelectionAsync(
TemplatingStrings.CreateNugetConfigConfirmation,
[TemplatingStrings.Yes, TemplatingStrings.No],
c => c,
cancellationToken);

if (string.Equals(choice, TemplatingStrings.Yes, StringComparisons.CliInputOrOutput))
{
await NuGetConfigMerger.CreateOrUpdateAsync(targetDirectory, channel, cancellationToken: cancellationToken);
_interactionService.DisplayMessage("package", TemplatingStrings.NuGetConfigCreatedConfirmationMessage);
}
}
else if (hasMissingSources)
{
var updateChoice = await _interactionService.PromptForSelectionAsync(
"Update NuGet.config to add missing package sources for the selected channel?",
[TemplatingStrings.Yes, TemplatingStrings.No],
c => c,
cancellationToken);

if (string.Equals(updateChoice, TemplatingStrings.Yes, StringComparisons.CliInputOrOutput))
{
await NuGetConfigMerger.CreateOrUpdateAsync(targetDirectory, channel, cancellationToken: cancellationToken);
_interactionService.DisplayMessage("package", "Updated NuGet.config with required package sources.");
}
}
}

/// <summary>
/// Creates or updates a NuGet.config for explicit channels without prompting.
/// This is used when creating projects in subdirectories where the behavior is expected.
/// </summary>
/// <param name="targetDirectory">The directory where the NuGet.config should be created or updated.</param>
/// <param name="channel">The package channel providing mapping information.</param>
/// <param name="cancellationToken">A cancellation token to observe while waiting for the task to complete.</param>
public async Task CreateOrUpdateWithoutPromptAsync(DirectoryInfo targetDirectory, PackageChannel channel, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(targetDirectory);
ArgumentNullException.ThrowIfNull(channel);

if (channel.Type is not PackageChannelType.Explicit)
{
return;
}

var mappings = channel.Mappings;
if (mappings is null || mappings.Length == 0)
{
return;
}

await NuGetConfigMerger.CreateOrUpdateAsync(targetDirectory, channel, cancellationToken: cancellationToken);
_interactionService.DisplayMessage("package", "Created or updated NuGet.config in the project directory with required package sources.");
}
}
51 changes: 5 additions & 46 deletions src/Aspire.Cli/Templating/DotNetTemplateFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,60 +480,19 @@ private async Task PromptToCreateOrUpdateNuGetConfigAsync(PackageChannel channel
var normalizedWorkingPath = workingDir.FullName;
var isInPlaceCreation = string.Equals(normalizedOutputPath, normalizedWorkingPath, StringComparison.OrdinalIgnoreCase);

var nugetConfigPrompter = new NuGetConfigPrompter(interactionService);

if (!isInPlaceCreation)
{
// For subdirectory creation, always create/update NuGet.config in the output directory only
// and ignore any existing NuGet.config in the working directory
await NuGetConfigMerger.CreateOrUpdateAsync(outputDir, channel, cancellationToken: cancellationToken);
interactionService.DisplayMessage("package", "Created or updated NuGet.config in the project directory with required package sources.");
await nugetConfigPrompter.CreateOrUpdateWithoutPromptAsync(outputDir, channel, cancellationToken);
return;
}

// In-place creation: preserve existing behavior
// Check if we need to create or update a NuGet.config in the working directory
var hasConfigInWorkingDir = TryFindNuGetConfigInDirectory(workingDir, out var nugetConfigFile);
var hasMissingSources = hasConfigInWorkingDir && NuGetConfigMerger.HasMissingSources(workingDir, channel);

if (!hasConfigInWorkingDir)
{
// Ask for confirmation before creating the file
var choice = await interactionService.PromptForSelectionAsync(
TemplatingStrings.CreateNugetConfigConfirmation,
[TemplatingStrings.Yes, TemplatingStrings.No],
c => c,
cancellationToken);

if (string.Equals(choice, TemplatingStrings.Yes, StringComparisons.CliInputOrOutput))
{
await NuGetConfigMerger.CreateOrUpdateAsync(outputDir, channel, cancellationToken: cancellationToken);
interactionService.DisplayMessage("package", TemplatingStrings.NuGetConfigCreatedConfirmationMessage);
}
}
else if (hasMissingSources)
{
var updateChoice = await interactionService.PromptForSelectionAsync(
"Update NuGet.config to add missing package sources for the selected channel?",
[TemplatingStrings.Yes, TemplatingStrings.No],
c => c,
cancellationToken);

if (string.Equals(updateChoice, TemplatingStrings.Yes, StringComparisons.CliInputOrOutput))
{
await NuGetConfigMerger.CreateOrUpdateAsync(workingDir, channel, cancellationToken: cancellationToken);
interactionService.DisplayMessage("package", "Updated NuGet.config with required package sources.");
}
}
}

private static bool TryFindNuGetConfigInDirectory(DirectoryInfo directory, out FileInfo? nugetConfigFile)
{
ArgumentNullException.ThrowIfNull(directory);

// Search only the specified directory for a file named "nuget.config", ignoring case
nugetConfigFile = directory
.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
.FirstOrDefault(f => string.Equals(f.Name, "nuget.config", StringComparison.OrdinalIgnoreCase));
return nugetConfigFile is not null;
// Prompt user before creating or updating NuGet.config
await nugetConfigPrompter.PromptToCreateOrUpdateAsync(workingDir, channel, cancellationToken);
}
}