File: CliTemplateEngineHost.cs
Web Access
Project: 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 Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Edge;

namespace Microsoft.TemplateEngine.Cli
{
    public class CliTemplateEngineHost : DefaultTemplateEngineHost, ICliTemplateEngineHost
    {
        public CliTemplateEngineHost(
            string hostIdentifier,
            string version,
            Dictionary<string, string> preferences,
            IReadOnlyList<(Type InterfaceType, IIdentifiedComponent Instance)> builtIns,
            IReadOnlyList<string>? fallbackHostNames = null,
            string? outputPath = null,
            LogLevel logLevel = LogLevel.Information)
            : base(
                  hostIdentifier,
                  version,
                  preferences,
                  builtIns,
                  fallbackHostNames,
                  loggerFactory: CreateLoggerFactory(logLevel))
        {
            string workingPath = FileSystem.GetCurrentDirectory();
            IsCustomOutputPath = outputPath != null;
            OutputPath = outputPath != null ? Path.Combine(workingPath, outputPath) : workingPath;
        }

        public string OutputPath { get; }

        public bool IsCustomOutputPath { get; }

        private static ILoggerFactory CreateLoggerFactory(LogLevel logLevel)
        {
            // Construct logging directly to avoid building a dependency injection container for every host.
            ConsoleLoggerProvider provider = new(
                new StaticOptionsMonitor<ConsoleLoggerOptions>(new()
                {
                    FormatterName = nameof(CliConsoleFormatter)
                }),
                [
                    new CliConsoleFormatter(new StaticOptionsMonitor<ConsoleFormatterOptions>(new()
                    {
                        IncludeScopes = true,
                        TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"
                    }))
                ]);

            LoggerFactory loggerFactory = new([], new LoggerFilterOptions { MinLevel = logLevel });
            loggerFactory.AddProvider(provider);
            return loggerFactory;
        }

        private sealed class StaticOptionsMonitor<TOptions>(TOptions options)
            : IOptionsMonitor<TOptions> where TOptions : new()
        {
            public TOptions CurrentValue => options;

            public TOptions Get(string? name) => options;

            public IDisposable? OnChange(Action<TOptions, string?> listener) => null;
        }

        private bool GlobalJsonFileExistsInPath
        {
            get
            {
                const string fileName = "global.json";
                string? workingPath = OutputPath;
                bool found;
                do
                {
                    string checkPath = Path.Join(workingPath, fileName);
                    found = FileSystem.FileExists(checkPath);
                    if (!found)
                    {
                        workingPath = Path.GetDirectoryName(workingPath.TrimEnd('/', '\\'));

                        if (string.IsNullOrWhiteSpace(workingPath) || !FileSystem.DirectoryExists(workingPath))
                        {
                            workingPath = null;
                        }
                    }
                }
                while (!found && (workingPath is not null));

                return found;
            }
        }

        public override bool TryGetHostParamDefault(string paramName, out string? value)
        {
            switch (paramName)
            {
                case "GlobalJsonExists":
                    value = GlobalJsonFileExistsInPath.ToString();
                    return true;
                default:
                    return base.TryGetHostParamDefault(paramName, out value);
            }
        }

        [Obsolete("Use CreationStatusResult instead")]
        bool ITemplateEngineHost.OnPotentiallyDestructiveChangesDetected(IReadOnlyList<IFileChange> changes, IReadOnlyList<IFileChange> destructiveChanges)
        {
            //return false to return TemplateCreationResult with CreationResultStatus.DestructiveChangesDetected status.
            return false;
        }
    }
}