From b679379ffe793839fdb3c130083d92024c400856 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:15:04 +0000 Subject: [PATCH 1/3] Initial plan From c5cdc4b2cf751660cdb515bf0931499ffbe546d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:20:25 +0000 Subject: [PATCH 2/3] Hide aspire setup command when bundle is not available Conditionally add SetupCommand to the CLI command tree only when IBundleService.IsBundle is true, following the same pattern used for ExecCommand and SdkCommand feature-flag gating. Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> --- src/Aspire.Cli/Commands/RootCommand.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Aspire.Cli/Commands/RootCommand.cs b/src/Aspire.Cli/Commands/RootCommand.cs index db956046a9a..6126c8d8761 100644 --- a/src/Aspire.Cli/Commands/RootCommand.cs +++ b/src/Aspire.Cli/Commands/RootCommand.cs @@ -10,6 +10,7 @@ using System.Diagnostics; #endif +using Aspire.Cli.Bundles; using Aspire.Cli.Commands.Sdk; using Aspire.Cli.Configuration; using Aspire.Cli.Interaction; @@ -131,6 +132,7 @@ public RootCommand( SdkCommand sdkCommand, SetupCommand setupCommand, ExtensionInternalCommand extensionInternalCommand, + IBundleService bundleService, IFeatures featureFlags, IInteractionService interactionService) : base(RootCommandStrings.Description) @@ -208,7 +210,11 @@ public RootCommand( Subcommands.Add(agentCommand); Subcommands.Add(telemetryCommand); Subcommands.Add(docsCommand); - Subcommands.Add(setupCommand); + + if (bundleService.IsBundle) + { + Subcommands.Add(setupCommand); + } if (featureFlags.IsFeatureEnabled(KnownFeatures.ExecCommandEnabled, false)) { From cb3fac6766e8cef512172c98b9283fb88da4f5d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:28:02 +0000 Subject: [PATCH 3/3] Add tests for setup command visibility based on bundle availability Add two tests to RootCommandTests: - SetupCommand_NotAvailable_WhenBundleIsNotAvailable: verifies setup command is hidden when the CLI has no embedded bundle - SetupCommand_Available_WhenBundleIsAvailable: verifies setup command is visible when the CLI has an embedded bundle Also add BundleServiceFactory to CliServiceCollectionTestOptions and TestBundleService to support overriding bundle behavior in tests. Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com> --- .../Commands/RootCommandTests.cs | 29 +++++++++++++++++++ tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs | 21 +++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/tests/Aspire.Cli.Tests/Commands/RootCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/RootCommandTests.cs index c58ec07c433..5a11b6da189 100644 --- a/tests/Aspire.Cli.Tests/Commands/RootCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/RootCommandTests.cs @@ -344,4 +344,33 @@ public async Task InformationalFlag_DoesNotCreateSentinel_OnSubsequentFirstRun() Assert.True(sentinel.WasCreated); } + [Fact] + public void SetupCommand_NotAvailable_WhenBundleIsNotAvailable() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper); + var provider = services.BuildServiceProvider(); + + var command = provider.GetRequiredService(); + var hasSetupCommand = command.Subcommands.Any(cmd => cmd.Name == "setup"); + + Assert.False(hasSetupCommand); + } + + [Fact] + public void SetupCommand_Available_WhenBundleIsAvailable() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options => + { + options.BundleServiceFactory = _ => new TestBundleService(isBundle: true); + }); + var provider = services.BuildServiceProvider(); + + var command = provider.GetRequiredService(); + var hasSetupCommand = command.Subcommands.Any(cmd => cmd.Name == "setup"); + + Assert.True(hasSetupCommand); + } + } diff --git a/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs b/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs index 5ef18fe61ad..7b746e42e66 100644 --- a/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs +++ b/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs @@ -130,7 +130,7 @@ public static IServiceCollection CreateServiceCollection(TemporaryWorkspace work // Bundle layout services - return null/no-op implementations to trigger SDK mode fallback // This ensures backward compatibility: no layout found = use legacy SDK mode services.AddSingleton(options.LayoutDiscoveryFactory); - services.AddSingleton(); + services.AddSingleton(options.BundleServiceFactory); services.AddSingleton(); // AppHost project handlers - must match Program.cs registration pattern @@ -501,6 +501,9 @@ public ISolutionLocator CreateDefaultSolutionLocatorFactory(IServiceProvider ser // Layout discovery - returns null by default (no bundle layout), causing SDK mode fallback public Func LayoutDiscoveryFactory { get; set; } = _ => new NullLayoutDiscovery(); + // Bundle service - returns no-op implementation by default (no embedded bundle) + public Func BundleServiceFactory { get; set; } = _ => new NullBundleService(); + public Func McpServerTransportFactory { get; set; } = (IServiceProvider serviceProvider) => { var loggerFactory = serviceProvider.GetService(); @@ -553,6 +556,22 @@ public Task ExtractAsync(string destinationPath, bool force => Task.FromResult(null); } +/// +/// A configurable bundle service for testing bundle-dependent behavior. +/// +internal sealed class TestBundleService(bool isBundle) : IBundleService +{ + public bool IsBundle => isBundle; + + public Task EnsureExtractedAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; + + public Task ExtractAsync(string destinationPath, bool force = false, CancellationToken cancellationToken = default) + => Task.FromResult(isBundle ? BundleExtractResult.AlreadyUpToDate : BundleExtractResult.NoPayload); + + public Task EnsureExtractedAndGetLayoutAsync(CancellationToken cancellationToken = default) + => Task.FromResult(null); +} + internal sealed class TestOutputTextWriter : TextWriter { private readonly ITestOutputHelper _outputHelper;