Skip to content

Commit c05b6d2

Browse files
CopilotmitchdennyCopilot
authored
Add ASPIRE_PLAYGROUND environment variable to force interactive mode with ANSI and color support (#12387)
* Initial plan * Add ASPIRE_PLAYGROUND environment variable support for forced interactive mode Co-authored-by: mitchdenny <[email protected]> * Refactor playground mode detection to eliminate code duplication and clarify test names Co-authored-by: mitchdenny <[email protected]> * Enable ANSI support and colors when ASPIRE_PLAYGROUND=true Co-authored-by: mitchdenny <[email protected]> * Output terminal capabiltites to debug issue. * Override enrichers for ASPIRE_PLAYGROUND. * Remove diagnostic messages. * Update src/Aspire.Cli/Program.cs Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: mitchdenny <[email protected]> Co-authored-by: Mitch Denny <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent cfde826 commit c05b6d2

File tree

3 files changed

+253
-7
lines changed

3 files changed

+253
-7
lines changed

src/Aspire.Cli/Program.cs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ private static async Task<IHost> BuildApplicationAsync(string[] args)
130130
builder.Services.AddSingleton<IFeatures, Features>();
131131
builder.Services.AddSingleton<AspireCliTelemetry>();
132132
builder.Services.AddTransient<IDotNetCliRunner, DotNetCliRunner>();
133-
builder.Services.AddSingleton<IDiskCache, DiskCache>();
133+
builder.Services.AddSingleton<IDiskCache, DiskCache>();
134134
builder.Services.AddSingleton<IDotNetSdkInstaller, DotNetSdkInstaller>();
135135
builder.Services.AddTransient<IAppHostBackchannel, AppHostBackchannel>();
136136
builder.Services.AddSingleton<INuGetPackageCache, NuGetPackageCache>();
@@ -229,13 +229,28 @@ private static IConfigurationService BuildConfigurationService(IServiceProvider
229229

230230
private static IAnsiConsole BuildAnsiConsole(IServiceProvider serviceProvider)
231231
{
232+
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
233+
var isPlayground = CliHostEnvironment.IsPlaygroundMode(configuration);
234+
232235
var settings = new AnsiConsoleSettings()
233236
{
234-
Ansi = AnsiSupport.Detect,
235-
Interactive = InteractionSupport.Detect,
236-
ColorSystem = ColorSystemSupport.Detect
237+
Ansi = isPlayground ? AnsiSupport.Yes : AnsiSupport.Detect,
238+
Interactive = isPlayground ? InteractionSupport.Yes : InteractionSupport.Detect,
239+
ColorSystem = isPlayground ? ColorSystemSupport.Standard : ColorSystemSupport.Detect,
237240
};
238241

242+
if (isPlayground)
243+
{
244+
// Enrichers interfere with interactive playground experience so
245+
// this suppresses the default enrichers so that the CLI experience
246+
// is more like what we would get in an interactive experience.
247+
settings.Enrichment.UseDefaultEnrichers = false;
248+
settings.Enrichment.Enrichers = new()
249+
{
250+
new AspirePlaygroundEnricher()
251+
};
252+
}
253+
239254
var ansiConsole = AnsiConsole.Create(settings);
240255
return ansiConsole;
241256
}
@@ -299,3 +314,28 @@ private static void AddInteractionServices(HostApplicationBuilder builder)
299314
}
300315
}
301316
}
317+
318+
internal class AspirePlaygroundEnricher : IProfileEnricher
319+
{
320+
public string Name => "Aspire Playground";
321+
322+
public bool Enabled(IDictionary<string, string> environmentVariables)
323+
{
324+
if (!environmentVariables.TryGetValue("ASPIRE_PLAYGROUND", out var value))
325+
{
326+
return false;
327+
}
328+
329+
if (!bool.TryParse(value, out var isEnabled))
330+
{
331+
return false;
332+
}
333+
334+
return isEnabled;
335+
}
336+
337+
public void Enrich(Profile profile)
338+
{
339+
profile.Capabilities.Interactive = true;
340+
}
341+
}

src/Aspire.Cli/Utils/CliHostEnvironment.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,29 @@ internal sealed class CliHostEnvironment : ICliHostEnvironment
6767

6868
public CliHostEnvironment(IConfiguration configuration, bool nonInteractive)
6969
{
70+
// Check if ASPIRE_PLAYGROUND is set to force interactive mode
71+
var playgroundMode = IsPlaygroundMode(configuration);
72+
73+
// If ASPIRE_PLAYGROUND is set, force interactive mode and ANSI support
74+
if (playgroundMode)
75+
{
76+
SupportsInteractiveInput = true;
77+
SupportsInteractiveOutput = true;
78+
SupportsAnsi = true;
79+
}
7080
// If --non-interactive is explicitly set, disable interactive input and output
71-
if (nonInteractive)
81+
else if (nonInteractive)
7282
{
7383
SupportsInteractiveInput = false;
7484
SupportsInteractiveOutput = false;
85+
SupportsAnsi = DetectAnsiSupport(configuration);
7586
}
7687
else
7788
{
7889
SupportsInteractiveInput = DetectInteractiveInput(configuration);
7990
SupportsInteractiveOutput = DetectInteractiveOutput(configuration);
91+
SupportsAnsi = DetectAnsiSupport(configuration);
8092
}
81-
82-
SupportsAnsi = DetectAnsiSupport(configuration);
8393
}
8494

8595
private static bool DetectInteractiveInput(IConfiguration configuration)
@@ -154,4 +164,14 @@ private static bool IsCI(IConfiguration configuration)
154164

155165
return false;
156166
}
167+
168+
/// <summary>
169+
/// Gets whether the ASPIRE_PLAYGROUND environment variable is set to force interactive mode.
170+
/// </summary>
171+
internal static bool IsPlaygroundMode(IConfiguration configuration)
172+
{
173+
var playgroundMode = configuration["ASPIRE_PLAYGROUND"];
174+
return !string.IsNullOrEmpty(playgroundMode) &&
175+
playgroundMode.Equals("true", StringComparison.OrdinalIgnoreCase);
176+
}
157177
}

tests/Aspire.Cli.Tests/Utils/CliHostEnvironmentTests.cs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,190 @@ public void SupportsAnsi_ReturnsTrue_WhenNonInteractiveTrue()
209209
// Assert
210210
Assert.True(env.SupportsAnsi);
211211
}
212+
213+
[Fact]
214+
public void SupportsInteractiveInput_ReturnsTrue_WhenPlaygroundModeSet()
215+
{
216+
// Arrange
217+
var configuration = new ConfigurationBuilder()
218+
.AddInMemoryCollection(new Dictionary<string, string?>
219+
{
220+
["ASPIRE_PLAYGROUND"] = "true"
221+
})
222+
.Build();
223+
224+
// Act
225+
var env = new CliHostEnvironment(configuration, nonInteractive: false);
226+
227+
// Assert
228+
Assert.True(env.SupportsInteractiveInput);
229+
}
230+
231+
[Fact]
232+
public void SupportsInteractiveOutput_ReturnsTrue_WhenPlaygroundModeSet()
233+
{
234+
// Arrange
235+
var configuration = new ConfigurationBuilder()
236+
.AddInMemoryCollection(new Dictionary<string, string?>
237+
{
238+
["ASPIRE_PLAYGROUND"] = "true"
239+
})
240+
.Build();
241+
242+
// Act
243+
var env = new CliHostEnvironment(configuration, nonInteractive: false);
244+
245+
// Assert
246+
Assert.True(env.SupportsInteractiveOutput);
247+
}
248+
249+
[Fact]
250+
public void SupportsInteractiveInput_ReturnsTrue_WhenPlaygroundModeSet_EvenInCI()
251+
{
252+
// Arrange - ASPIRE_PLAYGROUND should override CI environment detection
253+
var configuration = new ConfigurationBuilder()
254+
.AddInMemoryCollection(new Dictionary<string, string?>
255+
{
256+
["ASPIRE_PLAYGROUND"] = "true",
257+
["CI"] = "true"
258+
})
259+
.Build();
260+
261+
// Act
262+
var env = new CliHostEnvironment(configuration, nonInteractive: false);
263+
264+
// Assert
265+
Assert.True(env.SupportsInteractiveInput);
266+
}
267+
268+
[Fact]
269+
public void SupportsInteractiveOutput_ReturnsTrue_WhenPlaygroundModeSet_EvenInCI()
270+
{
271+
// Arrange - ASPIRE_PLAYGROUND should override CI environment detection
272+
var configuration = new ConfigurationBuilder()
273+
.AddInMemoryCollection(new Dictionary<string, string?>
274+
{
275+
["ASPIRE_PLAYGROUND"] = "true",
276+
["GITHUB_ACTIONS"] = "true"
277+
})
278+
.Build();
279+
280+
// Act
281+
var env = new CliHostEnvironment(configuration, nonInteractive: false);
282+
283+
// Assert
284+
Assert.True(env.SupportsInteractiveOutput);
285+
}
286+
287+
[Fact]
288+
public void SupportsInteractiveInput_ReturnsTrue_WhenPlaygroundModeSet_ButNonInteractiveIsTrue()
289+
{
290+
// Arrange - ASPIRE_PLAYGROUND should take precedence over --non-interactive flag
291+
var configuration = new ConfigurationBuilder()
292+
.AddInMemoryCollection(new Dictionary<string, string?>
293+
{
294+
["ASPIRE_PLAYGROUND"] = "true"
295+
})
296+
.Build();
297+
298+
// Act
299+
var env = new CliHostEnvironment(configuration, nonInteractive: true);
300+
301+
// Assert
302+
// ASPIRE_PLAYGROUND takes precedence over the --non-interactive flag
303+
Assert.True(env.SupportsInteractiveInput);
304+
}
305+
306+
[Fact]
307+
public void SupportsInteractiveOutput_ReturnsTrue_WhenPlaygroundModeSet_ButNonInteractiveIsTrue()
308+
{
309+
// Arrange - ASPIRE_PLAYGROUND should take precedence over --non-interactive flag
310+
var configuration = new ConfigurationBuilder()
311+
.AddInMemoryCollection(new Dictionary<string, string?>
312+
{
313+
["ASPIRE_PLAYGROUND"] = "true"
314+
})
315+
.Build();
316+
317+
// Act
318+
var env = new CliHostEnvironment(configuration, nonInteractive: true);
319+
320+
// Assert
321+
// ASPIRE_PLAYGROUND takes precedence over the --non-interactive flag
322+
Assert.True(env.SupportsInteractiveOutput);
323+
}
324+
325+
[Fact]
326+
public void SupportsInteractiveInput_ReturnsFalse_WhenPlaygroundModeSetToFalse()
327+
{
328+
// Arrange
329+
var configuration = new ConfigurationBuilder()
330+
.AddInMemoryCollection(new Dictionary<string, string?>
331+
{
332+
["ASPIRE_PLAYGROUND"] = "false",
333+
["CI"] = "true"
334+
})
335+
.Build();
336+
337+
// Act
338+
var env = new CliHostEnvironment(configuration, nonInteractive: false);
339+
340+
// Assert
341+
Assert.False(env.SupportsInteractiveInput);
342+
}
343+
344+
[Fact]
345+
public void SupportsAnsi_ReturnsTrue_WhenPlaygroundModeSet()
346+
{
347+
// Arrange
348+
var configuration = new ConfigurationBuilder()
349+
.AddInMemoryCollection(new Dictionary<string, string?>
350+
{
351+
["ASPIRE_PLAYGROUND"] = "true"
352+
})
353+
.Build();
354+
355+
// Act
356+
var env = new CliHostEnvironment(configuration, nonInteractive: false);
357+
358+
// Assert
359+
Assert.True(env.SupportsAnsi);
360+
}
361+
362+
[Fact]
363+
public void SupportsAnsi_ReturnsTrue_WhenPlaygroundModeSet_EvenWithNO_COLOR()
364+
{
365+
// Arrange - ASPIRE_PLAYGROUND should override NO_COLOR
366+
var configuration = new ConfigurationBuilder()
367+
.AddInMemoryCollection(new Dictionary<string, string?>
368+
{
369+
["ASPIRE_PLAYGROUND"] = "true",
370+
["NO_COLOR"] = "1"
371+
})
372+
.Build();
373+
374+
// Act
375+
var env = new CliHostEnvironment(configuration, nonInteractive: false);
376+
377+
// Assert
378+
Assert.True(env.SupportsAnsi);
379+
}
380+
381+
[Fact]
382+
public void SupportsAnsi_ReturnsTrue_WhenPlaygroundModeSet_WithNonInteractive()
383+
{
384+
// Arrange - ASPIRE_PLAYGROUND should enable ANSI even with --non-interactive
385+
var configuration = new ConfigurationBuilder()
386+
.AddInMemoryCollection(new Dictionary<string, string?>
387+
{
388+
["ASPIRE_PLAYGROUND"] = "true"
389+
})
390+
.Build();
391+
392+
// Act
393+
var env = new CliHostEnvironment(configuration, nonInteractive: true);
394+
395+
// Assert
396+
Assert.True(env.SupportsAnsi);
397+
}
212398
}

0 commit comments

Comments
 (0)