File: Utils\AspireAnsiConsoleOutput.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.Tool.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Text;
using Microsoft.Extensions.Configuration;
using Spectre.Console;
 
namespace Aspire.Cli.Utils;
 
/// <summary>
/// Custom console output that handles width detection in CI environments and respects ASPIRE_CONSOLE_WIDTH.
/// </summary>
internal sealed class AspireAnsiConsoleOutput : IAnsiConsoleOutput
{
    private readonly TextWriter _writer;
    private readonly IConfiguration _configuration;
    private int? _width;
 
    /// <inheritdoc/>
    public TextWriter Writer => _writer;
 
    /// <inheritdoc/>
    public bool IsTerminal
    {
        get
        {
            if (IsStandardOut(_writer))
            {
                return !Console.IsOutputRedirected;
            }
 
            if (IsStandardError(_writer))
            {
                return !Console.IsErrorRedirected;
            }
 
            return false;
        }
    }
 
    /// <inheritdoc/>
    public int Width
    {
        get => _width ?? GetConfiguredWidth();
        set => _width = value;
    }
 
    /// <inheritdoc/>
    public int Height => GetSafeHeight();
 
    /// <summary>
    /// Initializes a new instance of the <see cref="AspireAnsiConsoleOutput"/> class.
    /// </summary>
    /// <param name="writer">The text writer for console output.</param>
    /// <param name="configuration">The configuration for reading environment variables.</param>
    public AspireAnsiConsoleOutput(TextWriter writer, IConfiguration configuration)
    {
        _writer = writer ?? throw new ArgumentNullException(nameof(writer));
        _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
    }
 
    private int GetConfiguredWidth()
    {
        // Check if explicit width override is set via ASPIRE_CONSOLE_WIDTH
        var consoleWidthOverride = _configuration["ASPIRE_CONSOLE_WIDTH"];
        if (!string.IsNullOrEmpty(consoleWidthOverride) && 
            int.TryParse(consoleWidthOverride, out var width) && 
            width > 0)
        {
            // Cap at reasonable maximum to prevent performance issues
            return Math.Min(width, 500);
        }
 
        // Get width from console, automatically handling CI environment defaults
        return GetSafeWidth();
    }
 
    /// <inheritdoc/>
    public void SetEncoding(Encoding encoding)
    {
        if (IsStandardOut(_writer) || IsStandardError(_writer))
        {
            Console.OutputEncoding = encoding;
        }
    }
 
    private static bool IsStandardOut(TextWriter writer)
    {
        try
        {
            return writer == Console.Out;
        }
        catch (Exception ex) when (ex is IOException or InvalidOperationException)
        {
            return false;
        }
    }
 
    private static bool IsStandardError(TextWriter writer)
    {
        try
        {
            return writer == Console.Error;
        }
        catch (Exception ex) when (ex is IOException or InvalidOperationException)
        {
            return false;
        }
    }
 
    private int GetSafeWidth()
    {
        try
        {
            var width = Console.BufferWidth;
            if (width == 0)
            {
                // Return default width for non-terminal environments
                // In CI environments, 80 columns is too narrow, use 160 instead
                return IsTerminal ? 80 : 160;
            }
 
            return width;
        }
        catch (IOException)
        {
            // When console is redirected (CI environments), use a wider default (160)
            // to avoid awkward line breaks in logs
            return 160;
        }
    }
 
    private static int GetSafeHeight()
    {
        try
        {
            var height = Console.WindowHeight;
            if (height == 0)
            {
                return 24;
            }
 
            return height;
        }
        catch (IOException)
        {
            return 24;
        }
    }
}