-
Notifications
You must be signed in to change notification settings - Fork 745
Add ICliHostEnvironment service and --non-interactive flag for clean CI output #12135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
fb29933
6d64a21
a2ed2de
666863b
9bb5cee
f532b7b
a676c3e
175947c
4dd344e
4f460ce
bcdc802
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,6 +54,9 @@ private static string GetGlobalSettingsPath() | |
|
|
||
| private static async Task<IHost> BuildApplicationAsync(string[] args) | ||
| { | ||
| // Check for --non-interactive flag early | ||
| var nonInteractive = args?.Any(a => a == "--non-interactive") ?? false; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may have a better way of doing this. The That said -- you'll want to have an implementation of |
||
|
|
||
| var settings = new HostApplicationBuilderSettings | ||
| { | ||
| Configuration = new ConfigurationManager() | ||
|
|
@@ -109,6 +112,11 @@ private static async Task<IHost> BuildApplicationAsync(string[] args) | |
| // Shared services. | ||
| builder.Services.AddSingleton(_ => BuildCliExecutionContext(debugMode)); | ||
| builder.Services.AddSingleton(BuildAnsiConsole); | ||
| builder.Services.AddSingleton<ICliHostEnvironment>(provider => | ||
| { | ||
| var configuration = provider.GetRequiredService<IConfiguration>(); | ||
| return new CliHostEnvironment(configuration, nonInteractive); | ||
| }); | ||
| AddInteractionServices(builder); | ||
| builder.Services.AddSingleton<IProjectLocator, ProjectLocator>(); | ||
| builder.Services.AddSingleton<ISolutionLocator, SolutionLocator>(); | ||
|
|
@@ -262,7 +270,8 @@ private static void AddInteractionServices(HostApplicationBuilder builder) | |
| var ansiConsole = provider.GetRequiredService<IAnsiConsole>(); | ||
| ansiConsole.Profile.Width = 256; // VS code terminal will handle wrapping so set a large width here. | ||
| var executionContext = provider.GetRequiredService<CliExecutionContext>(); | ||
| var consoleInteractionService = new ConsoleInteractionService(ansiConsole, executionContext); | ||
| var hostEnvironment = provider.GetRequiredService<ICliHostEnvironment>(); | ||
| var consoleInteractionService = new ConsoleInteractionService(ansiConsole, executionContext, hostEnvironment); | ||
| return new ExtensionInteractionService(consoleInteractionService, | ||
| provider.GetRequiredService<IExtensionBackchannel>(), | ||
| extensionPromptEnabled); | ||
|
|
@@ -279,7 +288,8 @@ private static void AddInteractionServices(HostApplicationBuilder builder) | |
| { | ||
| var ansiConsole = provider.GetRequiredService<IAnsiConsole>(); | ||
| var executionContext = provider.GetRequiredService<CliExecutionContext>(); | ||
| return new ConsoleInteractionService(ansiConsole, executionContext); | ||
| var hostEnvironment = provider.GetRequiredService<ICliHostEnvironment>(); | ||
| return new ConsoleInteractionService(ansiConsole, executionContext, hostEnvironment); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| // 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.Configuration; | ||
|
|
||
| namespace Aspire.Cli.Utils; | ||
|
|
||
| /// <summary> | ||
| /// Provides information about the CLI host environment capabilities. | ||
| /// </summary> | ||
| internal interface ICliHostEnvironment | ||
| { | ||
| /// <summary> | ||
| /// Gets whether the host supports interactive input (e.g., prompts, user input). | ||
| /// </summary> | ||
| bool SupportsInteractiveInput { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets whether the host supports interactive output (e.g., spinners, progress bars). | ||
| /// </summary> | ||
| bool SupportsInteractiveOutput { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets whether the host supports colors and ANSI codes. | ||
| /// </summary> | ||
| bool SupportsAnsi { get; } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is amazing! Thank you, I was going to report an issue about this and I was asking on Aspirifriday's about it as well. 🔥 |
||
| } | ||
|
|
||
| /// <summary> | ||
| /// Default implementation that detects CLI host environment capabilities from configuration. | ||
| /// </summary> | ||
| internal sealed class CliHostEnvironment : ICliHostEnvironment | ||
| { | ||
| /// <summary> | ||
| /// Gets whether the host supports interactive input (e.g., prompts, user input). | ||
| /// </summary> | ||
| public bool SupportsInteractiveInput { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets whether the host supports interactive output (e.g., spinners, progress bars). | ||
| /// </summary> | ||
| public bool SupportsInteractiveOutput { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets whether the host supports colors and ANSI codes. | ||
| /// </summary> | ||
| public bool SupportsAnsi { get; } | ||
|
|
||
| public CliHostEnvironment(IConfiguration configuration, bool nonInteractive) | ||
| { | ||
| // If --non-interactive is explicitly set, disable interactive input and output | ||
| if (nonInteractive) | ||
| { | ||
| SupportsInteractiveInput = false; | ||
| SupportsInteractiveOutput = false; | ||
| } | ||
| else | ||
| { | ||
| SupportsInteractiveInput = DetectInteractiveInput(configuration); | ||
| SupportsInteractiveOutput = DetectInteractiveOutput(configuration); | ||
| } | ||
|
|
||
| SupportsAnsi = DetectAnsiSupport(configuration); | ||
| } | ||
|
|
||
| private static bool DetectInteractiveInput(IConfiguration configuration) | ||
| { | ||
| // Check if explicitly disabled via configuration | ||
| var nonInteractive = configuration["ASPIRE_NON_INTERACTIVE"]; | ||
| if (!string.IsNullOrEmpty(nonInteractive) && | ||
| (nonInteractive.Equals("true", StringComparison.OrdinalIgnoreCase) || | ||
| nonInteractive.Equals("1", StringComparison.Ordinal))) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| // Check if running in CI environment (no interactive input possible) | ||
| if (IsCI(configuration)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| private static bool DetectInteractiveOutput(IConfiguration configuration) | ||
| { | ||
| // Check if explicitly disabled via configuration | ||
| var nonInteractive = configuration["ASPIRE_NON_INTERACTIVE"]; | ||
| if (!string.IsNullOrEmpty(nonInteractive) && | ||
| (nonInteractive.Equals("true", StringComparison.OrdinalIgnoreCase) || | ||
| nonInteractive.Equals("1", StringComparison.Ordinal))) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| // Check if running in CI environment (spinners pollute logs) | ||
| if (IsCI(configuration)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| private static bool DetectAnsiSupport(IConfiguration configuration) | ||
| { | ||
| // ANSI codes are supported even in CI environments for colored output | ||
| // Only disable if explicitly configured | ||
| var noColor = configuration["NO_COLOR"]; | ||
| if (!string.IsNullOrEmpty(noColor)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| private static bool IsCI(IConfiguration configuration) | ||
| { | ||
| // Check for common CI environment variables | ||
| // https://github.com/watson/ci-info/blob/master/vendors.json | ||
| var ciEnvVars = new[] | ||
| { | ||
| "CI", // Generic CI indicator | ||
| "GITHUB_ACTIONS", | ||
| "AZURE_PIPELINES", | ||
| "TF_BUILD", // Azure Pipelines alternative | ||
| "JENKINS_URL", | ||
| "GITLAB_CI", | ||
| "CIRCLECI", | ||
| "TRAVIS", | ||
| "BUILDKITE", | ||
| "APPVEYOR", | ||
| "TEAMCITY_VERSION", | ||
| "BITBUCKET_BUILD_NUMBER", | ||
| "CODEBUILD_BUILD_ID", // AWS CodeBuild | ||
| }; | ||
davidfowl marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| foreach (var envVar in ciEnvVars) | ||
| { | ||
| var value = configuration[envVar]; | ||
| if (!string.IsNullOrEmpty(value)) | ||
| { | ||
| // For CI variable, only return true if it's "true" or "1" | ||
| if (envVar == "CI") | ||
| { | ||
| return value.Equals("true", StringComparison.OrdinalIgnoreCase) || | ||
| value.Equals("1", StringComparison.Ordinal); | ||
| } | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should consider hiding this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, interesting thought. Does that mean we'd only want this for publish/deploy?