diff --git a/src/Aspire.Cli/Backchannel/AppHostConnectionResolver.cs b/src/Aspire.Cli/Backchannel/AppHostConnectionResolver.cs index 0f7169bcf6a..5ba0564a766 100644 --- a/src/Aspire.Cli/Backchannel/AppHostConnectionResolver.cs +++ b/src/Aspire.Cli/Backchannel/AppHostConnectionResolver.cs @@ -43,6 +43,7 @@ internal sealed class AppHostConnectionResolver( IInteractionService interactionService, IProjectLocator projectLocator, CliExecutionContext executionContext, + ICliHostEnvironment hostEnvironment, ILogger logger, ProfilingTelemetry? profilingTelemetry = null) { @@ -199,6 +200,17 @@ public async Task ResolveConnectionAsync( } else if (inScopeConnections.Count > 1) { + if (!hostEnvironment.SupportsInteractiveInput) + { + // Can't prompt the user to pick an AppHost in non-interactive mode; + // fail with an actionable message instead of letting the prompt throw. + return new AppHostConnectionResult + { + ErrorMessage = SharedCommandStrings.MultipleAppHostsNonInteractive, + ExitCode = CliExitCodes.FailedToFindProject, + }; + } + selectedConnection = await PromptForAppHostSelectionAsync( inScopeConnections, SharedCommandStrings.MultipleInScopeAppHosts, @@ -208,6 +220,18 @@ public async Task ResolveConnectionAsync( } else if (outOfScopeConnections.Count > 0) { + if (!hostEnvironment.SupportsInteractiveInput) + { + // No in-scope AppHosts, and selecting from out-of-scope AppHosts requires + // a prompt. In non-interactive mode treat this as "not found" so scripts + // get a clean error and exit code instead of an unexpected prompt failure. + return new AppHostConnectionResult + { + ErrorMessage = notFoundMessage, + ExitCode = CliExitCodes.FailedToFindProject, + }; + } + selectedConnection = await PromptForAppHostSelectionAsync( outOfScopeConnections, SharedCommandStrings.NoInScopeAppHostsShowingAll, diff --git a/src/Aspire.Cli/Commands/CommonCommandServices.cs b/src/Aspire.Cli/Commands/CommonCommandServices.cs index d6ace2b5688..1386d062127 100644 --- a/src/Aspire.Cli/Commands/CommonCommandServices.cs +++ b/src/Aspire.Cli/Commands/CommonCommandServices.cs @@ -16,7 +16,8 @@ internal sealed class CommonCommandServices( IInteractionService interactionService, AspireCliTelemetry telemetry, ConsoleCancellationManager cancellationManager, - ILoggerFactory loggerFactory) + ILoggerFactory loggerFactory, + ICliHostEnvironment hostEnvironment) { public IFeatures Features { get; } = features; public ICliUpdateNotifier UpdateNotifier { get; } = updateNotifier; @@ -25,4 +26,5 @@ internal sealed class CommonCommandServices( public AspireCliTelemetry Telemetry { get; } = telemetry; public ConsoleCancellationManager CancellationManager { get; } = cancellationManager; public ILoggerFactory LoggerFactory { get; } = loggerFactory; + public ICliHostEnvironment HostEnvironment { get; } = hostEnvironment; } diff --git a/src/Aspire.Cli/Commands/DescribeCommand.cs b/src/Aspire.Cli/Commands/DescribeCommand.cs index 1e485d2bc36..c110493b869 100644 --- a/src/Aspire.Cli/Commands/DescribeCommand.cs +++ b/src/Aspire.Cli/Commands/DescribeCommand.cs @@ -111,7 +111,7 @@ public DescribeCommand( { Aliases.Add("resources"); _resourceColorMap = resourceColorMap; - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger); Arguments.Add(s_resourceArgument); Options.Add(s_appHostOption); diff --git a/src/Aspire.Cli/Commands/ExportCommand.cs b/src/Aspire.Cli/Commands/ExportCommand.cs index 49e3c3ea3d4..a95cca0c695 100644 --- a/src/Aspire.Cli/Commands/ExportCommand.cs +++ b/src/Aspire.Cli/Commands/ExportCommand.cs @@ -61,7 +61,7 @@ public ExportCommand( _httpClientFactory = httpClientFactory; _timeProvider = timeProvider; _logger = logger; - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger); Arguments.Add(s_resourceArgument); Options.Add(s_appHostOption); diff --git a/src/Aspire.Cli/Commands/LogsCommand.cs b/src/Aspire.Cli/Commands/LogsCommand.cs index fbfe25065f3..0afbd06be67 100644 --- a/src/Aspire.Cli/Commands/LogsCommand.cs +++ b/src/Aspire.Cli/Commands/LogsCommand.cs @@ -128,7 +128,7 @@ public LogsCommand( _resourceColorMap = resourceColorMap; _hostEnvironment = hostEnvironment; _logger = logger; - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger, profilingTelemetry); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger, profilingTelemetry); Arguments.Add(s_resourceArgument); Options.Add(s_appHostOption); diff --git a/src/Aspire.Cli/Commands/McpCallCommand.cs b/src/Aspire.Cli/Commands/McpCallCommand.cs index 72a81ef2a78..e56545ddc7d 100644 --- a/src/Aspire.Cli/Commands/McpCallCommand.cs +++ b/src/Aspire.Cli/Commands/McpCallCommand.cs @@ -45,7 +45,7 @@ public McpCallCommand( CommonCommandServices services) : base("call", McpCommandStrings.CallCommand_Description, services) { - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger); Arguments.Add(s_resourceArgument); Arguments.Add(s_toolArgument); diff --git a/src/Aspire.Cli/Commands/McpToolsCommand.cs b/src/Aspire.Cli/Commands/McpToolsCommand.cs index a2dc878021d..ba06f99c87c 100644 --- a/src/Aspire.Cli/Commands/McpToolsCommand.cs +++ b/src/Aspire.Cli/Commands/McpToolsCommand.cs @@ -35,7 +35,7 @@ public McpToolsCommand( CommonCommandServices services) : base("tools", McpCommandStrings.ToolsCommand_Description, services) { - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger); Options.Add(s_appHostOption); Options.Add(s_formatOption); diff --git a/src/Aspire.Cli/Commands/ResourceCommand.cs b/src/Aspire.Cli/Commands/ResourceCommand.cs index 56ab577e5c8..e460d689078 100644 --- a/src/Aspire.Cli/Commands/ResourceCommand.cs +++ b/src/Aspire.Cli/Commands/ResourceCommand.cs @@ -82,7 +82,7 @@ public ResourceCommand( { _backchannelMonitor = backchannelMonitor; _projectLocator = projectLocator; - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger); _logger = logger; Arguments.Add(s_resourceArgument); diff --git a/src/Aspire.Cli/Commands/StopCommand.cs b/src/Aspire.Cli/Commands/StopCommand.cs index 3e4fca9766f..c059050a4d0 100644 --- a/src/Aspire.Cli/Commands/StopCommand.cs +++ b/src/Aspire.Cli/Commands/StopCommand.cs @@ -44,7 +44,7 @@ public StopCommand( CommonCommandServices services) : base("stop", StopCommandStrings.Description, services) { - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, interactionService, projectLocator, services.ExecutionContext, logger, profilingTelemetry); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, interactionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger, profilingTelemetry); _hostEnvironment = hostEnvironment; _processShutdownService = processShutdownService; _logger = logger; diff --git a/src/Aspire.Cli/Commands/TelemetryLogsCommand.cs b/src/Aspire.Cli/Commands/TelemetryLogsCommand.cs index ff479fa5872..37d2ce2e0c0 100644 --- a/src/Aspire.Cli/Commands/TelemetryLogsCommand.cs +++ b/src/Aspire.Cli/Commands/TelemetryLogsCommand.cs @@ -64,7 +64,7 @@ public TelemetryLogsCommand( _resourceColorMap = resourceColorMap; _timeProvider = timeProvider; _logger = logger; - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, interactionService, projectLocator, services.ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, interactionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger); Arguments.Add(s_resourceArgument); Options.Add(s_appHostOption); diff --git a/src/Aspire.Cli/Commands/TelemetrySpansCommand.cs b/src/Aspire.Cli/Commands/TelemetrySpansCommand.cs index 485bb0186a2..546ff7986ee 100644 --- a/src/Aspire.Cli/Commands/TelemetrySpansCommand.cs +++ b/src/Aspire.Cli/Commands/TelemetrySpansCommand.cs @@ -54,7 +54,7 @@ public TelemetrySpansCommand( _resourceColorMap = resourceColorMap; _timeProvider = timeProvider; _logger = logger; - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger); Arguments.Add(s_resourceArgument); Options.Add(s_appHostOption); diff --git a/src/Aspire.Cli/Commands/TelemetryTracesCommand.cs b/src/Aspire.Cli/Commands/TelemetryTracesCommand.cs index eeacb139b17..8aa6fddb027 100644 --- a/src/Aspire.Cli/Commands/TelemetryTracesCommand.cs +++ b/src/Aspire.Cli/Commands/TelemetryTracesCommand.cs @@ -54,7 +54,7 @@ public TelemetryTracesCommand( _resourceColorMap = resourceColorMap; _timeProvider = timeProvider; _logger = logger; - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger); Arguments.Add(s_resourceArgument); Options.Add(s_appHostOption); diff --git a/src/Aspire.Cli/Commands/TerminalAttachCommand.cs b/src/Aspire.Cli/Commands/TerminalAttachCommand.cs index 09cde305f72..cf8cd8adb78 100644 --- a/src/Aspire.Cli/Commands/TerminalAttachCommand.cs +++ b/src/Aspire.Cli/Commands/TerminalAttachCommand.cs @@ -66,7 +66,7 @@ public TerminalAttachCommand( { _interactionService = services.InteractionService; _logger = logger; - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, services.InteractionService, projectLocator, services.ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, services.InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger); Arguments.Add(s_resourceArgument); Options.Add(s_appHostOption); diff --git a/src/Aspire.Cli/Commands/TerminalPsCommand.cs b/src/Aspire.Cli/Commands/TerminalPsCommand.cs index ec3cd4cd5a8..84b57dd8f66 100644 --- a/src/Aspire.Cli/Commands/TerminalPsCommand.cs +++ b/src/Aspire.Cli/Commands/TerminalPsCommand.cs @@ -63,7 +63,7 @@ public TerminalPsCommand( { _interactionService = services.InteractionService; _logger = logger; - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, services.InteractionService, projectLocator, services.ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, services.InteractionService, projectLocator, services.ExecutionContext, services.HostEnvironment, logger); Options.Add(s_appHostOption); Options.Add(s_formatOption); diff --git a/src/Aspire.Cli/Commands/WaitCommand.cs b/src/Aspire.Cli/Commands/WaitCommand.cs index a49dac9fea5..d74ff101d6c 100644 --- a/src/Aspire.Cli/Commands/WaitCommand.cs +++ b/src/Aspire.Cli/Commands/WaitCommand.cs @@ -47,7 +47,7 @@ public WaitCommand( TimeProvider? timeProvider = null) : base("wait", WaitCommandStrings.Description, services) { - _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, ExecutionContext, logger); + _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, InteractionService, projectLocator, ExecutionContext, services.HostEnvironment, logger); _logger = logger; _timeProvider = timeProvider ?? TimeProvider.System; diff --git a/src/Aspire.Cli/Resources/SharedCommandStrings.Designer.cs b/src/Aspire.Cli/Resources/SharedCommandStrings.Designer.cs index b6dd19c5d01..00f08ab79e5 100644 --- a/src/Aspire.Cli/Resources/SharedCommandStrings.Designer.cs +++ b/src/Aspire.Cli/Resources/SharedCommandStrings.Designer.cs @@ -171,6 +171,15 @@ internal static string MultipleInScopeAppHosts { } } + /// + /// Looks up a localized string similar to Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use.. + /// + internal static string MultipleAppHostsNonInteractive { + get { + return ResourceManager.GetString("MultipleAppHostsNonInteractive", resourceCulture); + } + } + internal static string PromptRunAgentInit { get { return ResourceManager.GetString("PromptRunAgentInit", resourceCulture); diff --git a/src/Aspire.Cli/Resources/SharedCommandStrings.resx b/src/Aspire.Cli/Resources/SharedCommandStrings.resx index ab8f423998e..807cf3dc81d 100644 --- a/src/Aspire.Cli/Resources/SharedCommandStrings.resx +++ b/src/Aspire.Cli/Resources/SharedCommandStrings.resx @@ -182,6 +182,9 @@ Multiple running AppHosts found in the current directory. Select from running AppHosts. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Would you like to configure AI agent environments for this project? diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.cs.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.cs.xlf index c8a32b43bd6..f4bb6d65c26 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.cs.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.cs.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.de.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.de.xlf index 5e434376353..062ad0d41b0 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.de.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.de.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.es.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.es.xlf index bf202fc6715..7a0d5e50167 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.es.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.es.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.fr.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.fr.xlf index 6ab0951b25a..82e237fe28c 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.fr.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.fr.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.it.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.it.xlf index 9269f235e7e..e0134099555 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.it.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.it.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ja.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ja.xlf index 1d67deacc53..c26cf19472d 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ja.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ja.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ko.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ko.xlf index e602e9cfa91..e2a514f3ccb 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ko.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ko.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.pl.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.pl.xlf index c7ce26d905e..9b42baa8b8e 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.pl.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.pl.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.pt-BR.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.pt-BR.xlf index a4077bf74bd..3e11258ddfe 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.pt-BR.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.pt-BR.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ru.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ru.xlf index 092d0677494..4a9433742f0 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ru.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ru.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.tr.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.tr.xlf index a5e5b3b9052..7ca9d8a463c 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.tr.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.tr.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.zh-Hans.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.zh-Hans.xlf index 6ed4ab62cb5..d503c3cc7ba 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.zh-Hans.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.zh-Hans.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.zh-Hant.xlf b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.zh-Hant.xlf index 0d9c9855ef2..6e810d089ac 100644 --- a/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.zh-Hant.xlf +++ b/src/Aspire.Cli/Resources/xlf/SharedCommandStrings.zh-Hant.xlf @@ -82,6 +82,11 @@ The --stream option requires --format json. + + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + Multiple running AppHosts were found, but the CLI is running in non-interactive mode. Pass --apphost to specify which AppHost to use. + + Multiple running AppHosts found in the current directory. Select from running AppHosts. Multiple running AppHosts found in the current directory. Select from running AppHosts. diff --git a/tests/Aspire.Cli.Tests/Backchannel/AppHostConnectionResolverTests.cs b/tests/Aspire.Cli.Tests/Backchannel/AppHostConnectionResolverTests.cs index 3b6ed2b2a90..539966f6a47 100644 --- a/tests/Aspire.Cli.Tests/Backchannel/AppHostConnectionResolverTests.cs +++ b/tests/Aspire.Cli.Tests/Backchannel/AppHostConnectionResolverTests.cs @@ -35,6 +35,7 @@ public async Task ResolveConnectionAsync_WithExplicitProjectFile_PreservesFastPa interactionService, projectLocator, executionContext, + TestHelpers.CreateInteractiveHostEnvironment(), NullLogger.Instance); var result = await resolver.ResolveConnectionAsync( @@ -64,6 +65,7 @@ public async Task ResolveConnectionAsync_WithExplicitProjectFile_DeletesDeadPidS new TestInteractionService(), new TestProjectLocator(), executionContext, + TestHelpers.CreateInteractiveHostEnvironment(), NullLogger.Instance); var result = await resolver.ResolveConnectionAsync( @@ -109,6 +111,7 @@ public async Task ResolveConnectionAsync_WithExplicitDirectoryAndMultipleAppHost interactionService, projectLocator, executionContext, + TestHelpers.CreateInteractiveHostEnvironment(), NullLogger.Instance); var result = await resolver.ResolveConnectionAsync( @@ -141,6 +144,7 @@ public async Task ResolveConnectionAsync_WithExplicitDirectoryAndNoAppHosts_Retu interactionService, projectLocator, executionContext, + TestHelpers.CreateInteractiveHostEnvironment(), NullLogger.Instance); var result = await resolver.ResolveConnectionAsync( @@ -156,6 +160,65 @@ public async Task ResolveConnectionAsync_WithExplicitDirectoryAndNoAppHosts_Retu Assert.Equal(InteractionServiceStrings.ProjectOptionSpecifiedDirectoryContainsNoAppHosts, result.ErrorMessage); } + [Fact] + public async Task ResolveConnectionAsync_NonInteractiveWithOnlyOutOfScopeAppHosts_ReturnsNotFoundError() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var executionContext = CreateExecutionContext(workspace.WorkspaceRoot); + var monitor = new TestAuxiliaryBackchannelMonitor(); + monitor.AddConnection("hash1", "socket-other", new TestAppHostAuxiliaryBackchannel { IsInScope = false }); + + var resolver = new AppHostConnectionResolver( + monitor, + new TestInteractionService(), + new TestProjectLocator(), + executionContext, + TestHelpers.CreateNonInteractiveHostEnvironment(), + NullLogger.Instance); + + var result = await resolver.ResolveConnectionAsync( + projectFile: null, + "Scanning", + "Select", + SharedCommandStrings.AppHostNotRunning, + TestContext.Current.CancellationToken); + + Assert.False(result.Success); + Assert.True(result.IsProjectResolutionError); + Assert.Equal(SharedCommandStrings.AppHostNotRunning, result.ErrorMessage); + Assert.Equal(CliExitCodes.FailedToFindProject, result.ExitCode); + } + + [Fact] + public async Task ResolveConnectionAsync_NonInteractiveWithMultipleInScopeAppHosts_ReturnsActionableError() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var executionContext = CreateExecutionContext(workspace.WorkspaceRoot); + var monitor = new TestAuxiliaryBackchannelMonitor(); + monitor.AddConnection("hash1", "socket-one", new TestAppHostAuxiliaryBackchannel { IsInScope = true }); + monitor.AddConnection("hash2", "socket-two", new TestAppHostAuxiliaryBackchannel { IsInScope = true }); + + var resolver = new AppHostConnectionResolver( + monitor, + new TestInteractionService(), + new TestProjectLocator(), + executionContext, + TestHelpers.CreateNonInteractiveHostEnvironment(), + NullLogger.Instance); + + var result = await resolver.ResolveConnectionAsync( + projectFile: null, + "Scanning", + "Select", + SharedCommandStrings.AppHostNotRunning, + TestContext.Current.CancellationToken); + + Assert.False(result.Success); + Assert.True(result.IsProjectResolutionError); + Assert.Equal(SharedCommandStrings.MultipleAppHostsNonInteractive, result.ErrorMessage); + Assert.Equal(CliExitCodes.FailedToFindProject, result.ExitCode); + } + private static CliExecutionContext CreateExecutionContext(DirectoryInfo workingDirectory) { return TestExecutionContextHelper.CreateExecutionContext( diff --git a/tests/Aspire.Cli.Tests/NuGet/NuGetPackagePrefetcherTests.cs b/tests/Aspire.Cli.Tests/NuGet/NuGetPackagePrefetcherTests.cs index 42e81e676fe..af5190fcd3a 100644 --- a/tests/Aspire.Cli.Tests/NuGet/NuGetPackagePrefetcherTests.cs +++ b/tests/Aspire.Cli.Tests/NuGet/NuGetPackagePrefetcherTests.cs @@ -227,7 +227,7 @@ private static bool IsRuntimeOnlyCommand(BaseCommand command) // Test command implementations internal sealed class TestCommand : BaseCommand { - public TestCommand(string name = "test") : base(name, "Test command", new CommonCommandServices(null!, null!, null!, null!, null!, null!, null!)) + public TestCommand(string name = "test") : base(name, "Test command", new CommonCommandServices(null!, null!, null!, null!, null!, null!, null!, null!)) { } @@ -239,7 +239,7 @@ protected override Task ExecuteAsync(ParseResult parseResult, Can internal sealed class TestCommandWithInterface : BaseCommand, IPackageMetaPrefetchingCommand { - public TestCommandWithInterface() : base("test-interface", "Test command with interface", new CommonCommandServices(null!, null!, null!, null!, null!, null!, null!)) + public TestCommandWithInterface() : base("test-interface", "Test command with interface", new CommonCommandServices(null!, null!, null!, null!, null!, null!, null!, null!)) { }