|
// 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 Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.Utils.Extensions;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.Constraints;
using Microsoft.TemplateEngine.Abstractions.Installer;
using Microsoft.TemplateEngine.Abstractions.TemplatePackage;
using Microsoft.TemplateEngine.Cli.Commands;
using Microsoft.TemplateEngine.Cli.TabularOutput;
using Microsoft.TemplateEngine.Edge;
using Microsoft.TemplateEngine.Edge.Settings;
namespace Microsoft.TemplateEngine.Cli
{
/// <summary>
/// The class is responsible for displaying template package manipulation results to console.
/// </summary>
internal class TemplatePackageDisplay
{
private readonly IReadOnlyDictionary<int, string> _vulnerabilitySeverityToColorMap = new Dictionary<int, string>
{
{ 0, LocalizableStrings.TemplatePackageCoordinator_VulnerabilitySeverity_Low.Cyan() },
{ 1, LocalizableStrings.TemplatePackageCoordinator_VulnerabilitySeverity_Moderate.Blue() },
{ 2, LocalizableStrings.TemplatePackageCoordinator_VulnerabilitySeverity_High.Yellow() },
{ 3, LocalizableStrings.TemplatePackageCoordinator_VulnerabilitySeverity_Critical.Red() },
};
private readonly IReporter _reporterOutput;
private readonly IReporter _reporterError;
internal TemplatePackageDisplay(
IReporter reporterOutput,
IReporter reporterError)
{
_reporterOutput = reporterOutput;
_reporterError = reporterError;
}
internal void DisplayUpdateCheckResult(CheckUpdateResult versionCheckResult, ICommandArgs args)
{
_ = versionCheckResult ?? throw new ArgumentNullException(nameof(versionCheckResult));
if (versionCheckResult.Success)
{
if (!versionCheckResult.IsLatestVersion)
{
string displayString = $"{versionCheckResult.TemplatePackage?.Identifier}::{versionCheckResult.TemplatePackage?.Version}"; // the package::version currently installed
_reporterOutput.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Update_Info_UpdateAvailable, displayString);
_reporterOutput.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Update_Info_UpdateSingleCommandHeader);
_reporterOutput.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<InstallCommand>()
.WithArgument(BaseInstallCommand.NameArgument, $"{versionCheckResult.TemplatePackage?.Identifier}::{versionCheckResult.LatestVersion}"));
_reporterOutput.WriteLine();
}
}
else
{
DisplayUpdateCheckErrors(versionCheckResult, ignoreLocalPackageNotFound: true);
_reporterError.WriteLine();
}
}
internal void DisplayBuiltInPackagesCheckResult(string packageId, string version, string provider, ICommandArgs args)
{
_reporterOutput.WriteLine(LocalizableStrings.TemplatePackageCoordinator_BuiltInCheck_Info_BuiltInPackageAvailable, $"{packageId}::{version}", provider);
_reporterOutput.WriteLine(LocalizableStrings.TemplatePackageCoordinator_BuiltInCheck_Info_UninstallPackage);
_reporterOutput.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<UninstallCommand>()
.WithArgument(BaseUninstallCommand.NameArgument, packageId));
}
internal async Task DisplayInstallResultAsync(
string packageToInstall,
InstallerOperationResult result,
ParseResult parseResult,
bool force,
TemplatePackageManager templatePackageManager,
IEngineEnvironmentSettings engineEnvironmentSettings,
TemplateConstraintManager constraintsManager,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(packageToInstall))
{
throw new ArgumentException(nameof(packageToInstall));
}
_ = result ?? throw new ArgumentNullException(nameof(result));
cancellationToken.ThrowIfCancellationRequested();
if (result.Success)
{
if (result.TemplatePackage is null)
{
throw new ArgumentException($"{nameof(result.TemplatePackage)} cannot be null when {nameof(result.Success)} is 'true'", nameof(result));
}
IEnumerable<ITemplateInfo> templates = await templatePackageManager.GetTemplatesAsync(result.TemplatePackage, cancellationToken).ConfigureAwait(false);
if (templates.Any())
{
_reporterOutput.WriteLine(LocalizableStrings.TemplatePackageCoordinator_lnstall_Info_Success, result.TemplatePackage.DisplayName);
TemplateGroupDisplay.DisplayTemplateList(
engineEnvironmentSettings,
templates,
new TabularOutputSettings(engineEnvironmentSettings.Environment),
reporter: _reporterOutput);
await EvaluateAndDisplayConstraintsAsync(constraintsManager, templates, cancellationToken).ConfigureAwait(false);
}
else
{
_reporterOutput.WriteLine(LocalizableStrings.TemplatePackageCoordinator_lnstall_Warning_No_Templates_In_Package, result.TemplatePackage.DisplayName);
}
if (force && result is InstallResult installResult && installResult.Vulnerabilities.Any())
{
_reporterOutput.WriteLine(string.Format(LocalizableStrings.TemplatePackageCoordinator_Download_VulnerablePackage));
DisplayVulnerabilityInfo(installResult.Vulnerabilities, _reporterOutput);
}
}
else
{
switch (result.Error)
{
case InstallerErrorCode.InvalidSource:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_InvalidNuGetFeeds,
packageToInstall,
result.ErrorMessage).Bold().Red());
break;
case InstallerErrorCode.PackageNotFound:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_PackageNotFound,
packageToInstall).Bold().Red());
break;
case InstallerErrorCode.DownloadFailed:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_DownloadFailed,
packageToInstall).Bold().Red());
break;
case InstallerErrorCode.UnsupportedRequest:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_UnsupportedRequest,
packageToInstall).Bold().Red());
break;
case InstallerErrorCode.AlreadyInstalled:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_AlreadyInstalled,
packageToInstall).Bold().Red());
_reporterError.WriteLine(LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_AlreadyInstalled_Hint, BaseInstallCommand.ForceOption.Name);
_reporterError.WriteCommand(Example.For<InstallCommand>(parseResult).WithArgument(BaseInstallCommand.NameArgument, packageToInstall).WithOption(BaseInstallCommand.ForceOption));
break;
case InstallerErrorCode.UpdateUninstallFailed:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_UninstallFailed,
packageToInstall).Bold().Red());
break;
case InstallerErrorCode.InvalidPackage:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_InvalidPackage,
packageToInstall).Bold().Red());
break;
case InstallerErrorCode.VulnerablePackage:
{
switch (result)
{
case InstallResult installResult when installResult.Vulnerabilities.Any():
_reporterError.WriteLine(string.Format(
LocalizableStrings.TemplatePackageCoordinator_Download_Error_VulnerablePackage,
packageToInstall).Bold().Red());
DisplayVulnerabilityInfo(installResult.Vulnerabilities, _reporterError);
_reporterError.WriteLine(string.Format(
LocalizableStrings.TemplatePackageCoordinator_Install_Error_VulnerablePackageTip,
packageToInstall,
SharedOptions.ForceOption.Name).Bold());
_reporterError.WriteCommand(Example.For<InstallCommand>(parseResult).WithArgument(BaseInstallCommand.NameArgument, packageToInstall).WithOption(BaseInstallCommand.ForceOption));
break;
case UpdateResult updateRequest when updateRequest.Vulnerabilities.Any():
_reporterError.WriteLine(string.Format(
LocalizableStrings.TemplatePackageCoordinator_Update_Error_VulnerablePackage,
packageToInstall).Bold().Red());
DisplayVulnerabilityInfo(updateRequest.Vulnerabilities, _reporterError);
_reporterError.WriteLine(string.Format(
LocalizableStrings.TemplatePackageCoordinator_Update_Error_VulnerablePackageTip,
packageToInstall,
SharedOptions.ForceOption.Name).Bold());
_reporterError.WriteCommand(Example.For<UninstallCommand>(parseResult).WithArgument(BaseUninstallCommand.NameArgument, packageToInstall));
_reporterError.WriteCommand(Example.For<InstallCommand>(parseResult).WithArgument(BaseInstallCommand.NameArgument, packageToInstall).WithOption(BaseInstallCommand.ForceOption));
break;
default:
throw new InvalidOperationException($"Unexpected result: {result.GetType()}");
}
}
break;
case InstallerErrorCode.GenericError:
default:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_GenericError,
packageToInstall).Bold().Red());
break;
}
}
}
internal async Task DisplayInstalledTemplatePackagesAsync(TemplatePackageManager templatePackageManager, GlobalArgs args, CancellationToken cancellationToken)
{
_ = args ?? throw new ArgumentNullException(nameof(args));
cancellationToken.ThrowIfCancellationRequested();
IEnumerable<IManagedTemplatePackage> managedTemplatePackages = await templatePackageManager.GetManagedTemplatePackagesAsync(false, cancellationToken).ConfigureAwait(false);
Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Uninstall_Info_InstalledItems);
if (!managedTemplatePackages.Any())
{
Reporter.Output.WriteLine(LocalizableStrings.NoItems);
return;
}
foreach (IManagedTemplatePackage managedSource in managedTemplatePackages)
{
Reporter.Output.WriteLine($"{managedSource.Identifier}".Indent());
if (!string.IsNullOrWhiteSpace(managedSource.Version))
{
Reporter.Output.WriteLine($"{LocalizableStrings.Version} {managedSource.Version}".Indent(level: 2));
}
IReadOnlyDictionary<string, string> displayDetails = managedSource.GetDetails();
if (displayDetails?.Any() ?? false)
{
Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Uninstall_Info_DetailsHeader.Indent(level: 2));
foreach (KeyValuePair<string, string> detail in displayDetails)
{
Reporter.Output.WriteLine($"{detail.Key}: {GetFormattedValue(detail.Value)}".Indent(level: 3));
}
}
IEnumerable<ITemplateInfo> templates = await templatePackageManager.GetTemplatesAsync(managedSource, cancellationToken).ConfigureAwait(false);
if (templates.Any())
{
Reporter.Output.WriteLine($"{LocalizableStrings.Templates}:".Indent(level: 2));
foreach (ITemplateInfo info in templates)
{
Reporter.Output.WriteLine(info.GetDisplayName().Indent(level: 3));
}
}
// uninstall command:
Reporter.Output.WriteLine($"{LocalizableStrings.TemplatePackageCoordinator_Uninstall_Info_UninstallCommandHint}".Indent(level: 2));
Reporter.Output.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<UninstallCommand>()
.WithArgument(BaseUninstallCommand.NameArgument, managedSource.Identifier),
indentLevel: 2);
Reporter.Output.WriteLine();
}
}
internal void DisplayUpdateCheckResults(IEngineEnvironmentSettings engineEnvironmentSettings, IEnumerable<CheckUpdateResult> versionCheckResults, GlobalArgs args, bool showUpdates = true)
{
_ = versionCheckResults ?? throw new ArgumentNullException(nameof(versionCheckResults));
//handle success
if (versionCheckResults.Any(result => result.Success && !result.IsLatestVersion) && showUpdates)
{
Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Update_Info_UpdateAvailablePackages);
IEnumerable<(string Identifier, string? CurrentVersion, string? LatestVersion)> displayableResults = versionCheckResults
.Where(result => result.Success && !result.IsLatestVersion && !string.IsNullOrWhiteSpace(result.LatestVersion))
.Select(result => (result.TemplatePackage.Identifier, result.TemplatePackage.Version, result.LatestVersion));
var formatter =
TabularOutput.TabularOutput
.For(
new TabularOutputSettings(engineEnvironmentSettings.Environment),
displayableResults)
.DefineColumn(r => r.Identifier, out object? packageColumn, LocalizableStrings.ColumnNamePackage, showAlways: true)
.DefineColumn(r => r.CurrentVersion ?? string.Empty, LocalizableStrings.ColumnNameCurrentVersion, showAlways: true)
.DefineColumn(r => r.LatestVersion ?? string.Empty, LocalizableStrings.ColumnNameLatestVersion, showAlways: true)
.OrderBy(packageColumn, StringComparer.CurrentCultureIgnoreCase);
Reporter.Output.WriteLine(formatter.Layout());
Reporter.Output.WriteLine();
Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Update_Info_UpdateSingleCommandHeader);
Reporter.Output.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<InstallCommand>()
.WithArgument(BaseInstallCommand.NameArgument, $"<package>::<version>"));
Reporter.Output.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<InstallCommand>()
.WithArgument(BaseInstallCommand.NameArgument, $"{displayableResults.First().Identifier}::{displayableResults.First().LatestVersion}"));
Reporter.Output.WriteLine();
Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Update_Info_UpdateAllCommandHeader);
Reporter.Output.WriteCommand(
Example
.For<NewCommand>(args.ParseResult)
.WithSubcommand<UpdateCommand>());
Reporter.Output.WriteLine();
}
//handle errors
if (versionCheckResults.Any(result => !result.Success))
{
foreach (CheckUpdateResult result in versionCheckResults.Where(result => !result.Success))
{
// explicit check of updates requested - so we do not want to ignore errors for
// local only packages
DisplayUpdateCheckErrors(result, ignoreLocalPackageNotFound: false);
}
Reporter.Error.WriteLine();
}
}
private void DisplayUpdateCheckErrors(CheckUpdateResult result, bool ignoreLocalPackageNotFound)
{
switch (result.Error)
{
case InstallerErrorCode.InvalidSource:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_Update_Error_InvalidNuGetFeeds,
result.TemplatePackage.DisplayName).Bold().Red());
break;
case InstallerErrorCode.PackageNotFound:
if (!ignoreLocalPackageNotFound || !result.TemplatePackage.IsLocalPackage)
{
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_Update_Error_PackageNotFound,
result.TemplatePackage.DisplayName).Bold().Red());
}
break;
case InstallerErrorCode.UnsupportedRequest:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_Update_Error_PackageNotSupported,
result.TemplatePackage.DisplayName).Bold().Red());
break;
case InstallerErrorCode.VulnerablePackage when result.Vulnerabilities.Any():
{
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_UpdateCheck_Error_VulnerablePackage,
result.TemplatePackage.Identifier).Bold().Red());
DisplayVulnerabilityInfo(result.Vulnerabilities, _reporterError);
}
break;
case InstallerErrorCode.GenericError:
default:
_reporterError.WriteLine(
string.Format(
LocalizableStrings.TemplatePackageCoordinator_Update_Error_GenericError,
result.TemplatePackage.DisplayName,
result.ErrorMessage).Bold().Red());
break;
}
}
private void DisplayVulnerabilityInfo(IReadOnlyList<VulnerabilityInfo> vulnerabilities, IReporter reporter)
{
reporter.Write(Environment.NewLine);
foreach (VulnerabilityInfo entry in vulnerabilities)
{
reporter.WriteLine($"{string.Empty.PadLeft(4)}{_vulnerabilitySeverityToColorMap[entry.Severity].Bold()}:");
reporter.WriteLine(
string.Join(
Environment.NewLine,
entry.AdvisoryUris.Select(advisory => $"{string.Empty.PadLeft(8)}{(advisory.Url(advisory))}")));
reporter.Write(Environment.NewLine);
}
}
private async Task EvaluateAndDisplayConstraintsAsync(TemplateConstraintManager constraintsManager, IEnumerable<ITemplateInfo> templates, CancellationToken cancellationToken)
{
var evaluationResult = await constraintsManager.EvaluateConstraintsAsync(templates, cancellationToken).ConfigureAwait(false);
var restrictedTemplates = evaluationResult.Where(r => r.Result.Any(cr => cr.EvaluationStatus != TemplateConstraintResult.Status.Allowed));
if (!restrictedTemplates.Any())
{
return;
}
_reporterOutput.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Install_ConstraintsNotice);
foreach (var template in restrictedTemplates)
{
bool showIdentity = !string.IsNullOrWhiteSpace(template.Template.GroupIdentity) && templates.Count(t => t.GroupIdentity == template.Template.GroupIdentity) > 1;
_reporterOutput.WriteLine(template.Template.GetDisplayName(showIdentity: showIdentity));
foreach (var constraintResult in template.Result.Where(r => r.EvaluationStatus != TemplateConstraintResult.Status.Allowed))
{
_reporterOutput.WriteLine(constraintResult.ToDisplayString().Indent(1));
}
}
}
private string GetFormattedValue(string rawValue)
{
if (bool.TryParse(rawValue, out bool value))
{
return value ? "✔" : "✘";
}
return rawValue;
}
}
}
|