File: Commands\Package\List\PackageListCommand.cs
Web Access
Project: src\src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.CommandLine;
using System.Diagnostics;
using System.Globalization;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Commands.MSBuild;
using Microsoft.DotNet.Cli.Commands.NuGet;
using Microsoft.DotNet.Cli.Commands.Run;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectTools;

namespace Microsoft.DotNet.Cli.Commands.Package.List;

internal sealed class PackageListCommand(ParseResult parseResult) : CommandBase<PackageListCommandDefinitionBase>(parseResult)
{
    public override int Execute()
    {
        var (fileOrDirectory, allowedAppKinds) = PackageCommandParser.ProcessPathOptions(Definition.FileOption, Definition.ProjectOption, Definition.GetProjectOrFileArgument(), _parseResult);

        fileOrDirectory = Path.GetFullPath(fileOrDirectory);

        bool isFileBasedApp = allowedAppKinds.HasFlag(AppKinds.FileBased) && VirtualProjectBuilder.IsValidEntryPointPath(fileOrDirectory);

        Debug.Assert(isFileBasedApp || allowedAppKinds.HasFlag(AppKinds.ProjectBased));

        string projectFile = isFileBasedApp ? Path.GetFullPath(fileOrDirectory) : GetProjectOrSolution(fileOrDirectory);
        bool noRestore = _parseResult.HasOption(Definition.NoRestore);
        int restoreExitCode = 0;

        if (!noRestore)
        {
            ReportOutputFormat formatOption = _parseResult.GetValue(Definition.FormatOption);
            bool interactive = _parseResult.GetValue(Definition.InteractiveOption);
            restoreExitCode = RunRestore(projectFile, formatOption, interactive, isFileBasedApp);
        }

        return restoreExitCode == 0
            ? NuGetCommand.Run(TransformArgs(projectFile), isFileBasedApp)
            : restoreExitCode;
    }

    private static int RunRestore(string projectOrSolution, ReportOutputFormat formatOption, bool interactive, bool isFileBasedApp)
    {
        CommandBase command;
        if (isFileBasedApp)
        {
            command = new VirtualProjectBuildingCommand(
                entryPointFileFullPath: projectOrSolution,
                msbuildArgs: MSBuildArgs.FromProperties(new Dictionary<string, string>
                {
                    ["NuGetInteractive"] = interactive.ToString(),
                }.AsReadOnly()))
            {
                NoCache = true,
                NoBuild = true,
                NoConsoleLogger = formatOption == ReportOutputFormat.json,
            };
        }
        else
        {
            List<string> args = ["-target:Restore", projectOrSolution];

            if (formatOption == ReportOutputFormat.json)
            {
                args.Add("-noConsoleLogger");
            }
            else
            {
                args.Add("-consoleLoggerParameters:NoSummary");
                args.Add("-verbosity:minimal");
            }

            args.Add($"-interactive:{interactive.ToString().ToLower()}");

            command = new MSBuildForwardingApp(rawMSBuildArgs: args);
        }

        int exitCode = 0;

        try
        {
            exitCode = command.Execute();
        }
        catch (Exception)
        {
            exitCode = 1;
        }

        if (exitCode != 0)
        {
            if (formatOption == ReportOutputFormat.json)
            {
                string jsonError = $$"""
{
   "version": 1,
   "problems": [
      {
         "text": "{{String.Format(CultureInfo.CurrentCulture, CliCommandStrings.Error_restore)}}",
         "level": "error"
      }
   ]
}
""";
                Console.WriteLine(jsonError);
            }
        }

        return exitCode;
    }

    private string[] TransformArgs(string projectOrSolution)
    {
        var args = new List<string>
        {
            "package",
            "list",
            projectOrSolution
        };

        args.AddRange(_parseResult.OptionValuesToBeForwarded(Definition));

        Definition.EnforceOptionRules(_parseResult);

        return [.. args];
    }

    /// <summary>
    /// Gets a solution file or a project file from a given directory.
    /// If the given path is a file, it just returns it after checking
    /// it exists.
    /// </summary>
    /// <returns>Path to send to the command</returns>
    private static string GetProjectOrSolution(string fileOrDirectory)
    {
        string resultPath = fileOrDirectory;

        if (Directory.Exists(resultPath))
        {
            string[] possibleSolutionPath = SlnFileFactory.ListSolutionFilesInDirectory(resultPath, false);

            //If more than a single sln file is found, an error is thrown since we can't determine which one to choose.
            if (possibleSolutionPath.Count() > 1)
            {
                throw new GracefulException(CliStrings.MoreThanOneSolutionInDirectory, resultPath);
            }
            //If a single solution is found, use it.
            else if (possibleSolutionPath.Count() == 1)
            {
                return possibleSolutionPath[0];
            }
            //If no solutions are found, look for a project file
            else
            {
                var possibleProjectPath = Directory.GetFiles(resultPath, "*.*proj", SearchOption.TopDirectoryOnly)
                                          .Where(path => !path.EndsWith(".xproj", StringComparison.OrdinalIgnoreCase))
                                          .ToList();

                //No projects found throws an error that no sln nor projs were found
                if (possibleProjectPath.Count() == 0)
                {
                    throw new GracefulException(CliCommandStrings.NoProjectsOrSolutions, resultPath);
                }
                //A single project found, use it
                else if (possibleProjectPath.Count() == 1)
                {
                    return possibleProjectPath[0];
                }
                //More than one project found. Not sure which one to choose
                else
                {
                    throw new GracefulException(CliStrings.MoreThanOneProjectInDirectory, resultPath);
                }
            }
        }

        if (!File.Exists(resultPath))
        {
            throw new GracefulException(CliCommandStrings.PackageListFileNotFound, resultPath);
        }

        return resultPath;
    }
}