File: TemplatePackageCoordinator.cs
Web Access
Project: ..\..\..\src\Cli\Microsoft.TemplateEngine.Cli\Microsoft.TemplateEngine.Cli.csproj (Microsoft.TemplateEngine.Cli)
// 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.NuGet;
using Microsoft.TemplateEngine.Cli.TabularOutput;
using Microsoft.TemplateEngine.Cli.TemplateSearch;
using Microsoft.TemplateEngine.Edge;
using Microsoft.TemplateEngine.Edge.Settings;
using Microsoft.TemplateEngine.Utils;
using NuGet.Configuration;
using NuGet.Credentials;
using NuGet.Versioning;
using static Microsoft.TemplateEngine.Cli.NuGet.NugetApiManager;
 
namespace Microsoft.TemplateEngine.Cli
{
    /// <summary>
    /// The class is responsible for template package manipulation flows: install template packages (-i, --install), check for update (--update-check), apply updates (--update-apply), uninstall template packages (-u, --uninstall).
    /// </summary>
    internal class TemplatePackageCoordinator
    {
        private const string SourceFeedKey = "NuGetSource";
        private const string NugetOrgFeed = "https://api.nuget.org/v3/index.json";
 
        private readonly IEngineEnvironmentSettings _engineEnvironmentSettings;
        private readonly TemplatePackageManager _templatePackageManager;
        private readonly TemplateConstraintManager _constraintsManager;
        private readonly HostSpecificDataLoader _hostSpecificDataLoader;
        private readonly TemplatePackageDisplay _templatePackageDisplay;
 
        internal TemplatePackageCoordinator(
            IEngineEnvironmentSettings environmentSettings,
            TemplatePackageManager templatePackageManager)
        {
            _engineEnvironmentSettings = environmentSettings ?? throw new ArgumentNullException(nameof(environmentSettings));
            _templatePackageManager = templatePackageManager ?? throw new ArgumentNullException(nameof(templatePackageManager));
            _constraintsManager = new TemplateConstraintManager(_engineEnvironmentSettings);
            _hostSpecificDataLoader = new HostSpecificDataLoader(_engineEnvironmentSettings);
            _templatePackageDisplay = new TemplatePackageDisplay(Reporter.Output, Reporter.Error);
        }
 
        /// <summary>
        /// Checks if there is an update for the package containing the template to execute.
        /// </summary>
        /// <param name="args">template command arguments.</param>
        /// <param name="cancellationToken"></param>
        /// <returns>Task for checking the update or null when check for update is not possible.</returns>
        internal async Task<CheckUpdateResult?> CheckUpdateForTemplate(TemplateCommandArgs args, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            //if update check is disabled - do nothing
            if (args.NoUpdateCheck)
            {
                return null;
            }
 
            ITemplatePackage templatePackage;
            try
            {
                templatePackage = await _templatePackageManager.GetTemplatePackageAsync(args.Template, cancellationToken).ConfigureAwait(false);
            }
            catch (Exception)
            {
                Reporter.Error.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Error_PackageForTemplateNotFound, args.Template.Identity);
                return null;
            }
 
            if (!(templatePackage is IManagedTemplatePackage managedTemplatePackage))
            {
                //update is not supported - built-in or optional workload source
                return null;
            }
            InitializeNuGetCredentialService(interactive: false);
            return (await managedTemplatePackage.ManagedProvider.GetLatestVersionsAsync(new[] { managedTemplatePackage }, cancellationToken).ConfigureAwait(false)).Single();
        }
 
        internal async Task<(string Id, string Version, string Provider)> ValidateBuiltInPackageAvailabilityAsync(
            ITemplateInfo template,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            ITemplatePackage templatePackage;
            try
            {
                templatePackage = await _templatePackageManager.GetTemplatePackageAsync(template, cancellationToken).ConfigureAwait(false);
            }
            catch (Exception)
            {
                Reporter.Error.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Error_PackageForTemplateNotFound, template.Identity);
                return default;
            }
 
            if (!(templatePackage is IManagedTemplatePackage managedTemplatePackage))
            {
                //update is not supported - built-in or optional workload source
                return default;
            }
 
            IReadOnlyList<ITemplatePackage> templatePackages = await _templatePackageManager.GetTemplatePackagesAsync(force: false, cancellationToken).ConfigureAwait(false);
 
            IEnumerable<(string Id, string Version, string Provider)> unmanagedTemplatePackages = templatePackages
                .Where(tp => tp is not IManagedTemplatePackage)
                .Select(tp => new
                {
                    Info = NuGetUtils.GetNuGetPackageInfo(_engineEnvironmentSettings, tp.MountPointUri),
                    Package = tp
                })
                .Where(i => i.Info != default)
                .Select(i => (i.Info.Id, i.Info.Version, i.Package.Provider.Factory.DisplayName));
 
            var matchingTemplatePackage = unmanagedTemplatePackages.FirstOrDefault(package => string.Equals(managedTemplatePackage.Identifier, package.Id, StringComparison.OrdinalIgnoreCase));
            if (matchingTemplatePackage == default)
            {
                return default;
            }
 
            NuGetVersion? managedPackageVersion;
            NuGetVersion? unmanagedPackageVersion;
 
            if (NuGetVersion.TryParse(managedTemplatePackage.Version, out managedPackageVersion) && NuGetVersion.TryParse(matchingTemplatePackage.Version, out unmanagedPackageVersion))
            {
                if (unmanagedPackageVersion >= managedPackageVersion)
                {
                    return matchingTemplatePackage;
                }
            }
            return default;
        }
 
        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
                    Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Update_Info_UpdateAvailable, displayString);
 
                    Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Update_Info_UpdateSingleCommandHeader);
                    Reporter.Output.WriteCommand(
                        Example
                            .For<NewCommand>(args.ParseResult)
                            .WithSubcommand<InstallCommand>()
                            .WithArgument(BaseInstallCommand.NameArgument, $"{versionCheckResult.TemplatePackage?.Identifier}::{versionCheckResult.LatestVersion}"));
                    Reporter.Output.WriteLine();
                }
            }
            else
            {
                HandleUpdateCheckErrors(versionCheckResult, ignoreLocalPackageNotFound: true);
                Reporter.Error.WriteLine();
            }
        }
 
        internal void DisplayBuiltInPackagesCheckResult(string packageId, string version, string provider, ICommandArgs args)
        {
            Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_BuiltInCheck_Info_BuiltInPackageAvailable, $"{packageId}::{version}", provider);
            Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_BuiltInCheck_Info_UninstallPackage);
            Reporter.Output.WriteCommand(
                Example
                 .For<NewCommand>(args.ParseResult)
                 .WithSubcommand<UninstallCommand>()
                 .WithArgument(BaseUninstallCommand.NameArgument, packageId));
        }
 
        /// <summary>
        /// Install the template package(s) flow (--install, -i).
        /// </summary>
        internal async Task<NewCommandStatus> EnterInstallFlowAsync(InstallCommandArgs args, CancellationToken cancellationToken)
        {
            _ = args ?? throw new ArgumentNullException(nameof(args));
            _ = args.TemplatePackages ?? throw new ArgumentNullException(nameof(args.TemplatePackages));
            if (!args.TemplatePackages.Any())
            {
                throw new ArgumentException($"{nameof(args.TemplatePackages)} should have at least one item to continue.", nameof(args.TemplatePackages));
            }
            cancellationToken.ThrowIfCancellationRequested();
            InitializeNuGetCredentialService(args.Interactive);
 
            NewCommandStatus resultStatus = NewCommandStatus.Success;
            TelemetryEventEntry.TrackEvent(TelemetryConstants.InstallEvent, new Dictionary<string, string?> { { TelemetryConstants.ToInstallCount, args.TemplatePackages.Count.ToString() } });
 
            var details = new Dictionary<string, string>();
            if (args.AdditionalSources?.Count > 0)
            {
                details[InstallerConstants.NuGetSourcesKey] = string.Join(InstallerConstants.NuGetSourcesSeparator.ToString(), args.AdditionalSources);
            }
            if (args.Interactive)
            {
                details[InstallerConstants.InteractiveModeKey] = "true";
            }
 
            // In future we might want give user ability to pick IManagerSourceProvider by Name or GUID
            var managedSourceProvider = _templatePackageManager.GetBuiltInManagedProvider(InstallationScope.Global);
            List<InstallRequest> installRequests = new();
 
            foreach (string installArg in args.TemplatePackages)
            {
                bool isPath = File.Exists(installArg) || Directory.Exists(installArg);
                string[] split = isPath ? new[] { installArg } : installArg.Split(["::"], StringSplitOptions.RemoveEmptyEntries).SelectMany(arg => arg.Split('@', StringSplitOptions.RemoveEmptyEntries)).ToArray();
                string identifier = split[0];
                string? version = split.Length > 1 ? split[1] : null;
 
                if (installArg.Contains("::"))
                {
                    Reporter.Output.WriteLine(string.Format(LocalizableStrings.Colon_Separator_Deprecated, split[0], split.Length > 1 ? split[1] : string.Empty).Yellow());
                }
 
                foreach (string expandedIdentifier in InstallRequestPathResolution.ExpandMaskedPath(identifier, _engineEnvironmentSettings))
                {
                    installRequests.Add(new InstallRequest(expandedIdentifier, version, details: details, force: args.Force));
                }
            }
 
            if (!installRequests.Any())
            {
                Reporter.Error.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Install_Error_FoundNoPackagesToInstall);
                return NewCommandStatus.NotFound;
            }
 
            //validate if installation requests have unique identifier
            HashSet<string> identifiers = new();
            foreach (InstallRequest installRequest in installRequests)
            {
                if (identifiers.Add(installRequest.PackageIdentifier))
                {
                    continue;
                }
                Reporter.Error.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Install_Error_SameInstallRequests, installRequest.PackageIdentifier);
                return NewCommandStatus.InstallFailed;
            }
 
            Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Install_Info_PackagesToBeInstalled);
            foreach (InstallRequest installRequest in installRequests)
            {
                Reporter.Output.WriteLine(installRequest.DisplayName.Indent());
            }
            Reporter.Output.WriteLine();
 
            bool validated = await ValidateInstallationRequestsAsync(args, installRequests, cancellationToken).ConfigureAwait(false);
            if (!validated)
            {
                return NewCommandStatus.InstallFailed;
            }
 
            IReadOnlyList<InstallResult> installResults = await managedSourceProvider.InstallAsync(installRequests, cancellationToken).ConfigureAwait(false);
            foreach (InstallResult result in installResults)
            {
                await _templatePackageDisplay.DisplayInstallResultAsync(
                    result.InstallRequest.DisplayName,
                    result,
                    args.ParseResult,
                    args.Force,
                    _templatePackageManager,
                    _engineEnvironmentSettings,
                    _constraintsManager,
                    cancellationToken).ConfigureAwait(false);
                if (!result.Success)
                {
                    resultStatus = result.Error == InstallerErrorCode.PackageNotFound ? NewCommandStatus.NotFound : NewCommandStatus.InstallFailed;
                }
            }
            return resultStatus;
        }
 
        /// <summary>
        /// Update the template package(s) flow (--update-check and --update-apply).
        /// </summary>
        internal async Task<NewCommandStatus> EnterUpdateFlowAsync(UpdateCommandArgs commandArgs, CancellationToken cancellationToken)
        {
            _ = commandArgs ?? throw new ArgumentNullException(nameof(commandArgs));
            cancellationToken.ThrowIfCancellationRequested();
            InitializeNuGetCredentialService(commandArgs.Interactive);
 
            bool applyUpdates = !commandArgs.CheckOnly;
            bool allTemplatesUpToDate = true;
            NewCommandStatus success = NewCommandStatus.Success;
            var managedTemplatePackages = await _templatePackageManager.GetManagedTemplatePackagesAsync(false, cancellationToken).ConfigureAwait(false);
 
            foreach (var packagesGrouping in managedTemplatePackages.GroupBy(package => package.ManagedProvider))
            {
                var provider = packagesGrouping.Key;
                IReadOnlyList<CheckUpdateResult> checkUpdateResults = await provider.GetLatestVersionsAsync(packagesGrouping, cancellationToken).ConfigureAwait(false);
                _templatePackageDisplay.DisplayUpdateCheckResults(_engineEnvironmentSettings, checkUpdateResults, commandArgs, showUpdates: !applyUpdates);
                if (checkUpdateResults.Any(result => !result.Success))
                {
                    success = NewCommandStatus.InstallFailed;
                }
                allTemplatesUpToDate = checkUpdateResults.All(result => result.Success && result.IsLatestVersion);
 
                if (applyUpdates)
                {
                    IEnumerable<CheckUpdateResult> updatesToApply = checkUpdateResults.Where(update => update.Success && !update.IsLatestVersion);
                    if (!updatesToApply.Any())
                    {
                        continue;
                    }
                    if (updatesToApply.Any(update => update.TemplatePackage is null || update.LatestVersion is null))
                    {
                        throw new InvalidOperationException($"Unexpected result received from {nameof(provider.GetLatestVersionsAsync)} method: returned result where {nameof(CheckUpdateResult.TemplatePackage)} is null, or {nameof(CheckUpdateResult.LatestVersion)} is null.");
                    }
 
                    Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Update_Info_PackagesToBeUpdated);
                    foreach (CheckUpdateResult update in updatesToApply)
                    {
                        Reporter.Output.WriteLine($"{update.TemplatePackage!.Identifier}::{update.LatestVersion}".Indent());
                    }
                    Reporter.Output.WriteLine();
 
                    IReadOnlyList<UpdateResult> updateResults = await provider.UpdateAsync(updatesToApply.Select(update => new UpdateRequest(update.TemplatePackage!, update.LatestVersion!)), cancellationToken).ConfigureAwait(false);
                    foreach (var updateResult in updateResults)
                    {
                        if (!updateResult.Success)
                        {
                            success = NewCommandStatus.InstallFailed;
                        }
 
                        await _templatePackageDisplay.DisplayInstallResultAsync(
                           updateResult.UpdateRequest.TemplatePackage.DisplayName,
                           updateResult,
                           commandArgs.ParseResult,
                           // force is not supported by update flow
                           force: false,
                           _templatePackageManager,
                           _engineEnvironmentSettings,
                           _constraintsManager,
                           cancellationToken).ConfigureAwait(false);
                    }
                }
            }
 
            if (allTemplatesUpToDate)
            {
                Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Update_Info_AllPackagesAreUpToDate);
            }
 
            return success;
        }
 
        /// <summary>
        /// Uninstall the template package(s) flow (--uninstall, -u).
        /// </summary>
        internal async Task<NewCommandStatus> EnterUninstallFlowAsync(UninstallCommandArgs args, CancellationToken cancellationToken)
        {
            _ = args ?? throw new ArgumentNullException(nameof(args));
            cancellationToken.ThrowIfCancellationRequested();
 
            NewCommandStatus result = NewCommandStatus.Success;
            if (args.TemplatePackages == null || args.TemplatePackages.Count <= 0)
            {
                //display all installed template packages
                await _templatePackageDisplay.DisplayInstalledTemplatePackagesAsync(_templatePackageManager, args, cancellationToken).ConfigureAwait(false);
                return result;
            }
 
            Dictionary<IManagedTemplatePackageProvider, List<IManagedTemplatePackage>> sourcesToUninstall;
            (result, sourcesToUninstall) = await DetermineSourcesToUninstallAsync(args, cancellationToken).ConfigureAwait(false);
 
            foreach (KeyValuePair<IManagedTemplatePackageProvider, List<IManagedTemplatePackage>> providerSourcesToUninstall in sourcesToUninstall)
            {
                IReadOnlyList<UninstallResult> uninstallResults = await providerSourcesToUninstall.Key.UninstallAsync(providerSourcesToUninstall.Value, cancellationToken).ConfigureAwait(false);
                foreach (UninstallResult uninstallResult in uninstallResults)
                {
                    if (uninstallResult.Success)
                    {
                        Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Uninstall_Info_Success, uninstallResult.TemplatePackage.DisplayName);
                    }
                    else
                    {
                        Reporter.Error.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Uninstall_Error_GenericError, uninstallResult.TemplatePackage.DisplayName, uninstallResult.ErrorMessage);
                        result = NewCommandStatus.InstallFailed;
                    }
                }
            }
            //rebuild cache after uninstall to remove deleted templates.
            await _templatePackageManager.RebuildTemplateCacheAsync(cancellationToken).ConfigureAwait(false);
            return result;
        }
 
        /// <summary>
        /// Searches and displays a package metadata.
        /// </summary>
        internal async Task<NewCommandStatus> DisplayTemplatePackageMetadata(
            string packageIdentity,
            string? packageVersion,
            bool interactiveAuth,
            IReadOnlyList<string>? additionalSources,
            NugetApiManager nugetApiManager,
            CancellationToken cancellationToken = default)
        {
            NugetPackageMetadata? nuGetPackageMetadata;
            IEnumerable<ITemplateInfo>? packageTemplates;
            IManagedTemplatePackage? localPackage;
 
            InitializeNuGetCredentialService(interactiveAuth);
 
            try
            {
                (localPackage, packageTemplates) = await _templatePackageManager
                    .GetManagedTemplatePackageAsync(packageIdentity, packageVersion, cancellationToken).ConfigureAwait(false);
            }
            catch
            {
                localPackage = null;
                packageTemplates = null;
            }
 
            // The package was found locally
            if (localPackage != null && packageTemplates != null)
            {
                string? packageSource = string.Empty;
                PackageSource? sourceFeed = null;
                if (localPackage.GetDetails().TryGetValue(SourceFeedKey, out packageSource))
                {
                    sourceFeed = new PackageSource(packageSource);
                }
 
                nuGetPackageMetadata = await nugetApiManager.GetPackageMetadataAsync(
                    packageIdentity,
                    packageVersion,
                    sourceFeed,
                    cancellationToken).ConfigureAwait(false);
 
                if (nuGetPackageMetadata == null)
                {
                    DisplayLocalPackageMetadata(localPackage, Reporter.Output);
 
                    var templatesToDisplay = TemplateGroupDisplay.GetTemplateGroupsForListDisplay(packageTemplates, null, null, _engineEnvironmentSettings.Environment);
                    DisplayPackageTemplateList(templatesToDisplay, Reporter.Output);
                    return NewCommandStatus.Success;
                }
            }
            else
            {
                IEnumerable<PackageSource> packageSources = LoadNuGetSources(additionalSources, includeNuGetFeed: PathUtility.CheckForNuGetInNuGetConfig());
 
                nuGetPackageMetadata = await GetPackageMetadataFromMultipleFeedsAsync(packageSources, nugetApiManager, packageIdentity, packageVersion, cancellationToken).ConfigureAwait(false);
                if (nuGetPackageMetadata != null && nuGetPackageMetadata.Source.Source.Equals(NugetOrgFeed))
                {
                    packageTemplates = await CliTemplateSearchCoordinator.SearchForPackageTemplatesAsync(
                        _engineEnvironmentSettings,
                        packageIdentity,
                        packageVersion,
                        cancellationToken).ConfigureAwait(false);
                }
 
            }
 
            if (nuGetPackageMetadata != null)
            {
                DisplayNuGetPackageMetadata(nuGetPackageMetadata, Reporter.Output);
                if (packageTemplates != null && packageTemplates.Any())
                {
                    var templatesToDisplay = TemplateGroupDisplay.GetTemplateGroupsForListDisplay(packageTemplates, null, null, _engineEnvironmentSettings.Environment);
                    DisplayPackageTemplateList(templatesToDisplay, Reporter.Output);
                }
                return NewCommandStatus.Success;
            }
 
            Reporter.Output.WriteLine(
                LocalizableStrings.Generic_Info_NoMatchingTemplatePackage.Bold().Red(),
                $"{packageIdentity}{(string.IsNullOrWhiteSpace(packageVersion) ? string.Empty : $"::{packageVersion}")}");
 
            return NewCommandStatus.NotFound;
        }
 
        internal void DisplayNuGetPackageMetadata(NugetPackageMetadata packageMetadata, IReporter reporter)
        {
            reporter.WriteLine($"{packageMetadata.Identity.Id}");
            WriteIfNotNull(LocalizableStrings.DetailsCommand_Property_Version, packageMetadata.PackageVersion.ToString(), reporter, 1);
            if (packageMetadata.PrefixReserved != null && packageMetadata.Source.Source.Equals(NugetOrgFeed))
            {
                WriteIfNotNull(LocalizableStrings.DetailsCommand_Property_PrefixReserved, packageMetadata.PrefixReserved.ToString(), reporter, 1);
            }
            WriteIfNotNull(LocalizableStrings.DetailsCommand_Property_Description, packageMetadata.Description, reporter, 1);
 
            string sourceFeed = packageMetadata.Source.Source == packageMetadata.Source.Name ? packageMetadata.Source.Source : $"{packageMetadata.Source.Name} [{packageMetadata.Source.Source}]";
            reporter.WriteLine($"{LocalizableStrings.DetailsCommand_Property_SourceFeed}: {sourceFeed}".Indent(1));
 
            if (!string.IsNullOrEmpty(packageMetadata.Authors))
            {
                reporter.WriteLine($"{LocalizableStrings.DetailsCommand_Property_Authors}:".Indent(1));
 
                var packageAuthors = packageMetadata.Authors.Split(",");
                foreach (var author in packageAuthors)
                {
                    reporter.WriteLine(author.Trim().Indent(2));
                }
            }
 
            if (!string.IsNullOrEmpty(packageMetadata.Owners))
            {
                reporter.WriteLine($"{LocalizableStrings.DetailsCommand_Property_Owners}:".Indent(1));
 
                var packageOwners = packageMetadata.Owners.Split(",");
                foreach (var owner in packageOwners)
                {
                    reporter.WriteLine(AnsiExtensions.Url($"https://nuget.org/profiles/{owner.Trim()}", owner).Indent(2));
                }
            }
 
            reporter.WriteLine($"{LocalizableStrings.DetailsCommand_Property_LicenseMetadata}:".Indent(1));
            WriteIfNotNull(LocalizableStrings.DetailsCommand_Property_License, packageMetadata.License, reporter, 2);
 
            if (!string.IsNullOrEmpty(packageMetadata.LicenseExpression))
            {
                var licenseExpressionUrl = "https://licenses.nuget.org/" + packageMetadata.LicenseExpression;
                reporter.WriteLine(
                    $"{LocalizableStrings.DetailsCommand_Property_LicenseExpression}: ".Indent(1) +
                    $"{AnsiExtensions.Url(licenseExpressionUrl, packageMetadata.LicenseExpression)}");
            }
 
            var licenseUrl = packageMetadata.LicenseUrl?.ToString();
            if (!string.IsNullOrEmpty(licenseUrl))
            {
                reporter.WriteLine(
                    $"{LocalizableStrings.DetailsCommand_Property_LicenseUrl}: ".Indent(2) +
                    $"{AnsiExtensions.Url(licenseUrl, licenseUrl)}");
            }
 
            var projectUrl = packageMetadata.ProjectUrl?.ToString();
            if (!string.IsNullOrEmpty(projectUrl))
            {
                reporter.WriteLine(
                    $"{LocalizableStrings.DetailsCommand_Property_RepoUrl}: {projectUrl}".Indent(2));
            }
        }
 
        internal void DisplayLocalPackageMetadata(IManagedTemplatePackage package, IReporter reporter)
        {
            reporter.WriteLine($"{package.Identifier}");
 
            var packageDetails = package.GetDetails();
 
            string? authors;
            packageDetails.TryGetValue("Author", out authors);
            if (!string.IsNullOrEmpty(authors))
            {
                reporter.WriteLine($"{LocalizableStrings.DetailsCommand_Property_Authors}:".Indent(1));
 
                var packageAuthors = authors.Split(",");
                foreach (var author in packageAuthors)
                {
                    reporter.WriteLine(author.Trim().Indent(2));
                }
            }
 
            string? nuGetSource;
            packageDetails.TryGetValue("NuGetSource", out nuGetSource);
 
            if (!string.IsNullOrEmpty(nuGetSource))
            {
                reporter.WriteLine(
                    $"{LocalizableStrings.DetailsCommand_Property_RepoUrl}: {nuGetSource}".Indent(1));
            }
        }
 
        internal void DisplayPackageTemplateList(IReadOnlyList<TemplateGroupTableRow> templatesToDisplay, IReporter reporter)
        {
            reporter.WriteLine($"{LocalizableStrings.DetailsCommand_Property_Templates}:".Indent(1));
 
            TabularOutput<TemplateGroupTableRow> formatter =
                TabularOutput.TabularOutput
                    .For(
                        new TabularOutputSettings(_engineEnvironmentSettings.Environment),
                        templatesToDisplay)
                    .DefineColumn(t => t.Name, LocalizableStrings.ColumnNameTemplateName, minWidth: 15, showAlways: true, shrinkIfNeeded: true)
                    .DefineColumn(t => t.ShortNames, LocalizableStrings.ColumnNameShortName, minWidth: 15, showAlways: true)
                    .DefineColumn(t => t.Type, LocalizableStrings.ColumnNameType, minWidth: 15, showAlways: true)
                    .DefineColumn(t => t.Classifications, LocalizableStrings.ColumnNameTags, minWidth: 15, showAlways: true, shrinkIfNeeded: true)
                    .DefineColumn(t => t.Languages, LocalizableStrings.ColumnNameLanguage, minWidth: 15, showAlways: true);
 
            reporter.WriteLine(formatter.Layout(2));
        }
 
        private static void InitializeNuGetCredentialService(bool interactive)
        {
            try
            {
                DefaultCredentialServiceUtility.SetupDefaultCredentialService(new CliNuGetLogger(), !interactive);
            }
            catch (Exception ex)
            {
                Reporter.Verbose.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Verbose_NuGetCredentialServiceError, ex.ToString());
            }
        }
 
        private void WriteIfNotNull(string metadataName, string? metadataEntry, IReporter reporter, int indent = 0)
        {
            if (!string.IsNullOrEmpty(metadataEntry))
            {
                reporter.WriteLine($"{metadataName}: {metadataEntry}".Indent(indent));
            }
        }
 
        private async Task<bool> ValidateInstallationRequestsAsync(InstallCommandArgs args, List<InstallRequest> installRequests, CancellationToken cancellationToken)
        {
            var templatePackages = await _templatePackageManager.GetTemplatePackagesAsync(force: false, cancellationToken).ConfigureAwait(false);
            IReadOnlyList<(string Id, string Version)> unmanagedTemplatePackages = templatePackages
                .Where(tp => tp is not IManagedTemplatePackage)
                .Select(tp => NuGetUtils.GetNuGetPackageInfo(_engineEnvironmentSettings, tp.MountPointUri))
                .Where(i => i != default)
                .ToList();
 
            HashSet<(InstallRequest Request, (string Id, string Version) PackageInfo)> invalidTemplatePackages = new();
 
            foreach (var installRequest in installRequests)
            {
                var foundPackage = unmanagedTemplatePackages.FirstOrDefault(package => string.Equals(package.Id, installRequest.PackageIdentifier, StringComparison.OrdinalIgnoreCase));
                if (foundPackage != default)
                {
                    invalidTemplatePackages.Add((installRequest, foundPackage));
                }
            }
 
            if (invalidTemplatePackages.Any())
            {
                IReporter reporter = args.Force ? Reporter.Output : Reporter.Error;
 
                reporter.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Install_Info_OverrideNotice);
                reporter.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Install_Info_PackageIsAvailable);
                foreach (var request in invalidTemplatePackages)
                {
                    reporter.WriteLine($"{request.PackageInfo.Id}::{request.PackageInfo.Version}".Indent());
                }
                reporter.WriteLine();
 
                if (!args.Force)
                {
                    reporter.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Install_Info_UseForceToOverride, SharedOptions.ForceOption.Name);
                    reporter.WriteCommand(
                        Example
                            .For<InstallCommand>(args.ParseResult)
                            .WithArgument(BaseInstallCommand.NameArgument, installRequests.Select(ir => ir.DisplayName).ToArray())
                            .WithOption(SharedOptions.ForceOption));
                    return false;
                }
            }
            return true;
        }
 
        private async Task<(NewCommandStatus, Dictionary<IManagedTemplatePackageProvider, List<IManagedTemplatePackage>>)> DetermineSourcesToUninstallAsync(UninstallCommandArgs commandArgs, CancellationToken cancellationToken)
        {
            _ = commandArgs ?? throw new ArgumentNullException(nameof(commandArgs));
            _ = commandArgs.TemplatePackages ?? throw new ArgumentNullException(nameof(commandArgs.TemplatePackages));
            cancellationToken.ThrowIfCancellationRequested();
 
            NewCommandStatus result = NewCommandStatus.Success;
            IReadOnlyList<IManagedTemplatePackage> templatePackages = await _templatePackageManager.GetManagedTemplatePackagesAsync(false, cancellationToken).ConfigureAwait(false);
 
            var packagesToUninstall = new Dictionary<IManagedTemplatePackageProvider, List<IManagedTemplatePackage>>();
            List<string> notFoundPackages = new();
            foreach (var requestedPackageIdentifier in commandArgs.TemplatePackages)
            {
                bool templatePackageIdentified = false;
                // First try to search for installed packages that have identical identifier as requested to be unistalled
                foreach (IManagedTemplatePackage templatePackage in templatePackages)
                {
                    if (templatePackage.Identifier.Equals(requestedPackageIdentifier, StringComparison.OrdinalIgnoreCase))
                    {
                        templatePackageIdentified = true;
                        if (packagesToUninstall.TryGetValue(templatePackage.ManagedProvider, out List<IManagedTemplatePackage>? packages))
                        {
                            packages.Add(templatePackage);
                        }
                        else
                        {
                            packagesToUninstall[templatePackage.ManagedProvider] = new List<IManagedTemplatePackage>() { templatePackage };
                        }
                    }
                }
 
                if (!templatePackageIdentified)
                {
                    // If not found - try to expand path and search with expanded path for all local packages (folders and nugets)
                    foreach (string expandedIdentifier in InstallRequestPathResolution.ExpandMaskedPath(requestedPackageIdentifier, _engineEnvironmentSettings))
                    {
                        templatePackageIdentified = false;
                        foreach (IManagedTemplatePackage templatePackage in templatePackages.Where(pm => pm.IsLocalPackage))
                        {
                            if (templatePackage.Identifier.Equals(expandedIdentifier, StringComparison.OrdinalIgnoreCase))
                            {
                                templatePackageIdentified = true;
                                if (packagesToUninstall.TryGetValue(templatePackage.ManagedProvider, out List<IManagedTemplatePackage>? packages))
                                {
                                    packages.Add(templatePackage);
                                }
                                else
                                {
                                    packagesToUninstall[templatePackage.ManagedProvider] = new List<IManagedTemplatePackage>() { templatePackage };
                                }
                            }
                        }
 
                        if (!templatePackageIdentified)
                        {
                            notFoundPackages.Add(expandedIdentifier);
                        }
                    }
                }
            }
 
            foreach (string notFoundPackage in notFoundPackages)
            {
                result = NewCommandStatus.NotFound;
                Reporter.Error.WriteLine(
                    string.Format(
                        LocalizableStrings.TemplatePackageCoordinator_Error_PackageNotFound,
                        notFoundPackage).Bold().Red());
                if (await IsTemplateShortNameAsync(notFoundPackage, cancellationToken).ConfigureAwait(false))
                {
                    var packages = await GetTemplatePackagesByShortNameAsync(notFoundPackage, cancellationToken).ConfigureAwait(false);
                    var managedPackages = packages.OfType<IManagedTemplatePackage>();
                    if (managedPackages.Any())
                    {
                        Reporter.Error.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Error_TemplateIncludedToPackages, notFoundPackage);
                        foreach (IManagedTemplatePackage managedPackage in managedPackages)
                        {
                            IEnumerable<ITemplateInfo> templates = await _templatePackageManager.GetTemplatesAsync(managedPackage, cancellationToken).ConfigureAwait(false);
                            var templateGroupsCount = templates.GroupBy(x => x.GroupIdentity, x => !string.IsNullOrEmpty(x.GroupIdentity), StringComparer.OrdinalIgnoreCase).Count();
                            Reporter.Error.WriteLine(
                                  string.Format(
                                      LocalizableStrings.TemplatePackageCoordinator_Error_PackageNameContainsTemplates,
                                      managedPackage.DisplayName,
                                      templateGroupsCount).Indent());
                        }
                        Reporter.Error.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Uninstall_Error_UninstallCommandHeader);
 
                        if (string.IsNullOrWhiteSpace(managedPackages?.First().Identifier))
                        {
                            Reporter.Error.WriteCommand(
                                 Example
                                    .For<NewCommand>(commandArgs.ParseResult)
                                    .WithSubcommand<UninstallCommand>()
                                    .WithArgument(BaseUninstallCommand.NameArgument));
                        }
                        else
                        {
                            Reporter.Error.WriteCommand(
                                 Example
                                    .For<NewCommand>(commandArgs.ParseResult)
                                    .WithSubcommand<UninstallCommand>()
                                    .WithArgument(BaseUninstallCommand.NameArgument, managedPackages.First().Identifier));
                        }
                    }
                    else
                    {
                        Reporter.Error.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Uninstall_Error_ListPackagesHeader);
                        Reporter.Error.WriteCommand(
                             Example
                                .For<NewCommand>(commandArgs.ParseResult)
                                .WithSubcommand<UninstallCommand>());
                    }
                }
                else
                {
                    Reporter.Error.WriteLine(LocalizableStrings.TemplatePackageCoordinator_Uninstall_Error_ListPackagesHeader);
                    Reporter.Error.WriteCommand(
                        Example
                           .For<NewCommand>(commandArgs.ParseResult)
                           .WithSubcommand<UninstallCommand>());
                }
                Reporter.Error.WriteLine();
            }
 
            return (result, packagesToUninstall);
        }
 
        private async Task<IEnumerable<ITemplatePackage>> GetTemplatePackagesByShortNameAsync(string sourceIdentifier, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(sourceIdentifier))
            {
                throw new ArgumentException(nameof(sourceIdentifier));
            }
            cancellationToken.ThrowIfCancellationRequested();
 
            IReadOnlyList<ITemplateInfo> templates = await _templatePackageManager.GetTemplatesAsync(cancellationToken).ConfigureAwait(false);
            var templatesWithMatchedShortName = templates.Where(template =>
            {
                return template.ShortNameList.Contains(sourceIdentifier, StringComparer.OrdinalIgnoreCase);
            });
 
            var templatePackages = await Task.WhenAll(
                templatesWithMatchedShortName.Select(
                    t => _templatePackageManager.GetTemplatePackageAsync(t, cancellationToken)))
                .ConfigureAwait(false);
 
            return templatePackages.Distinct();
        }
 
        private async Task<bool> IsTemplateShortNameAsync(string sourceIdentifier, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(sourceIdentifier))
            {
                throw new ArgumentException(nameof(sourceIdentifier));
            }
            cancellationToken.ThrowIfCancellationRequested();
 
            IReadOnlyList<ITemplateInfo> templates = await _templatePackageManager.GetTemplatesAsync(cancellationToken).ConfigureAwait(false);
            return templates.Any(template =>
            {
                return template.ShortNameList.Contains(sourceIdentifier, StringComparer.OrdinalIgnoreCase);
            });
        }
 
        private void DisplayUpdateCheckResults(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
                    HandleUpdateCheckErrors(result, ignoreLocalPackageNotFound: false);
                }
                Reporter.Error.WriteLine();
            }
        }
 
        private async Task DisplayInstalledTemplatePackagesAsync(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();
            }
        }
 
        private string GetFormattedValue(string rawValue)
        {
            if (bool.TryParse(rawValue, out bool value))
            {
                return value ? "✔" : "✘";
            }
 
            return rawValue;
        }
 
        private async Task DisplayInstallResultAsync(string packageToInstall, InstallerOperationResult result, ParseResult parseResult, 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())
                {
                    Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_lnstall_Info_Success, result.TemplatePackage.DisplayName);
                    TemplateGroupDisplay.DisplayTemplateList(
                        _engineEnvironmentSettings,
                        templates,
                        new TabularOutputSettings(_engineEnvironmentSettings.Environment),
                        reporter: Reporter.Output);
                    await EvaluateAndDisplayConstraintsAsync(templates, cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    Reporter.Output.WriteLine(LocalizableStrings.TemplatePackageCoordinator_lnstall_Warning_No_Templates_In_Package, result.TemplatePackage.DisplayName);
                }
            }
            else
            {
                switch (result.Error)
                {
                    case InstallerErrorCode.InvalidSource:
                        Reporter.Error.WriteLine(
                            string.Format(
                                LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_InvalidNuGetFeeds,
                                packageToInstall,
                                result.ErrorMessage).Bold().Red());
                        break;
                    case InstallerErrorCode.PackageNotFound:
                        Reporter.Error.WriteLine(
                            string.Format(
                                LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_PackageNotFound,
                                packageToInstall).Bold().Red());
                        break;
                    case InstallerErrorCode.DownloadFailed:
                        Reporter.Error.WriteLine(
                            string.Format(
                                LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_DownloadFailed,
                                packageToInstall).Bold().Red());
                        break;
                    case InstallerErrorCode.UnsupportedRequest:
                        Reporter.Error.WriteLine(
                            string.Format(
                                LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_UnsupportedRequest,
                                packageToInstall).Bold().Red());
                        break;
                    case InstallerErrorCode.AlreadyInstalled:
                        Reporter.Error.WriteLine(
                              string.Format(
                                  LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_AlreadyInstalled,
                                  packageToInstall).Bold().Red());
                        Reporter.Error.WriteLine(LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_AlreadyInstalled_Hint, BaseInstallCommand.ForceOption.Aliases.First());
                        Reporter.Error.WriteCommand(Example.For<InstallCommand>(parseResult).WithArgument(BaseInstallCommand.NameArgument, packageToInstall).WithOption(BaseInstallCommand.ForceOption));
 
                        break;
                    case InstallerErrorCode.UpdateUninstallFailed:
                        Reporter.Error.WriteLine(
                              string.Format(
                                  LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_UninstallFailed,
                                  packageToInstall).Bold().Red());
                        break;
                    case InstallerErrorCode.InvalidPackage:
                        Reporter.Error.WriteLine(
                              string.Format(
                                  LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_InvalidPackage,
                                  packageToInstall).Bold().Red());
                        break;
                    case InstallerErrorCode.GenericError:
                    default:
                        Reporter.Error.WriteLine(
                            string.Format(
                                LocalizableStrings.TemplatePackageCoordinator_lnstall_Error_GenericError,
                                packageToInstall).Bold().Red());
                        break;
                }
            }
        }
 
        private async Task EvaluateAndDisplayConstraintsAsync(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;
            }
 
            Reporter.Output.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;
                Reporter.Output.WriteLine(template.Template.GetDisplayName(showIdentity: showIdentity));
                foreach (var constraintResult in template.Result.Where(r => r.EvaluationStatus != TemplateConstraintResult.Status.Allowed))
                {
                    Reporter.Output.WriteLine(constraintResult.ToDisplayString().Indent(1));
                }
            }
        }
 
        private void HandleUpdateCheckErrors(CheckUpdateResult result, bool ignoreLocalPackageNotFound)
        {
            switch (result.Error)
            {
                case InstallerErrorCode.InvalidSource:
                    Reporter.Error.WriteLine(
                        string.Format(
                            LocalizableStrings.TemplatePackageCoordinator_Update_Error_InvalidNuGetFeeds,
                            result.TemplatePackage.DisplayName).Bold().Red());
                    break;
                case InstallerErrorCode.PackageNotFound:
                    if (!ignoreLocalPackageNotFound || !result.TemplatePackage.IsLocalPackage)
                    {
                        Reporter.Error.WriteLine(
                            string.Format(
                                LocalizableStrings.TemplatePackageCoordinator_Update_Error_PackageNotFound,
                                result.TemplatePackage.DisplayName).Bold().Red());
                    }
                    break;
                case InstallerErrorCode.UnsupportedRequest:
                    Reporter.Error.WriteLine(
                        string.Format(
                            LocalizableStrings.TemplatePackageCoordinator_Update_Error_PackageNotSupported,
                            result.TemplatePackage.DisplayName).Bold().Red());
                    break;
                case InstallerErrorCode.GenericError:
                default:
                    Reporter.Error.WriteLine(
                        string.Format(
                            LocalizableStrings.TemplatePackageCoordinator_Update_Error_GenericError,
                            result.TemplatePackage.DisplayName,
                            result.ErrorMessage).Bold().Red());
                    break;
            }
        }
 
        private IEnumerable<PackageSource> LoadNuGetSources(IEnumerable<string>? additionalSources, bool includeNuGetFeed)
        {
            IEnumerable<PackageSource> defaultSources;
            string currentDirectory = string.Empty;
            try
            {
                currentDirectory = Directory.GetCurrentDirectory();
                ISettings settings = Settings.LoadDefaultSettings(currentDirectory);
                PackageSourceProvider packageSourceProvider = new(settings);
                defaultSources = packageSourceProvider.LoadPackageSources().Where(source => source.IsEnabled);
                if (includeNuGetFeed)
                {
                    var nuGetFeed = new PackageSource(NugetOrgFeed, "NuGet.org");
                    defaultSources = defaultSources.Append(nuGetFeed);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format(LocalizableStrings.DetailsCommand_UnableToLoadResources, currentDirectory), ex);
            }
 
            if (additionalSources == null || !additionalSources.Any())
            {
                if (!defaultSources.Any())
                {
                    throw new Exception(LocalizableStrings.DetailsCommand_NoNuGetSources);
                }
                return defaultSources;
            }
 
            List<PackageSource> customSources = new();
            foreach (string source in additionalSources)
            {
                if (string.IsNullOrWhiteSpace(source))
                {
                    continue;
                }
                if (defaultSources.Any(s => s.Source.Equals(source, StringComparison.OrdinalIgnoreCase)))
                {
                    Reporter.Verbose.WriteLine($"Custom source {source} is already loaded from default configuration.");
                    continue;
                }
                PackageSource packageSource = new(source);
                if (packageSource.TrySourceAsUri == null)
                {
                    Reporter.Output.WriteLine(string.Format(LocalizableStrings.DetailsCommand_UnableToLoadResource, source));
                    continue;
                }
                customSources.Add(packageSource);
            }
 
            IEnumerable<PackageSource> retrievedSources = customSources.Concat(defaultSources);
            if (!retrievedSources.Any())
            {
                throw new Exception(LocalizableStrings.DetailsCommand_NoNuGetSources);
            }
            return retrievedSources;
        }
 
        private async Task<NugetPackageMetadata?> GetPackageMetadataFromMultipleFeedsAsync(
            IEnumerable<PackageSource> sources,
            NugetApiManager apiManager,
            string packageIdentifier,
            string? packageVersion = null,
            CancellationToken cancellationToken = default)
        {
            IEnumerable<NugetPackageMetadata?> foundPackages =
            await Task.WhenAll(
                sources.Select(source => apiManager.GetPackageMetadataAsync(packageIdentifier, packageVersion, source, cancellationToken)))
                        .ConfigureAwait(false);
 
            var accumulativeSearchResults = foundPackages
                .Where(result => result is not null);
 
            if (accumulativeSearchResults == null || !accumulativeSearchResults.Any())
            {
                return null;
            }
 
            var floatRange = new FloatRange(NuGetVersionFloatBehavior.AbsoluteLatest);
 
            NugetPackageMetadata? latestVersion = accumulativeSearchResults.Aggregate(
                (NugetPackageMetadata?)null,
                (max, current) =>
                    (max == null || current!.Identity.Version > max.Identity.Version)
                    &&
                    floatRange.Satisfies(current!.Identity.Version) ?
                        current : max);
 
            return latestVersion;
        }
    }
}