diff --git a/src/Aspire.Cli/Utils/CliHostEnvironment.cs b/src/Aspire.Cli/Utils/CliHostEnvironment.cs index 5f6a3f3e4b0..2daf9109acd 100644 --- a/src/Aspire.Cli/Utils/CliHostEnvironment.cs +++ b/src/Aspire.Cli/Utils/CliHostEnvironment.cs @@ -66,6 +66,11 @@ internal sealed class CliHostEnvironment : ICliHostEnvironment public bool SupportsAnsi { get; } public CliHostEnvironment(IConfiguration configuration, bool nonInteractive) + : this(configuration, nonInteractive, Console.IsInputRedirected, Console.IsOutputRedirected) + { + } + + public CliHostEnvironment(IConfiguration configuration, bool nonInteractive, bool isInputRedirected, bool isOutputRedirected) { // If --non-interactive is explicitly set, disable interactive input and output if (nonInteractive) @@ -75,14 +80,14 @@ public CliHostEnvironment(IConfiguration configuration, bool nonInteractive) } else { - SupportsInteractiveInput = DetectInteractiveInput(configuration); - SupportsInteractiveOutput = DetectInteractiveOutput(configuration); + SupportsInteractiveInput = DetectInteractiveInput(configuration, isInputRedirected); + SupportsInteractiveOutput = DetectInteractiveOutput(configuration, isOutputRedirected); } - SupportsAnsi = DetectAnsiSupport(configuration); + SupportsAnsi = DetectAnsiSupport(configuration, isOutputRedirected); } - private static bool DetectInteractiveInput(IConfiguration configuration) + private static bool DetectInteractiveInput(IConfiguration configuration, bool isInputRedirected) { // Check if explicitly disabled via configuration var nonInteractive = configuration["ASPIRE_NON_INTERACTIVE"]; @@ -99,10 +104,16 @@ private static bool DetectInteractiveInput(IConfiguration configuration) return false; } + // Check if console input is redirected (e.g., piped from a file or another command) + if (isInputRedirected) + { + return false; + } + return true; } - private static bool DetectInteractiveOutput(IConfiguration configuration) + private static bool DetectInteractiveOutput(IConfiguration configuration, bool isOutputRedirected) { // Check if explicitly disabled via configuration var nonInteractive = configuration["ASPIRE_NON_INTERACTIVE"]; @@ -119,19 +130,30 @@ private static bool DetectInteractiveOutput(IConfiguration configuration) return false; } + // Check if console output is redirected (e.g., piped to a file or another command) + if (isOutputRedirected) + { + return false; + } + return true; } - private static bool DetectAnsiSupport(IConfiguration configuration) + private static bool DetectAnsiSupport(IConfiguration configuration, bool isOutputRedirected) { - // ANSI codes are supported even in CI environments for colored output - // Only disable if explicitly configured + // Check if explicitly disabled via NO_COLOR environment variable var noColor = configuration["NO_COLOR"]; if (!string.IsNullOrEmpty(noColor)) { return false; } + // Disable ANSI colors when console output is redirected to avoid garbled output in files + if (isOutputRedirected) + { + return false; + } + return true; } diff --git a/tests/Aspire.Cli.Tests/TestHelpers.cs b/tests/Aspire.Cli.Tests/TestHelpers.cs index 1ad76ade2ef..2ccf424361f 100644 --- a/tests/Aspire.Cli.Tests/TestHelpers.cs +++ b/tests/Aspire.Cli.Tests/TestHelpers.cs @@ -11,12 +11,13 @@ internal static class TestHelpers public static ICliHostEnvironment CreateInteractiveHostEnvironment() { var configuration = new ConfigurationBuilder().Build(); - return new CliHostEnvironment(configuration, nonInteractive: false); + // For tests, explicitly set redirection to false to simulate an interactive environment + return new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: false); } public static ICliHostEnvironment CreateNonInteractiveHostEnvironment() { var configuration = new ConfigurationBuilder().Build(); - return new CliHostEnvironment(configuration, nonInteractive: true); + return new CliHostEnvironment(configuration, nonInteractive: true, isInputRedirected: false, isOutputRedirected: false); } } diff --git a/tests/Aspire.Cli.Tests/Utils/CliHostEnvironmentTests.cs b/tests/Aspire.Cli.Tests/Utils/CliHostEnvironmentTests.cs index 7a0217e7a84..8f96b6c8ffb 100644 --- a/tests/Aspire.Cli.Tests/Utils/CliHostEnvironmentTests.cs +++ b/tests/Aspire.Cli.Tests/Utils/CliHostEnvironmentTests.cs @@ -15,7 +15,7 @@ public void SupportsInteractiveInput_ReturnsTrue_WhenNoConfigSet() var configuration = new ConfigurationBuilder().Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: false); + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.True(env.SupportsInteractiveInput); @@ -28,7 +28,7 @@ public void SupportsInteractiveOutput_ReturnsTrue_WhenNoConfigSet() var configuration = new ConfigurationBuilder().Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: false); + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.True(env.SupportsInteractiveOutput); @@ -41,7 +41,7 @@ public void SupportsAnsi_ReturnsTrue_WhenNoConfigSet() var configuration = new ConfigurationBuilder().Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: false); + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.True(env.SupportsAnsi); @@ -61,7 +61,7 @@ public void SupportsInteractiveInput_ReturnsFalse_WhenNonInteractiveSet(string k .Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: false); + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.False(env.SupportsInteractiveInput); @@ -81,7 +81,7 @@ public void SupportsInteractiveOutput_ReturnsFalse_WhenNonInteractiveSet(string .Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: false); + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.False(env.SupportsInteractiveOutput); @@ -104,7 +104,7 @@ public void SupportsInteractiveInput_ReturnsFalse_InCIEnvironment(string envVar, .Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: false); + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.False(env.SupportsInteractiveInput); @@ -126,7 +126,7 @@ public void SupportsInteractiveOutput_ReturnsFalse_InCIEnvironment(string envVar .Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: false); + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.False(env.SupportsInteractiveOutput); @@ -138,7 +138,7 @@ public void SupportsInteractiveOutput_ReturnsFalse_InCIEnvironment(string envVar [InlineData("GITHUB_ACTIONS", "true")] public void SupportsAnsi_ReturnsTrue_InCIEnvironment(string envVar, string value) { - // Arrange - ANSI should still be supported in CI for colored output + // Arrange - ANSI can still be supported in CI if output is not redirected var configuration = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { @@ -147,7 +147,7 @@ public void SupportsAnsi_ReturnsTrue_InCIEnvironment(string envVar, string value .Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: false); + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.True(env.SupportsAnsi); @@ -165,7 +165,7 @@ public void SupportsAnsi_ReturnsFalse_WhenNO_COLORSet() .Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: false); + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.False(env.SupportsAnsi); @@ -178,7 +178,7 @@ public void SupportsInteractiveInput_ReturnsFalse_WhenNonInteractiveTrue() var configuration = new ConfigurationBuilder().Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: true); + var env = new CliHostEnvironment(configuration, nonInteractive: true, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.False(env.SupportsInteractiveInput); @@ -191,7 +191,7 @@ public void SupportsInteractiveOutput_ReturnsFalse_WhenNonInteractiveTrue() var configuration = new ConfigurationBuilder().Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: true); + var env = new CliHostEnvironment(configuration, nonInteractive: true, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.False(env.SupportsInteractiveOutput); @@ -200,13 +200,52 @@ public void SupportsInteractiveOutput_ReturnsFalse_WhenNonInteractiveTrue() [Fact] public void SupportsAnsi_ReturnsTrue_WhenNonInteractiveTrue() { - // Arrange - ANSI should still be supported even in non-interactive mode + // Arrange - ANSI can still be supported even in non-interactive mode var configuration = new ConfigurationBuilder().Build(); // Act - var env = new CliHostEnvironment(configuration, nonInteractive: true); + var env = new CliHostEnvironment(configuration, nonInteractive: true, isInputRedirected: false, isOutputRedirected: false); // Assert Assert.True(env.SupportsAnsi); } + + [Fact] + public void SupportsInteractiveInput_ReturnsFalse_WhenInputRedirected() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + + // Act + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: true, isOutputRedirected: false); + + // Assert + Assert.False(env.SupportsInteractiveInput); + } + + [Fact] + public void SupportsInteractiveOutput_ReturnsFalse_WhenOutputRedirected() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + + // Act + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: true); + + // Assert + Assert.False(env.SupportsInteractiveOutput); + } + + [Fact] + public void SupportsAnsi_ReturnsFalse_WhenOutputRedirected() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + + // Act + var env = new CliHostEnvironment(configuration, nonInteractive: false, isInputRedirected: false, isOutputRedirected: true); + + // Assert + Assert.False(env.SupportsAnsi); + } }