File: Commands\RestoreCommand.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.Tool.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.CommandLine;
using System.Globalization;
using Aspire.Cli.Configuration;
using Aspire.Cli.DotNet;
using Aspire.Cli.Interaction;
using Aspire.Cli.Projects;
using Aspire.Cli.Resources;
using Aspire.Cli.Telemetry;
using Aspire.Cli.Utils;
using Microsoft.Extensions.Logging;
 
namespace Aspire.Cli.Commands;
 
/// <summary>
/// Restores dependencies for .NET AppHost projects and generates SDK code for guest (non-.NET) AppHost projects.
/// For guest AppHosts, always regenerates without checking the hash, unlike <c>aspire run</c> which
/// skips code generation when the package hash is unchanged.
/// </summary>
internal sealed class RestoreCommand : BaseCommand
{
    internal override HelpGroup HelpGroup => HelpGroup.AppCommands;
 
    private readonly IProjectLocator _projectLocator;
    private readonly IAppHostProjectFactory _projectFactory;
    private readonly IDotNetCliRunner _runner;
    private readonly IDotNetSdkInstaller _sdkInstaller;
    private readonly IInteractionService _interactionService;
    private readonly ILogger<RestoreCommand> _logger;
 
    private static readonly OptionWithLegacy<FileInfo?> s_appHostOption = new("--apphost", "--project", SharedCommandStrings.AppHostOptionDescription);
 
    public RestoreCommand(
        IProjectLocator projectLocator,
        IAppHostProjectFactory projectFactory,
        IDotNetCliRunner runner,
        IDotNetSdkInstaller sdkInstaller,
        IFeatures features,
        ICliUpdateNotifier updateNotifier,
        CliExecutionContext executionContext,
        IInteractionService interactionService,
        ILogger<RestoreCommand> logger,
        AspireCliTelemetry telemetry)
        : base("restore", RestoreCommandStrings.Description, features, updateNotifier, executionContext, interactionService, telemetry)
    {
        _projectLocator = projectLocator;
        _projectFactory = projectFactory;
        _runner = runner;
        _sdkInstaller = sdkInstaller;
        _interactionService = interactionService;
        _logger = logger;
 
        Options.Add(s_appHostOption);
    }
 
    protected override async Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
    {
        var passedAppHostProjectFile = parseResult.GetValue(s_appHostOption);
 
        try
        {
            using var activity = Telemetry.StartDiagnosticActivity(Name);
 
            var searchResult = await _projectLocator.UseOrFindAppHostProjectFileAsync(
                passedAppHostProjectFile,
                MultipleAppHostProjectsFoundBehavior.Prompt,
                createSettingsFile: false,
                cancellationToken);
 
            var effectiveAppHostFile = searchResult.SelectedProjectFile;
 
            if (effectiveAppHostFile is null)
            {
                return ExitCodeConstants.FailedToFindProject;
            }
 
            var project = _projectFactory.TryGetProject(effectiveAppHostFile);
 
            if (project is null)
            {
                InteractionService.DisplayError(RestoreCommandStrings.UnrecognizedAppHostType);
                return ExitCodeConstants.FailedToFindProject;
            }
 
            if (project is DotNetAppHostProject)
            {
                if (!await SdkInstallHelper.EnsureSdkInstalledAsync(_sdkInstaller, InteractionService, Telemetry, cancellationToken))
                {
                    return ExitCodeConstants.SdkNotInstalled;
                }
 
                var appHostDirectory = effectiveAppHostFile.Directory!;
                _logger.LogDebug("Restoring packages for {AppHost} in {Directory}", effectiveAppHostFile.FullName, appHostDirectory.FullName);
 
                var restoreExitCode = await _interactionService.ShowStatusAsync(
                    RestoreCommandStrings.RestoringSdkCode,
                    async () => await _runner.RestoreAsync(effectiveAppHostFile, new DotNetCliRunnerInvocationOptions(), cancellationToken),
                    emoji: KnownEmojis.Gear);
 
                if (restoreExitCode == 0)
                {
                    _interactionService.DisplaySuccess(
                        string.Format(CultureInfo.CurrentCulture, RestoreCommandStrings.RestoreSucceeded, effectiveAppHostFile.Name));
                    return ExitCodeConstants.Success;
                }
 
                return ExitCodeConstants.FailedToBuildArtifacts;
            }
 
            if (project is GuestAppHostProject guestProject)
            {
                var directory = effectiveAppHostFile.Directory!;
                _logger.LogDebug("Restoring SDK code for {AppHost} in {Directory}", effectiveAppHostFile.FullName, directory.FullName);
 
                var success = await _interactionService.ShowStatusAsync(
                    RestoreCommandStrings.RestoringSdkCode,
                    async () => await guestProject.BuildAndGenerateSdkAsync(directory, cancellationToken),
                    emoji: KnownEmojis.Gear);
 
                if (success)
                {
                    _interactionService.DisplaySuccess(
                        string.Format(CultureInfo.CurrentCulture, RestoreCommandStrings.RestoreSucceeded, effectiveAppHostFile.Name));
                    return ExitCodeConstants.Success;
                }
 
                return ExitCodeConstants.FailedToBuildArtifacts;
            }
 
            InteractionService.DisplayError(RestoreCommandStrings.UnrecognizedAppHostType);
            return ExitCodeConstants.FailedToFindProject;
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            InteractionService.DisplayCancellationMessage();
            return ExitCodeConstants.Success;
        }
        catch (ProjectLocatorException ex)
        {
            return HandleProjectLocatorException(ex, InteractionService, Telemetry);
        }
        catch (Exception ex)
        {
            var errorMessage = string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.UnexpectedErrorOccurred, ex.Message);
            Telemetry.RecordError(errorMessage, ex);
            InteractionService.DisplayError(errorMessage);
            return ExitCodeConstants.FailedToBuildArtifacts;
        }
    }
}