diff --git a/src/Aspire.Cli/Commands/BaseCommand.cs b/src/Aspire.Cli/Commands/BaseCommand.cs
index e90f57cef46..0e23e7b0e93 100644
--- a/src/Aspire.Cli/Commands/BaseCommand.cs
+++ b/src/Aspire.Cli/Commands/BaseCommand.cs
@@ -61,6 +61,8 @@ protected BaseCommand(string name, string description, IFeatures features, ICliU
result = CommandResult.Failure((int)CliExitCodes.MissingRequiredArgument);
}
+ var isErrorExitCode = result.ExitCode != CliExitCodes.Success;
+
if (result.ErrorMessage is not null)
{
interactionService.DisplayError(result.ErrorMessage);
@@ -74,18 +76,19 @@ protected BaseCommand(string name, string description, IFeatures features, ICliU
if (result.ShouldDisplayCancellationMessage)
{
- interactionService.DisplayCancellationMessage();
+ interactionService.DisplayCancellationMessage(isErrorExitCode ? ConsoleOutput.Error : null);
}
// Display the CLI log file path on non-zero exit codes so the user knows
// where to find diagnostic details. Suppress for user-input errors where
// the log wouldn't contain useful context (e.g., missing required arguments).
- if (result.ExitCode != CliExitCodes.Success && result.ExitCode != CliExitCodes.MissingRequiredArgument)
+ if (isErrorExitCode && result.ExitCode != CliExitCodes.MissingRequiredArgument)
{
interactionService.DisplayMessage(
KnownEmojis.PageFacingUp,
string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.SeeLogsAt, MarkupHelpers.SafeFileLink(interactionService, executionContext.LogFilePath)),
- allowMarkup: true);
+ allowMarkup: true,
+ consoleOverride: ConsoleOutput.Error);
// If we connected to a running app host, also display the log file path of
// the CLI process that launched it so users can diagnose issues in both processes.
@@ -94,7 +97,8 @@ protected BaseCommand(string name, string description, IFeatures features, ICliU
interactionService.DisplayMessage(
KnownEmojis.MagnifyingGlassTiltedLeft,
string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.SeeAppHostLogsAt, MarkupHelpers.SafeFileLink(interactionService, executionContext.AppHostCliLogFilePath)),
- allowMarkup: true);
+ allowMarkup: true,
+ consoleOverride: ConsoleOutput.Error);
}
}
diff --git a/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs b/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs
index 36cfa874c21..98df4e7e650 100644
--- a/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs
+++ b/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs
@@ -36,10 +36,22 @@ internal class ConsoleInteractionService : IInteractionService
///
/// Console used for human-readable messages; routes to stderr when is set to .
///
- private IAnsiConsole MessageConsole => Console == ConsoleOutput.Error ? _errorConsole : _outConsole;
+ private IAnsiConsole MessageConsole => GetConsoleOutput(null);
// Limit logging to prompts and messages. Don't log raw text output since it may contain sensitive information.
- private ILogger MessageLogger => Console == ConsoleOutput.Error ? _stderrLogger : _stdoutLogger;
+ private ILogger MessageLogger => GetLogger(null);
+
+ private IAnsiConsole GetConsoleOutput(ConsoleOutput? consoleOverride) => (consoleOverride ?? Console) switch
+ {
+ ConsoleOutput.Error => _errorConsole,
+ _ => _outConsole
+ };
+
+ private ILogger GetLogger(ConsoleOutput? consoleOverride) => (consoleOverride ?? Console) switch
+ {
+ ConsoleOutput.Error => _stderrLogger,
+ _ => _stdoutLogger
+ };
public ConsoleOutput Console { get; set; }
@@ -373,8 +385,10 @@ public void DisplayError(string errorMessage, bool allowMarkup = false)
WriteEmojiMessage(_errorConsole, _stderrLogger, KnownEmojis.CrossMark, $"[red bold]{formatted}[/]", allowMarkup: true);
}
- public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false)
- => WriteEmojiMessage(MessageConsole, MessageLogger, emoji, message, allowMarkup);
+ public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false, ConsoleOutput? consoleOverride = null)
+ {
+ WriteEmojiMessage(GetConsoleOutput(consoleOverride), GetLogger(consoleOverride), emoji, message, allowMarkup);
+ }
private static void WriteEmojiMessage(IAnsiConsole target, ILogger logger, KnownEmoji emoji, string message, bool allowMarkup)
{
@@ -533,10 +547,10 @@ await MessageConsole.Live(initialRenderable)
});
}
- public void DisplayCancellationMessage()
+ public void DisplayCancellationMessage(ConsoleOutput? consoleOverride = null)
{
- MessageConsole.WriteLine();
- DisplayMessage(KnownEmojis.StopSign, $"[teal bold]{InteractionServiceStrings.StoppingAspire}[/]", allowMarkup: true);
+ GetConsoleOutput(consoleOverride).WriteLine();
+ DisplayMessage(KnownEmojis.StopSign, $"[teal bold]{InteractionServiceStrings.StoppingAspire}[/]", allowMarkup: true, consoleOverride: consoleOverride);
}
public async Task PromptConfirmAsync(string promptText, PromptBinding? binding = null, CancellationToken cancellationToken = default)
diff --git a/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs b/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs
index e908e26cc26..19c252333d1 100644
--- a/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs
+++ b/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs
@@ -352,12 +352,12 @@ public void DisplayError(string errorMessage, bool allowMarkup = false)
Debug.Assert(result);
}
- public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false)
+ public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false, ConsoleOutput? consoleOverride = null)
{
var result = _extensionTaskChannel.Writer.TryWrite(async () =>
{
await Backchannel.DisplayMessageAsync(emoji.Name, message.RemoveSpectreFormatting(), _cancellationToken);
- _consoleInteractionService.DisplayMessage(emoji, message, allowMarkup);
+ _consoleInteractionService.DisplayMessage(emoji, message, allowMarkup, consoleOverride);
});
Debug.Assert(result);
}
@@ -405,11 +405,11 @@ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lin
// here would surface every line twice.
}
- public void DisplayCancellationMessage()
+ public void DisplayCancellationMessage(ConsoleOutput? consoleOverride = null)
{
var result = _extensionTaskChannel.Writer.TryWrite(() => Backchannel.DisplayCancellationMessageAsync(_cancellationToken));
Debug.Assert(result);
- _consoleInteractionService.DisplayCancellationMessage();
+ _consoleInteractionService.DisplayCancellationMessage(consoleOverride);
}
public void DisplayEmptyLine()
diff --git a/src/Aspire.Cli/Interaction/IInteractionService.cs b/src/Aspire.Cli/Interaction/IInteractionService.cs
index 77cac8fb208..869894c7172 100644
--- a/src/Aspire.Cli/Interaction/IInteractionService.cs
+++ b/src/Aspire.Cli/Interaction/IInteractionService.cs
@@ -19,7 +19,7 @@ internal interface IInteractionService
Task> PromptForSelectionsAsync(string promptText, IEnumerable choices, Func choiceFormatter, IEnumerable? preSelected = null, bool optional = false, PromptBinding? binding = null, bool echoSelected = true, CancellationToken cancellationToken = default) where T : notnull;
int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, string appHostHostingVersion);
void DisplayError(string errorMessage, bool allowMarkup = false);
- void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false);
+ void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false, ConsoleOutput? consoleOverride = null);
void DisplayPlainText(string text);
void DisplayRawText(string text, ConsoleOutput? consoleOverride = null);
void DisplayMarkdown(string markdown, ConsoleOutput? consoleOverride = null, int? maxWidth = null);
@@ -29,7 +29,7 @@ internal interface IInteractionService
void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines);
void DisplayRenderable(IRenderable renderable);
Task DisplayLiveAsync(IRenderable initialRenderable, Func, Task> callback);
- void DisplayCancellationMessage();
+ void DisplayCancellationMessage(ConsoleOutput? consoleOverride = null);
void DisplayEmptyLine();
///
diff --git a/tests/Aspire.Cli.Tests/Commands/BaseCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/BaseCommandTests.cs
index d704c13e397..6124e5afd9d 100644
--- a/tests/Aspire.Cli.Tests/Commands/BaseCommandTests.cs
+++ b/tests/Aspire.Cli.Tests/Commands/BaseCommandTests.cs
@@ -1,8 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Globalization;
using Aspire.Cli.Commands;
using Aspire.Cli.Interaction;
+using Aspire.Cli.Projects;
+using Aspire.Cli.Resources;
using Aspire.Cli.Tests.TestServices;
using Aspire.Cli.Tests.Utils;
using Microsoft.AspNetCore.InternalTesting;
@@ -108,4 +111,163 @@ public async Task BaseCommand_WithUpdateNotification_DoesNotDisplayTrailingBlank
Assert.Equal(0, testInteractionService.DisplayEmptyLineCount);
}
+
+ [Fact]
+ public async Task BaseCommand_OnFailure_DisplaysLogFilePathOnStderr()
+ {
+ using var workspace = TemporaryWorkspace.Create(outputHelper);
+ var testInteractionService = new TestInteractionService();
+ var projectLocator = new TestProjectLocator
+ {
+ UseOrFindAppHostProjectFileWithBehaviorAsyncCallback = (_, _, _, _) =>
+ Task.FromResult(new AppHostProjectSearchResult(null, []))
+ };
+
+ var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
+ {
+ options.InteractionServiceFactory = _ => testInteractionService;
+ options.ProjectLocatorFactory = _ => projectLocator;
+ });
+ using var provider = services.BuildServiceProvider();
+
+ var command = provider.GetRequiredService();
+ var result = command.Parse("run");
+
+ var exitCode = await result.InvokeAsync().DefaultTimeout();
+
+ Assert.NotEqual(CliExitCodes.Success, exitCode);
+
+ var executionContext = provider.GetRequiredService();
+ var expectedLogMessage = string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.SeeLogsAt, executionContext.LogFilePath);
+ var logMessage = Assert.Single(testInteractionService.DisplayedMessages, m => m.Message == expectedLogMessage);
+ Assert.Equal(ConsoleOutput.Error, logMessage.ConsoleOverride);
+ }
+
+ [Fact]
+ public async Task BaseCommand_OnFailure_DisplaysAppHostLogFilePathOnStderr()
+ {
+ using var workspace = TemporaryWorkspace.Create(outputHelper);
+ var testInteractionService = new TestInteractionService();
+ var projectLocator = new TestProjectLocator
+ {
+ UseOrFindAppHostProjectFileWithBehaviorAsyncCallback = (_, _, _, _) =>
+ Task.FromResult(new AppHostProjectSearchResult(null, []))
+ };
+
+ var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
+ {
+ options.InteractionServiceFactory = _ => testInteractionService;
+ options.ProjectLocatorFactory = _ => projectLocator;
+ options.CliExecutionContextFactory = _ =>
+ {
+ var ctx = workspace.CreateExecutionContext();
+ ctx.AppHostCliLogFilePath = "/tmp/aspire-logs/apphost.log";
+ return ctx;
+ };
+ });
+ using var provider = services.BuildServiceProvider();
+
+ var command = provider.GetRequiredService();
+ var result = command.Parse("run");
+
+ var exitCode = await result.InvokeAsync().DefaultTimeout();
+
+ Assert.NotEqual(CliExitCodes.Success, exitCode);
+
+ var executionContext = provider.GetRequiredService();
+ var expectedCliLogMessage = string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.SeeLogsAt, executionContext.LogFilePath);
+ var expectedAppHostLogMessage = string.Format(CultureInfo.CurrentCulture, InteractionServiceStrings.SeeAppHostLogsAt, "/tmp/aspire-logs/apphost.log");
+
+ var cliLogMessage = Assert.Single(testInteractionService.DisplayedMessages, m => m.Message == expectedCliLogMessage);
+ Assert.Equal(ConsoleOutput.Error, cliLogMessage.ConsoleOverride);
+
+ var appHostLogMessage = Assert.Single(testInteractionService.DisplayedMessages, m => m.Message == expectedAppHostLogMessage);
+ Assert.Equal(ConsoleOutput.Error, appHostLogMessage.ConsoleOverride);
+ }
+
+ [Fact]
+ public async Task BaseCommand_OnCancellationWithErrorExitCode_DisplaysCancellationMessageOnStderr()
+ {
+ using var workspace = TemporaryWorkspace.Create(outputHelper);
+ var testInteractionService = new TestInteractionService();
+ var projectLocator = new TestProjectLocator
+ {
+ UseOrFindAppHostProjectFileWithBehaviorAsyncCallback = (_, _, _, _) =>
+ throw new OperationCanceledException()
+ };
+
+ var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
+ {
+ options.InteractionServiceFactory = _ => testInteractionService;
+ options.ProjectLocatorFactory = _ => projectLocator;
+ });
+ using var provider = services.BuildServiceProvider();
+
+ var command = provider.GetRequiredService();
+ var result = command.Parse("add SomePackage");
+
+ var exitCode = await result.InvokeAsync().DefaultTimeout();
+
+ // add catches OperationCanceledException and returns CommandResult.Cancelled() (exit code 130)
+ Assert.Equal(CliExitCodes.Cancelled, exitCode);
+
+ var cancellationOverride = Assert.Single(testInteractionService.DisplayedCancellations);
+ Assert.Equal(ConsoleOutput.Error, cancellationOverride);
+ }
+
+ [Fact]
+ public async Task BaseCommand_OnCancellationWithSuccessExitCode_DisplaysCancellationMessageOnStdout()
+ {
+ using var workspace = TemporaryWorkspace.Create(outputHelper);
+ var testInteractionService = new TestInteractionService();
+ var projectLocator = new TestProjectLocator
+ {
+ // Throw with the cancellation token so RunCommand's
+ // `catch (OperationCanceledException ex) when (ex.CancellationToken == cancellationToken)`
+ // matches and returns CommandResult.Cancelled(CliExitCodes.Success).
+ UseOrFindAppHostProjectFileWithBehaviorAsyncCallback = (_, _, _, ct) =>
+ throw new OperationCanceledException(ct)
+ };
+
+ var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
+ {
+ options.InteractionServiceFactory = _ => testInteractionService;
+ options.ProjectLocatorFactory = _ => projectLocator;
+ });
+ using var provider = services.BuildServiceProvider();
+
+ var command = provider.GetRequiredService();
+ // run catches OperationCanceledException and returns CommandResult.Cancelled(CliExitCodes.Success)
+ var result = command.Parse("run");
+
+ var exitCode = await result.InvokeAsync().DefaultTimeout();
+
+ Assert.Equal(CliExitCodes.Success, exitCode);
+
+ var cancellationOverride = Assert.Single(testInteractionService.DisplayedCancellations);
+ Assert.Null(cancellationOverride);
+ }
+
+ [Fact]
+ public async Task BaseCommand_OnSuccess_DoesNotDisplayLogFilePath()
+ {
+ using var workspace = TemporaryWorkspace.Create(outputHelper);
+ var testInteractionService = new TestInteractionService();
+ var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
+ {
+ options.InteractionServiceFactory = _ => testInteractionService;
+ });
+ using var provider = services.BuildServiceProvider();
+
+ var command = provider.GetRequiredService();
+ var result = command.Parse("ps");
+
+ var exitCode = await result.InvokeAsync().DefaultTimeout();
+
+ Assert.Equal(CliExitCodes.Success, exitCode);
+
+ // On success, no log file messages should be displayed
+ Assert.DoesNotContain(testInteractionService.DisplayedMessages,
+ m => m.ConsoleOverride == ConsoleOutput.Error);
+ }
}
diff --git a/tests/Aspire.Cli.Tests/Commands/DashboardRunCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/DashboardRunCommandTests.cs
index e70b350718f..51202ac5425 100644
--- a/tests/Aspire.Cli.Tests/Commands/DashboardRunCommandTests.cs
+++ b/tests/Aspire.Cli.Tests/Commands/DashboardRunCommandTests.cs
@@ -364,7 +364,7 @@ public async Task DashboardRunCommand_WhenCancelled_DisplaysCancellationMessageA
var exitCode = await pendingRun.DefaultTimeout();
Assert.Equal(CliExitCodes.Success, exitCode);
- Assert.Equal(1, testInteractionService.DisplayCancellationMessageCount);
+ Assert.Single(testInteractionService.DisplayedCancellations);
}
[Theory]
diff --git a/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs b/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs
index 6b034692269..fbac53444a6 100644
--- a/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs
+++ b/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs
@@ -950,11 +950,11 @@ public Task PromptConfirmAsync(string promptText, PromptBinding? bin
public void ShowStatus(string statusText, Action action, KnownEmoji? emoji = null, bool allowMarkup = false) => action();
public int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, string appHostHostingVersion) => 0;
public void DisplayError(string errorMessage, bool allowMarkup = false) => DisplayedErrors.Add(errorMessage);
- public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false) { }
+ public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false, ConsoleOutput? consoleOverride = null) { }
public void DisplaySuccess(string message, bool allowMarkup = false) { }
public void DisplaySubtleMessage(string message, bool allowMarkup = false) { }
public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines) { }
- public void DisplayCancellationMessage() { }
+ public void DisplayCancellationMessage(ConsoleOutput? consoleOverride = null) { }
public void DisplayEmptyLine() { }
public void DisplayPlainText(string text) { }
public void DisplayRawText(string text, ConsoleOutput? consoleOverride = null) { }
diff --git a/tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs
index 110d780e762..d217b684558 100644
--- a/tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs
+++ b/tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs
@@ -754,7 +754,7 @@ public async Task RunCommand_WhenDashboardFailsToStart_ContinuesWithWarning()
// The command should handle cancellation gracefully
Assert.Equal(CliExitCodes.Success, exitCode);
- Assert.Equal(1, testInteractionService.DisplayCancellationMessageCount);
+ Assert.Single(testInteractionService.DisplayedCancellations);
// Verify a warning was displayed (not an error)
var m = Assert.Single(testInteractionService.DisplayedMessages);
diff --git a/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs
index 457bcd480ca..c4096028ed2 100644
--- a/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs
+++ b/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs
@@ -2364,7 +2364,7 @@ public Task> PromptForSelectionsAsync(string promptText, IEn
public int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, string appHostHostingVersion)
=> _innerService.DisplayIncompatibleVersionError(ex, appHostHostingVersion);
public void DisplayError(string errorMessage, bool allowMarkup = false) => _innerService.DisplayError(errorMessage, allowMarkup);
- public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false) => _innerService.DisplayMessage(emoji, message, allowMarkup);
+ public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false, ConsoleOutput? consoleOverride = null) => _innerService.DisplayMessage(emoji, message, allowMarkup, consoleOverride);
public void DisplayPlainText(string text) => _innerService.DisplayPlainText(text);
public void DisplayRawText(string text, ConsoleOutput? consoleOverride = null) => _innerService.DisplayRawText(text, consoleOverride);
public void DisplayMarkdown(string markdown, ConsoleOutput? consoleOverride = null, int? maxWidth = null) => _innerService.DisplayMarkdown(markdown, consoleOverride, maxWidth);
@@ -2372,10 +2372,10 @@ public int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, stri
public void DisplaySuccess(string message, bool allowMarkup = false) => _innerService.DisplaySuccess(message, allowMarkup);
public void DisplaySubtleMessage(string message, bool allowMarkup = false) => _innerService.DisplaySubtleMessage(message, allowMarkup);
public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines) => _innerService.DisplayLines(lines);
- public void DisplayCancellationMessage()
+ public void DisplayCancellationMessage(ConsoleOutput? consoleOverride = null)
{
OnCancellationMessageDisplayed?.Invoke();
- _innerService.DisplayCancellationMessage();
+ _innerService.DisplayCancellationMessage(consoleOverride);
}
public void DisplayEmptyLine() => _innerService.DisplayEmptyLine();
public void DisplayVersionUpdateNotification(string newerVersion, string? updateCommand = null)
diff --git a/tests/Aspire.Cli.Tests/Projects/ExtensionGuestLauncherTests.cs b/tests/Aspire.Cli.Tests/Projects/ExtensionGuestLauncherTests.cs
index 4436e857f92..399971642ae 100644
--- a/tests/Aspire.Cli.Tests/Projects/ExtensionGuestLauncherTests.cs
+++ b/tests/Aspire.Cli.Tests/Projects/ExtensionGuestLauncherTests.cs
@@ -165,10 +165,10 @@ public Task LaunchAppHostAsync(string projectFile, List arguments, List<
public Task> PromptForSelectionsAsync(string promptText, IEnumerable choices, Func choiceFormatter, IEnumerable? preSelected = null, bool optional = false, PromptBinding? binding = null, bool echoSelected = true, CancellationToken cancellationToken = default) where T : notnull => throw new NotImplementedException();
public int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, string appHostHostingVersion) => throw new NotImplementedException();
public void DisplayError(string errorMessage, bool allowMarkup = false) => throw new NotImplementedException();
- public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false) => throw new NotImplementedException();
+ public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false, ConsoleOutput? consoleOverride = null) => throw new NotImplementedException();
public void DisplaySuccess(string message, bool allowMarkup = false) => throw new NotImplementedException();
public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines) => throw new NotImplementedException();
- public void DisplayCancellationMessage() => throw new NotImplementedException();
+ public void DisplayCancellationMessage(ConsoleOutput? consoleOverride = null) => throw new NotImplementedException();
public Task PromptConfirmAsync(string promptText, PromptBinding? binding = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public void DisplaySubtleMessage(string message, bool allowMarkup = false) => throw new NotImplementedException();
public void DisplayEmptyLine() => throw new NotImplementedException();
diff --git a/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs b/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs
index 0948cf06eee..0a9cdf29808 100644
--- a/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs
+++ b/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs
@@ -397,9 +397,9 @@ public void ShowStatus(string message, Action work, KnownEmoji? emoji = null, bo
public void DisplaySuccess(string message, bool allowMarkup = false) { }
public void DisplayError(string message, bool allowMarkup = false) { }
- public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false) { }
+ public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false, ConsoleOutput? consoleOverride = null) { }
public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines) { }
- public void DisplayCancellationMessage() { }
+ public void DisplayCancellationMessage(ConsoleOutput? consoleOverride = null) { }
public int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, string appHostHostingVersion) => 0;
public void DisplayPlainText(string text) { }
public void DisplayRawText(string text, ConsoleOutput? consoleOverride = null) { }
diff --git a/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs b/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs
index dde56a140b3..34be3af7485 100644
--- a/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs
+++ b/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs
@@ -81,7 +81,7 @@ public void DisplayError(string errorMessage, bool allowMarkup = false)
DisplayErrorCallback?.Invoke(errorMessage);
}
- public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false)
+ public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false, ConsoleOutput? consoleOverride = null)
{
}
@@ -118,7 +118,7 @@ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lin
{
}
- public void DisplayCancellationMessage()
+ public void DisplayCancellationMessage(ConsoleOutput? consoleOverride = null)
{
}
diff --git a/tests/Aspire.Cli.Tests/TestServices/TestInteractionService.cs b/tests/Aspire.Cli.Tests/TestServices/TestInteractionService.cs
index e23f69e33a7..464d2ccb555 100644
--- a/tests/Aspire.Cli.Tests/TestServices/TestInteractionService.cs
+++ b/tests/Aspire.Cli.Tests/TestServices/TestInteractionService.cs
@@ -44,14 +44,14 @@ internal sealed class TestInteractionService : IInteractionService
public List StringPromptCalls { get; } = [];
public List BooleanPromptCalls { get; } = [];
public List DisplayedErrors { get; } = [];
- public List<(KnownEmoji Emoji, string Message)> DisplayedMessages { get; } = [];
+ public List<(KnownEmoji Emoji, string Message, ConsoleOutput? ConsoleOverride)> DisplayedMessages { get; } = [];
public List<(OutputLineStream Stream, string Line)> DisplayedLines { get; } = [];
public List DisplayedPlainText { get; } = [];
public List<(string Text, ConsoleOutput? ConsoleOverride)> DisplayedRawText { get; } = [];
public List DisplayedSuccess { get; } = [];
public List ShownStatuses { get; } = [];
public int DisplayEmptyLineCount { get; private set; }
- public int DisplayCancellationMessageCount { get; private set; }
+ public List DisplayedCancellations { get; } = [];
// Response queue setup methods
public void SetupStringPromptResponse(string response) => _responses.Enqueue((response, ResponseType.String));
@@ -199,11 +199,11 @@ public void DisplayError(string errorMessage, bool allowMarkup = false)
}
}
- public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false)
+ public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false, ConsoleOutput? consoleOverride = null)
{
lock (_displayLock)
{
- DisplayedMessages.Add((emoji, message));
+ DisplayedMessages.Add((emoji, message, consoleOverride));
}
}
@@ -223,9 +223,12 @@ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lin
}
}
- public void DisplayCancellationMessage()
+ public void DisplayCancellationMessage(ConsoleOutput? consoleOverride = null)
{
- DisplayCancellationMessageCount++;
+ lock (_displayLock)
+ {
+ DisplayedCancellations.Add(consoleOverride);
+ }
}
public Task PromptConfirmAsync(string promptText, PromptBinding? binding = null, CancellationToken cancellationToken = default)