diff --git a/Directory.Packages.props b/Directory.Packages.props
index 5a4aad32136..27058b57833 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -84,6 +84,8 @@
+
+
@@ -179,6 +181,7 @@
<_allowBuildFromSourcePackage Include="Microsoft.Extensions.FileSystemGlobbing" />
<_allowBuildFromSourcePackage Include="Microsoft.Web.Xdt" />
<_allowBuildFromSourcePackage Include="Newtonsoft.Json" />
+ <_allowBuildFromSourcePackage Include="Spectre.Console" />
<_allowBuildFromSourcePackage Include="System.Collections.Immutable" />
<_allowBuildFromSourcePackage Include="System.CommandLine" />
<_allowBuildFromSourcePackage Include="System.ComponentModel.Composition" />
diff --git a/NuGet.Config b/NuGet.Config
index 68f75ca225c..07a8d89d473 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -41,6 +41,7 @@
+
diff --git a/NuGet.sln b/NuGet.sln
index 6e3dd987608..89ddd5d7f97 100644
--- a/NuGet.sln
+++ b/NuGet.sln
@@ -19,6 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
build\config.props = build\config.props
Directory.Packages.props = Directory.Packages.props
build\DotNetSdkVersions.txt = build\DotNetSdkVersions.txt
+ NuGet.Config = NuGet.Config
build\sign.targets = build\sign.targets
spelling.dic = spelling.dic
build\test.targets = build\test.targets
diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/DependencyGraphPrinter.cs b/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/DependencyGraphPrinter.cs
index 7cf95afd387..61e0f1b1e4e 100644
--- a/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/DependencyGraphPrinter.cs
+++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/DependencyGraphPrinter.cs
@@ -7,19 +7,14 @@
using System.Collections.Generic;
using System.Linq;
using NuGet.Shared;
+using Spectre.Console;
+using Spectre.Console.Rendering;
namespace NuGet.CommandLine.XPlat.Commands.Why
{
internal static class DependencyGraphPrinter
{
- private const ConsoleColor TargetPackageColor = ConsoleColor.Cyan;
-
- // Dependency graph console output symbols
- private const string ChildNodeSymbol = "├─ ";
- private const string LastChildNodeSymbol = "└─ ";
-
- private const string ChildPrefixSymbol = "│ ";
- private const string LastChildPrefixSymbol = " ";
+ private static readonly Color TargetPackageColor = Color.Cyan;
///
/// Prints the dependency graphs for all target frameworks.
@@ -27,10 +22,10 @@ internal static class DependencyGraphPrinter
/// A dictionary mapping target frameworks to their dependency graphs.
/// The package we want the dependency paths for.
///
- public static void PrintAllDependencyGraphs(Dictionary?> dependencyGraphPerFramework, string targetPackage, ILoggerWithColor logger)
+ public static void PrintAllDependencyGraphs(Dictionary?> dependencyGraphPerFramework, string targetPackage, IAnsiConsole logger)
{
// print empty line
- logger.LogMinimal("");
+ logger.WriteLine();
// deduplicate the dependency graphs
List> deduplicatedFrameworks = GetDeduplicatedFrameworks(dependencyGraphPerFramework);
@@ -51,29 +46,27 @@ public static void PrintAllDependencyGraphs(DictionaryThe top-level package nodes of the dependency graph.
/// The package we want the dependency paths for.
///
- private static void PrintDependencyGraphPerFramework(List frameworks, List? topLevelNodes, string targetPackage, ILoggerWithColor logger)
+ private static void PrintDependencyGraphPerFramework(List frameworks, List? topLevelNodes, string targetPackage, IAnsiConsole logger)
{
- // print framework header
- foreach (var framework in frameworks)
- {
- logger.LogMinimal($" [{framework}]");
- }
-
- logger.LogMinimal($" {ChildPrefixSymbol}");
+ var tree = new Tree(string.Join("\n", frameworks.Select(f => $"[[{f}]]")));
if (topLevelNodes == null || topLevelNodes.Count == 0)
{
- logger.LogMinimal($" {LastChildNodeSymbol}{Strings.WhyCommand_Message_NoDependencyGraphsFoundForFramework}\n\n");
+ tree.AddNode(Strings.WhyCommand_Message_NoDependencyGraphsFoundForFramework);
+ logger.Write(PadTree(tree));
return;
}
var stack = new Stack();
// initialize the stack with all top-level nodes
- int counter = 0;
foreach (var node in topLevelNodes.OrderByDescending(c => c.Id, StringComparer.OrdinalIgnoreCase))
{
- stack.Push(new StackOutputData(node, prefix: " ", isLastChild: counter++ == 0));
+ stack.Push(new StackOutputData
+ {
+ Node = node,
+ ParentNode = tree
+ });
}
// print the dependency graph
@@ -81,41 +74,39 @@ private static void PrintDependencyGraphPerFramework(List frameworks, Li
{
var current = stack.Pop();
- string currentPrefix, childPrefix;
- if (current.IsLastChild)
- {
- currentPrefix = current.Prefix + LastChildNodeSymbol;
- childPrefix = current.Prefix + LastChildPrefixSymbol;
- }
- else
- {
- currentPrefix = current.Prefix + ChildNodeSymbol;
- childPrefix = current.Prefix + ChildPrefixSymbol;
- }
-
- // print current node
- if (current.Node.Id.Equals(targetPackage, StringComparison.OrdinalIgnoreCase))
- {
- logger.LogMinimal($"{currentPrefix}", Console.ForegroundColor);
- logger.LogMinimal($"{current.Node.Id} (v{current.Node.Version})\n", TargetPackageColor);
- }
- else
- {
- logger.LogMinimal($"{currentPrefix}{current.Node.Id} (v{current.Node.Version})");
- }
+ var treeNodeText = GetNodeText(current.Node, targetPackage);
+ var treeNode = current.ParentNode.AddNode(treeNodeText);
if (current.Node.Children?.Count > 0)
{
- // push all the node's children onto the stack
- counter = 0;
foreach (var child in current.Node.Children.OrderByDescending(c => c.Id, StringComparer.OrdinalIgnoreCase))
{
- stack.Push(new StackOutputData(child, childPrefix, isLastChild: counter++ == 0));
+ stack.Push(new StackOutputData
+ {
+ Node = child,
+ ParentNode = treeNode
+ });
}
}
}
- logger.LogMinimal("");
+ logger.Write(PadTree(tree));
+ logger.WriteLine();
+ }
+
+ private static IRenderable GetNodeText(DependencyNode node, string targetPackage)
+ {
+ string text = $"{node.Id} (v{node.Version})";
+ Style? style = node.Id.Equals(targetPackage, StringComparison.OrdinalIgnoreCase)
+ ? new Style(foreground: TargetPackageColor)
+ : null;
+
+ return new Text(text, style);
+ }
+
+ private static IRenderable PadTree(Tree tree)
+ {
+ return new Padder(tree, new Padding(left: 2, 0, 0, 0));
}
///
@@ -173,16 +164,9 @@ private static int GetDependencyGraphHashCode(List? graph)
private class StackOutputData
{
- public DependencyNode Node { get; set; }
- public string Prefix { get; set; }
- public bool IsLastChild { get; set; }
+ public required DependencyNode Node { get; init; }
- public StackOutputData(DependencyNode node, string prefix, bool isLastChild)
- {
- Node = node;
- Prefix = prefix;
- IsLastChild = isLastChild;
- }
+ public required IHasTreeNodes ParentNode { get; init; }
}
}
}
diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommand.cs b/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommand.cs
index 3acb50281ef..fac895af5ed 100644
--- a/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommand.cs
+++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommand.cs
@@ -9,6 +9,7 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.CommandLineUtils;
+using Spectre.Console;
namespace NuGet.CommandLine.XPlat.Commands.Why
{
@@ -22,9 +23,9 @@ internal static void Register(CommandLineApplication app)
});
}
- internal static void Register(Command rootCommand, Func getLogger)
+ internal static void Register(Command rootCommand, IAnsiConsole console)
{
- Register(rootCommand, getLogger, WhyCommandRunner.ExecuteCommand);
+ Register(rootCommand, console, WhyCommandRunner.ExecuteCommand);
}
///
@@ -34,10 +35,12 @@ internal static void Register(Command rootCommand, Func getLog
/// The dotnet nuget command handler, to add why to.
public static void GetWhyCommand(Command rootCommand)
{
- Register(rootCommand, CommandOutputLogger.Create, WhyCommandRunner.ExecuteCommand);
+ Register(rootCommand,
+ Spectre.Console.AnsiConsole.Console,
+ WhyCommandRunner.ExecuteCommand);
}
- internal static void Register(Command rootCommand, Func getLogger, Func> action)
+ internal static void Register(Command rootCommand, IAnsiConsole console, Func> action)
{
var whyCommand = new DocumentedCommand("why", Strings.WhyCommand_Description, "https://aka.ms/dotnet/nuget/why");
@@ -100,15 +103,13 @@ bool HasPathArgument(ArgumentResult ar)
whyCommand.SetAction(async (parseResult, cancellationToken) =>
{
- ILoggerWithColor logger = getLogger();
-
try
{
var whyCommandArgs = new WhyCommandArgs(
- parseResult.GetValue(path),
- parseResult.GetValue(package),
- parseResult.GetValue(frameworks),
- logger,
+ parseResult.GetValue(path)!,
+ parseResult.GetValue(package)!,
+ parseResult.GetValue(frameworks)!,
+ console,
cancellationToken);
int exitCode = await action(whyCommandArgs);
@@ -116,7 +117,7 @@ bool HasPathArgument(ArgumentResult ar)
}
catch (ArgumentException ex)
{
- logger.LogError(ex.Message);
+ console.Markup($"[red]{ex.Message}[/]");
return ExitCodes.InvalidArguments;
}
});
diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommandArgs.cs b/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommandArgs.cs
index d8c6ddc6022..db4a7efd335 100644
--- a/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommandArgs.cs
+++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommandArgs.cs
@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
+using Spectre.Console;
namespace NuGet.CommandLine.XPlat.Commands.Why
{
@@ -14,7 +15,7 @@ internal class WhyCommandArgs
public string Path { get; }
public string Package { get; }
public List Frameworks { get; }
- public ILoggerWithColor Logger { get; }
+ public IAnsiConsole Logger { get; }
public CancellationToken CancellationToken { get; }
///
@@ -29,7 +30,7 @@ public WhyCommandArgs(
string path,
string package,
List frameworks,
- ILoggerWithColor logger,
+ IAnsiConsole logger,
CancellationToken cancellationToken)
{
Path = path ?? throw new ArgumentNullException(nameof(path));
diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommandRunner.cs b/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommandRunner.cs
index aa54ce78a47..950329cfffd 100644
--- a/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommandRunner.cs
+++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommandRunner.cs
@@ -10,6 +10,7 @@
using System.Threading.Tasks;
using Microsoft.Build.Evaluation;
using NuGet.ProjectModel;
+using Spectre.Console;
namespace NuGet.CommandLine.XPlat.Commands.Why
{
@@ -38,11 +39,11 @@ public static Task ExecuteCommand(WhyCommandArgs whyCommandArgs)
}
catch (ArgumentException ex)
{
- whyCommandArgs.Logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.WhyCommand_Error_ArgumentExceptionThrown,
- ex.Message));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.WhyCommand_Error_ArgumentExceptionThrown,
+ ex.Message);
+ whyCommandArgs.Logger.MarkupLine($"[red]{message}[/]");
return Task.FromResult(ExitCodes.InvalidArguments);
}
@@ -63,7 +64,7 @@ public static Task ExecuteCommand(WhyCommandArgs whyCommandArgs)
if (dependencyGraphPerFramework != null)
{
- whyCommandArgs.Logger.LogMinimal(
+ whyCommandArgs.Logger.WriteLine(
string.Format(CultureInfo.CurrentCulture,
Strings.WhyCommand_Message_DependencyGraphsFoundInProject,
assetsFile.PackageSpec.Name,
@@ -73,7 +74,7 @@ public static Task ExecuteCommand(WhyCommandArgs whyCommandArgs)
}
else
{
- whyCommandArgs.Logger.LogMinimal(
+ whyCommandArgs.Logger.WriteLine(
string.Format(CultureInfo.CurrentCulture,
Strings.WhyCommand_Message_NoDependencyGraphsFoundInProject,
assetsFile.PackageSpec.Name,
@@ -89,7 +90,7 @@ public static Task ExecuteCommand(WhyCommandArgs whyCommandArgs)
return Task.FromResult(anyErrors ? ExitCodes.Error : ExitCodes.Success);
}
- private static IEnumerable<(string assetsFilePath, string? projectPath)> FindAssetsFiles(string path, ILoggerWithColor logger)
+ private static IEnumerable<(string assetsFilePath, string? projectPath)> FindAssetsFiles(string path, IAnsiConsole logger)
{
if (XPlatUtility.IsJsonFile(path))
{
@@ -108,23 +109,22 @@ public static Task ExecuteCommand(WhyCommandArgs whyCommandArgs)
{
if (!MSBuildAPIUtility.IsPackageReferenceProject(project))
{
- logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.Error_NotPRProject,
- project.FullPath));
-
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.Error_NotPRProject,
+ project.FullPath);
+ logger.MarkupLine($"[red]{message}[/]");
continue;
}
string? assetsFilePath = project.GetPropertyValue(ProjectAssetsFile);
if (string.IsNullOrEmpty(assetsFilePath) || !File.Exists(assetsFilePath))
{
- logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.Error_AssetsFileNotFound,
- project.FullPath));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.Error_AssetsFileNotFound,
+ project.FullPath);
+ logger.MarkupLine($"[red]{message}[/]");
continue;
}
@@ -132,7 +132,7 @@ public static Task ExecuteCommand(WhyCommandArgs whyCommandArgs)
}
else
{
- logger.LogMinimal(
+ logger.WriteLine(
string.Format(
CultureInfo.CurrentCulture,
Strings.WhyCommand_Message_NonSDKStyleProjectsAreNotSupported,
@@ -149,15 +149,15 @@ public static Task ExecuteCommand(WhyCommandArgs whyCommandArgs)
///
/// Validates that the input 'path' argument is a valid path to a directory, solution file or project file.
///
- private static bool ValidatePathArgument(string path, ILoggerWithColor logger)
+ private static bool ValidatePathArgument(string path, IAnsiConsole logger)
{
if (string.IsNullOrEmpty(path))
{
- logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.WhyCommand_Error_ArgumentCannotBeEmpty,
- "PROJECT|SOLUTION"));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.WhyCommand_Error_ArgumentCannotBeEmpty,
+ "PROJECT|SOLUTION");
+ logger.MarkupLine($"[red]{message}[/]");
return false;
}
@@ -169,11 +169,11 @@ private static bool ValidatePathArgument(string path, ILoggerWithColor logger)
}
catch (ArgumentException)
{
- logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.WhyCommand_Error_ArgumentExceptionThrown,
- string.Format(CultureInfo.CurrentCulture, Strings.Error_PathIsMissingOrInvalid, path)));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.WhyCommand_Error_ArgumentExceptionThrown,
+ string.Format(CultureInfo.CurrentCulture, Strings.Error_PathIsMissingOrInvalid, path));
+ logger.MarkupLine($"[red]{message}[/]");
return false;
}
@@ -186,24 +186,24 @@ private static bool ValidatePathArgument(string path, ILoggerWithColor logger)
}
else
{
- logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.WhyCommand_Error_ArgumentExceptionThrown,
- string.Format(CultureInfo.CurrentCulture, Strings.Error_PathIsMissingOrInvalid, path)));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.WhyCommand_Error_ArgumentExceptionThrown,
+ string.Format(CultureInfo.CurrentCulture, Strings.Error_PathIsMissingOrInvalid, path));
+ logger.MarkupLine($"[red]{message}[/]");
return false;
}
}
- private static bool ValidatePackageArgument(string package, ILoggerWithColor logger)
+ private static bool ValidatePackageArgument(string package, IAnsiConsole logger)
{
if (string.IsNullOrEmpty(package))
{
- logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.WhyCommand_Error_ArgumentCannotBeEmpty,
- "PACKAGE"));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.WhyCommand_Error_ArgumentCannotBeEmpty,
+ "PACKAGE");
+ logger.MarkupLine($"[red]{message}[/]");
return false;
}
@@ -213,19 +213,19 @@ private static bool ValidatePackageArgument(string package, ILoggerWithColor log
///
/// Validates that the input frameworks options have corresponding targets in the assets file. Outputs a warning message if a framework does not exist.
///
- private static void ValidateFrameworksOptionsExistInAssetsFile(LockFile assetsFile, List inputFrameworks, ILoggerWithColor logger)
+ private static void ValidateFrameworksOptionsExistInAssetsFile(LockFile assetsFile, List inputFrameworks, IAnsiConsole logger)
{
foreach (var frameworkAlias in inputFrameworks)
{
if (assetsFile.GetTarget(frameworkAlias, runtimeIdentifier: null) == null)
{
- logger.LogWarning(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.WhyCommand_Warning_AssetsFileDoesNotContainSpecifiedTarget,
- assetsFile.Path,
- assetsFile.PackageSpec.Name,
- frameworkAlias));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.WhyCommand_Warning_AssetsFileDoesNotContainSpecifiedTarget,
+ assetsFile.Path,
+ assetsFile.PackageSpec.Name,
+ frameworkAlias);
+ logger.MarkupLine($"[yellow]{message}[/]");
}
}
}
@@ -237,25 +237,25 @@ private static void ValidateFrameworksOptionsExistInAssetsFile(LockFile assetsFi
///
/// Logger for the 'why' command
/// Assets file for the given project. Returns null if there was any issue finding or parsing the assets file.
- private static LockFile? GetProjectAssetsFile(string assetsFilePath, string? projectPath, ILoggerWithColor logger)
+ private static LockFile? GetProjectAssetsFile(string assetsFilePath, string? projectPath, IAnsiConsole logger)
{
if (!File.Exists(assetsFilePath))
{
if (!string.IsNullOrEmpty(projectPath))
{
- logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.Error_AssetsFileNotFound,
- projectPath));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.Error_AssetsFileNotFound,
+ projectPath);
+ logger.MarkupLine($"[red]{message}[/]");
}
else
{
- logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.Error_PathIsMissingOrInvalid,
- assetsFilePath));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.Error_PathIsMissingOrInvalid,
+ assetsFilePath);
+ logger.MarkupLine($"[red]{message}[/]");
}
return null;
@@ -271,20 +271,20 @@ private static void ValidateFrameworksOptionsExistInAssetsFile(LockFile assetsFi
{
if (string.IsNullOrEmpty(projectPath))
{
- logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.WhyCommand_Error_InvalidAssetsFile_WithoutProject,
- assetsFilePath));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.WhyCommand_Error_InvalidAssetsFile_WithoutProject,
+ assetsFilePath);
+ logger.MarkupLine($"[red]{message}[/]");
}
else
{
- logger.LogError(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.WhyCommand_Error_InvalidAssetsFile_WithProject,
- assetsFilePath,
- projectPath));
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.WhyCommand_Error_InvalidAssetsFile_WithProject,
+ assetsFilePath,
+ projectPath);
+ logger.MarkupLine($"[red]{message}[/]");
}
return null;
diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/NuGet.CommandLine.XPlat.csproj b/src/NuGet.Core/NuGet.CommandLine.XPlat/NuGet.CommandLine.XPlat.csproj
index 850e30c7035..118c2aca8c6 100644
--- a/src/NuGet.Core/NuGet.CommandLine.XPlat/NuGet.CommandLine.XPlat.csproj
+++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/NuGet.CommandLine.XPlat.csproj
@@ -17,6 +17,7 @@
+
diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/Program.cs b/src/NuGet.Core/NuGet.CommandLine.XPlat/Program.cs
index f625524ad53..d34900d0f4f 100644
--- a/src/NuGet.Core/NuGet.CommandLine.XPlat/Program.cs
+++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/Program.cs
@@ -108,8 +108,8 @@ public static int MainInternal(string[] args, CommandOutputLogger log, IEnvironm
ConfigCommand.Register(nugetCommand, getHidePrefixLogger);
ConfigCommand.Register(rootCommand, getHidePrefixLogger);
- Commands.Why.WhyCommand.Register(nugetCommand, getHidePrefixLogger);
- Commands.Why.WhyCommand.Register(rootCommand, getHidePrefixLogger);
+ Commands.Why.WhyCommand.Register(nugetCommand, Spectre.Console.AnsiConsole.Console);
+ Commands.Why.WhyCommand.Register(rootCommand, Spectre.Console.AnsiConsole.Console);
}
CancellationTokenSource tokenSource = new CancellationTokenSource();
diff --git a/test/NuGet.Core.FuncTests/Dotnet.Integration.Test/DotnetWhyTests.cs b/test/NuGet.Core.FuncTests/Dotnet.Integration.Test/DotnetWhyTests.cs
index 2519be13095..2ed30711691 100644
--- a/test/NuGet.Core.FuncTests/Dotnet.Integration.Test/DotnetWhyTests.cs
+++ b/test/NuGet.Core.FuncTests/Dotnet.Integration.Test/DotnetWhyTests.cs
@@ -56,7 +56,7 @@ await SimpleTestPackageUtility.CreatePackagesAsync(
// Assert
Assert.Equal(ExitCodes.Success, result.ExitCode);
- Assert.Contains($"Project '{ProjectName}' has the following dependency graph(s) for '{packageY.Id}'", result.AllOutput);
+ Assert.Contains($"Project '{ProjectName}' has the following dependency graph(s) for '{packageY.Id}'", result.AllOutput.Replace("\n", "").Replace("\r", ""));
}
[Fact]
@@ -118,7 +118,7 @@ await SimpleTestPackageUtility.CreatePackagesAsync(
// Assert
Assert.Equal(ExitCodes.Success, result.ExitCode);
- Assert.Contains($"Project '{ProjectName}' has the following dependency graph(s) for '{packageY.Id}'", result.AllOutput);
+ Assert.Contains($"Project '{ProjectName}' has the following dependency graph(s) for '{packageY.Id}'", result.AllOutput.Replace("\n", "").Replace("\r", ""));
}
[Fact]
@@ -150,7 +150,7 @@ await SimpleTestPackageUtility.CreatePackagesAsync(
// Assert
Assert.Equal(ExitCodes.Success, result.ExitCode);
- Assert.Contains($"Project '{ProjectName}' has the following dependency graph(s) for '{packageY.Id}'", result.AllOutput);
+ Assert.Contains($"Project '{ProjectName}' has the following dependency graph(s) for '{packageY.Id}'", result.AllOutput.Replace("\n", "").Replace("\r", ""));
}
[Fact]
@@ -186,40 +186,6 @@ public void WhyCommand_EmptyPackageArgument_Fails()
Assert.Contains($"Required argument missing for command: 'why'.", result.Errors);
}
- [Fact]
- public async Task WhyCommand_InvalidFrameworksOption_WarnsCorrectly()
- {
- // Arrange
- var pathContext = new SimpleTestPathContext();
- var inputFrameworksOption = "invalidFrameworkAlias";
- var project = XPlatTestUtils.CreateProject(ProjectName, pathContext, Constants.ProjectTargetFramework);
-
- var packageX = XPlatTestUtils.CreatePackage("PackageX", "1.0.0", Constants.ProjectTargetFramework);
- var packageY = XPlatTestUtils.CreatePackage("PackageY", "1.0.1", Constants.ProjectTargetFramework);
-
- packageX.Dependencies.Add(packageY);
-
- project.AddPackageToFramework(Constants.ProjectTargetFramework, packageX);
-
- await SimpleTestPackageUtility.CreatePackagesAsync(
- pathContext.PackageSource,
- packageX,
- packageY);
-
- string addPackageCommandArgs = $"add {project.ProjectPath} package {packageX.Id}";
- CommandRunnerResult addPackageResult = _testFixture.RunDotnetExpectSuccess(pathContext.SolutionRoot, addPackageCommandArgs, testOutputHelper: _testOutputHelper);
-
- string whyCommandArgs = $"nuget why {project.ProjectPath} {packageY.Id} -f {inputFrameworksOption} -f {Constants.ProjectTargetFramework}";
-
- // Act
- CommandRunnerResult result = _testFixture.RunDotnetExpectSuccess(pathContext.SolutionRoot, whyCommandArgs, testOutputHelper: _testOutputHelper);
-
- // Assert
- Assert.Equal(ExitCodes.Success, result.ExitCode);
- Assert.Contains($"warn : The assets file '{project.AssetsFileOutputPath}' for project '{ProjectName}' does not contain a target for the specified input framework '{inputFrameworksOption}'.", result.AllOutput);
- Assert.Contains($"Project '{ProjectName}' has the following dependency graph(s) for '{packageY.Id}'", result.AllOutput);
- }
-
[Fact]
public async Task WhyCommand_DirectoryWithProject_HasTransitiveDependency_DependencyPathExists()
{
@@ -250,7 +216,7 @@ await SimpleTestPackageUtility.CreatePackagesAsync(
// Assert
Assert.Equal(ExitCodes.Success, result.ExitCode);
- Assert.Contains($"Project '{ProjectName}' has the following dependency graph(s) for '{packageY.Id}'", result.AllOutput);
+ result.AllOutput.Replace("\n", "").Replace("\r", "").Should().Contain($"Project '{ProjectName}' has the following dependency graph(s) for '{packageY.Id}'");
}
[Fact]
diff --git a/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/NuGet.XPlat.FuncTest.csproj b/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/NuGet.XPlat.FuncTest.csproj
index 73610909851..4d55c7a887b 100644
--- a/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/NuGet.XPlat.FuncTest.csproj
+++ b/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/NuGet.XPlat.FuncTest.csproj
@@ -1,4 +1,4 @@
-
+
$(LatestNETCoreTargetFramework)
Functional tests for nuget in dotnet CLI scenarios, using the NuGet.CommandLine.XPlat assembly.
@@ -13,6 +13,10 @@
+
+
+
+
diff --git a/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/XPlatWhyTests.cs b/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/XPlatWhyTests.cs
index ecc9b6d6a46..2003e977c2c 100644
--- a/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/XPlatWhyTests.cs
+++ b/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/XPlatWhyTests.cs
@@ -3,10 +3,12 @@
using System.Threading;
using System.Threading.Tasks;
+using FluentAssertions;
using NuGet.CommandLine.XPlat;
using NuGet.CommandLine.XPlat.Commands.Why;
using NuGet.Packaging;
using NuGet.Test.Utility;
+using Spectre.Console.Testing;
using Xunit;
using Xunit.Abstractions;
@@ -50,23 +52,35 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async(
var addPackageCommandRunner = new AddPackageReferenceCommandRunner();
var addPackageResult = await addPackageCommandRunner.ExecuteCommand(addPackageArgs, new MSBuildAPIUtility(logger));
+ var console = new TestConsole();
+ console.Width(100);
+
var whyCommandArgs = new WhyCommandArgs(
project.ProjectPath,
packageY.Id,
[projectFramework],
- logger,
+ console,
CancellationToken.None);
// Act
var result = await WhyCommandRunner.ExecuteCommand(whyCommandArgs);
// Assert
- var output = logger.ShowMessages();
+ var output = console.Output;
+
+ string[] expected =
+ [
+ "Project 'Test.Project.DotnetNugetWhy' has the following dependency graph(s) for 'PackageY':",
+ "",
+ " [net472] ",
+ " └── PackageX (v1.0.0) ",
+ " └── PackageY (v1.0.1) ",
+ "",
+ ""
+ ];
Assert.Equal(ExitCodes.Success, result);
- Assert.Contains($"Project '{ProjectName}' has the following dependency graph(s) for '{packageY.Id}'", output);
- Assert.Contains($"{packageX.Id} (v{packageX.Version})", output);
- Assert.Contains($"{packageY.Id} (v{packageY.Version})", output);
+ output.Should().Be(string.Join("\n", expected));
}
[Fact]
@@ -93,18 +107,21 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async(
var addPackageCommandRunner = new AddPackageReferenceCommandRunner();
var addPackageResult = await addPackageCommandRunner.ExecuteCommand(addPackageArgs, new MSBuildAPIUtility(logger));
+ var console = new TestConsole();
+ console.Width(500);
+
var whyCommandArgs = new WhyCommandArgs(
project.ProjectPath,
packageZ.Id,
[projectFramework],
- logger,
+ console,
CancellationToken.None);
// Act
var result = await WhyCommandRunner.ExecuteCommand(whyCommandArgs);
// Assert
- var output = logger.ShowMessages();
+ var output = console.Output;
Assert.Equal(ExitCodes.Success, result);
Assert.Contains($"Project '{ProjectName}' does not have a dependency on '{packageZ.Id}'", output);
@@ -114,7 +131,8 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async(
public async Task WhyCommand_ProjectDidNotRunRestore_Fails()
{
// Arrange
- var logger = new TestCommandOutputLogger(_testOutputHelper);
+ var logger = new TestConsole();
+ logger.Width(500);
var pathContext = new SimpleTestPathContext();
var projectFramework = "net472";
@@ -138,7 +156,7 @@ public async Task WhyCommand_ProjectDidNotRunRestore_Fails()
var result = await WhyCommandRunner.ExecuteCommand(whyCommandArgs);
// Assert
- var output = logger.ShowMessages();
+ var output = logger.Lines;
Assert.Equal(ExitCodes.Success, result);
Assert.Contains($"No assets file was found for `{project.ProjectPath}`. Please run restore before running this command.", output);
@@ -148,7 +166,8 @@ public async Task WhyCommand_ProjectDidNotRunRestore_Fails()
public async Task WhyCommand_EmptyProjectArgument_Fails()
{
// Arrange
- var logger = new TestCommandOutputLogger(_testOutputHelper);
+ var logger = new TestConsole();
+ logger.Width(500);
var whyCommandArgs = new WhyCommandArgs(
"",
@@ -161,17 +180,17 @@ public async Task WhyCommand_EmptyProjectArgument_Fails()
var result = await WhyCommandRunner.ExecuteCommand(whyCommandArgs);
// Assert
- var errorOutput = logger.ShowErrors();
+ var errorOutput = logger.Lines;
Assert.Equal(ExitCodes.InvalidArguments, result);
- Assert.Contains($"Unable to run 'dotnet nuget why'. The 'PROJECT|SOLUTION' argument cannot be empty.", errorOutput);
+ errorOutput.Should().Contain($"Unable to run 'dotnet nuget why'. The 'PROJECT|SOLUTION' argument cannot be empty.");
}
[Fact]
public async Task WhyCommand_EmptyPackageArgument_Fails()
{
// Arrange
- var logger = new TestCommandOutputLogger(_testOutputHelper);
+ var logger = new TestConsole();
var pathContext = new SimpleTestPathContext();
var projectFramework = "net472";
@@ -188,7 +207,7 @@ public async Task WhyCommand_EmptyPackageArgument_Fails()
var result = await WhyCommandRunner.ExecuteCommand(whyCommandArgs);
// Assert
- var errorOutput = logger.ShowErrors();
+ var errorOutput = logger.Lines;
Assert.Equal(ExitCodes.InvalidArguments, result);
Assert.Contains($"Unable to run 'dotnet nuget why'. The 'PACKAGE' argument cannot be empty.", errorOutput);
@@ -198,7 +217,8 @@ public async Task WhyCommand_EmptyPackageArgument_Fails()
public async Task WhyCommand_InvalidProject_Fails()
{
// Arrange
- var logger = new TestCommandOutputLogger(_testOutputHelper);
+ var logger = new TestConsole();
+ logger.Width(500);
string fakeProjectPath = "FakeProjectPath.csproj";
@@ -213,7 +233,7 @@ public async Task WhyCommand_InvalidProject_Fails()
var result = await WhyCommandRunner.ExecuteCommand(whyCommandArgs);
// Assert
- var errorOutput = logger.ShowErrors();
+ var errorOutput = logger.Lines;
Assert.Equal(ExitCodes.InvalidArguments, result);
Assert.Contains($"Unable to run 'dotnet nuget why'. Missing or invalid path '{fakeProjectPath}'. Please provide a path to a project, solution file, or directory.", errorOutput);
@@ -246,18 +266,21 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async(
var addPackageCommandRunner = new AddPackageReferenceCommandRunner();
var addPackageResult = await addPackageCommandRunner.ExecuteCommand(addPackageCommandArgs, new MSBuildAPIUtility(logger));
+ var console = new TestConsole();
+ console.Width(500);
+
var whyCommandArgs = new WhyCommandArgs(
project.ProjectPath,
packageY.Id,
[inputFrameworksOption, projectFramework],
- logger,
+ console,
CancellationToken.None);
// Act
var result = await WhyCommandRunner.ExecuteCommand(whyCommandArgs);
// Assert
- var output = logger.ShowMessages();
+ var output = console.Output;
Assert.Equal(ExitCodes.Success, result);
Assert.Contains($"The assets file '{project.AssetsFileOutputPath}' for project '{ProjectName}' does not contain a target for the specified input framework '{inputFrameworksOption}'.", output);
diff --git a/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/Commands/Why/WhyCommandLineParsingTests.cs b/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/Commands/Why/WhyCommandLineParsingTests.cs
index 44a4e3379f5..33ad8d85daa 100644
--- a/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/Commands/Why/WhyCommandLineParsingTests.cs
+++ b/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/Commands/Why/WhyCommandLineParsingTests.cs
@@ -7,6 +7,7 @@
using FluentAssertions;
using NuGet.CommandLine.XPlat.Commands;
using NuGet.CommandLine.XPlat.Commands.Why;
+using Spectre.Console.Testing;
using Xunit;
namespace NuGet.CommandLine.Xplat.Tests.Commands.Why
@@ -20,7 +21,7 @@ public void WhyCommand_HasHelpUrl()
Command rootCommand = new("nuget");
// Act
- WhyCommand.Register(rootCommand, NullLoggerWithColor.GetInstance);
+ WhyCommand.Register(rootCommand, new TestConsole());
// Assert
rootCommand.Subcommands[0].Should().BeAssignableTo();
@@ -33,7 +34,7 @@ public void WithTwoArguments_PathAndPackageAreSet()
// Arrange
Command rootCommand = new("nuget");
- WhyCommand.Register(rootCommand, NullLoggerWithColor.GetInstance, whyCommandArgs =>
+ WhyCommand.Register(rootCommand, new TestConsole(), whyCommandArgs =>
{
// Assert
whyCommandArgs.Path.Should().Be(@"path\to\my.proj");
@@ -54,7 +55,7 @@ public void WithOneArguments_PackageIsSet()
// Arrange
Command rootCommand = new("nuget");
- WhyCommand.Register(rootCommand, NullLoggerWithColor.GetInstance, whyCommandArgs =>
+ WhyCommand.Register(rootCommand, new TestConsole(), whyCommandArgs =>
{
// Assert
whyCommandArgs.Path.Should().NotBeNull();
@@ -75,7 +76,7 @@ public void WithZeroArguments_HasParseError()
// Arrange
Command rootCommand = new("nuget");
- WhyCommand.Register(rootCommand, NullLoggerWithColor.GetInstance, whyCommandArgs =>
+ WhyCommand.Register(rootCommand, new TestConsole(), whyCommandArgs =>
{
// Assert
throw new Exception("Should not get here");
@@ -92,7 +93,7 @@ public void WithThreeArguments_HasParseError()
// Arrange
Command rootCommand = new("nuget");
- WhyCommand.Register(rootCommand, NullLoggerWithColor.GetInstance, whyCommandArgs =>
+ WhyCommand.Register(rootCommand, new TestConsole(), whyCommandArgs =>
{
// Assert
throw new Exception("Should not get here");
@@ -112,7 +113,7 @@ public void FrameworkOption_CanBeAtAnyPosition(string args)
// Arrange
Command rootCommand = new("nuget");
- WhyCommand.Register(rootCommand, NullLoggerWithColor.GetInstance, whyCommandArgs =>
+ WhyCommand.Register(rootCommand, new TestConsole(), whyCommandArgs =>
{
// Assert
whyCommandArgs.Path.Should().Be("my.proj");
@@ -135,7 +136,7 @@ public void FrameworkOption_CanBeLongOrShortForm(string arg)
// Arrange
Command rootCommand = new("nuget");
- WhyCommand.Register(rootCommand, NullLoggerWithColor.GetInstance, whyCommandArgs =>
+ WhyCommand.Register(rootCommand, new TestConsole(), whyCommandArgs =>
{
// Assert
whyCommandArgs.Path.Should().Be("my.proj");
@@ -156,7 +157,7 @@ public void FrameworkOption_AcceptsMultipleValues()
// Arrange
Command rootCommand = new("nuget");
- WhyCommand.Register(rootCommand, NullLoggerWithColor.GetInstance, whyCommandArgs =>
+ WhyCommand.Register(rootCommand, new TestConsole(), whyCommandArgs =>
{
// Assert
whyCommandArgs.Path.Should().Be("my.proj");
@@ -177,7 +178,7 @@ public void HelpOption_ShowsHelp()
// Arrange
Command rootCommand = new("nuget");
- WhyCommand.Register(rootCommand, NullLoggerWithColor.GetInstance, whyCommandArgs =>
+ WhyCommand.Register(rootCommand, new TestConsole(), whyCommandArgs =>
{
// Assert
whyCommandArgs.Path.Should().Be("my.proj");
diff --git a/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/NuGet.CommandLine.Xplat.Tests.csproj b/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/NuGet.CommandLine.Xplat.Tests.csproj
index cebd25bcdb3..6a6b4be08e4 100644
--- a/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/NuGet.CommandLine.Xplat.Tests.csproj
+++ b/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/NuGet.CommandLine.Xplat.Tests.csproj
@@ -13,6 +13,7 @@
+
diff --git a/test/TestUtilities/Test.Utility/TestDotnetCLiUtility.cs b/test/TestUtilities/Test.Utility/TestDotnetCLiUtility.cs
index e41aa95dfa1..9276ef549a8 100644
--- a/test/TestUtilities/Test.Utility/TestDotnetCLiUtility.cs
+++ b/test/TestUtilities/Test.Utility/TestDotnetCLiUtility.cs
@@ -196,6 +196,63 @@ private static void UpdateCliWithLatestNuGetAssemblies(string cliDirectory)
CopyPackSdkArtifacts(artifactsDirectory, pathToSdkInCli, configuration);
CopyRestoreArtifacts(artifactsDirectory, pathToSdkInCli, configuration);
CopyNuGetSdkResolverArtifacts(artifactsDirectory, pathToSdkInCli, configuration);
+ AddSpectreConsoleToDepsJson(pathToSdkInCli);
+ }
+
+ // Temporary. Can be removed once https://github.com/dotnet/dotnet/pull/3527 is merged and a new .NET SDK
+ private static void AddSpectreConsoleToDepsJson(string pathToSdkInCli)
+ {
+ string[] depsFiles = ["NuGet.CommandLine.XPlat.deps.json", "dotnet.deps.json"];
+
+ foreach (var depsFile in depsFiles)
+ {
+ var depsFilePath = Path.Combine(pathToSdkInCli, depsFile);
+ JObject jObject = JObject.Parse(File.ReadAllText(depsFilePath));
+
+ var targetNode = (JObject)((JObject)jObject["targets"]).Properties().First().Value;
+ JProperty spectreConsoleProperty = targetNode.Properties()
+ .FirstOrDefault(p => p.Name.StartsWith("Spectre.Console/"));
+
+ bool changed = false;
+
+ if (spectreConsoleProperty is null)
+ {
+ spectreConsoleProperty = new JProperty("Spectre.Console/0.54.0", JObject.Parse("""{"runtime":{"lib/net9.0/Spectre.Console.dll":{"assemblyVersion":"0.0.0.0","fileVersion":"0.54.0.0"}}}"""));
+ targetNode.Add(spectreConsoleProperty);
+ changed = true;
+ }
+
+ JProperty library = (JProperty)((JObject)jObject["libraries"]).Properties()
+ .FirstOrDefault(p => p.Name.StartsWith("Spectre.Console/"));
+ if (library is null)
+ {
+ var value = JObject.Parse(
+ """
+ {
+ "type": "package",
+ "serviceable": true,
+ "sha512": "sha512-StDXCFayfy0yB1xzUHT2tgEpV1/HFTiS4JgsAQS49EYTfMixSwwucaQs/bIOCwXjWwIQTMuxjUIxcB5XsJkFJA==",
+ "path": "spectre.console/0.54.0",
+ "hashPath": "spectre.console.0.54.0.nupkg.sha512"
+ }
+ """);
+ library = new JProperty("Spectre.Console/0.54.0", value);
+ ((JObject)jObject["libraries"]).Add(library);
+ changed = true;
+ }
+
+ if (changed)
+ {
+ using (StreamWriter streamWriter = File.CreateText(depsFilePath))
+ using (JsonTextWriter writer = new JsonTextWriter(streamWriter)
+ {
+ Formatting = Formatting.Indented
+ })
+ {
+ jObject.WriteTo(writer);
+ }
+ }
+ }
}
private static void CopyRestoreArtifacts(string artifactsDirectory, string pathToSdkInCli, string configuration)