Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,7 @@ internal static IResourceBuilder<T> WithVSCodeDebugging<T>(this IResourceBuilder
/// <summary>
/// Configures a browser debugger for the JavaScript application resource, enabling browser-based debugging
/// through a child resource that launches when the parent application is ready.
/// This resource relies on IDE support for browser debugging and will not be added if the required IDE capability is missing.
/// </summary>
Comment thread
adamint marked this conversation as resolved.
/// <typeparam name="T">The type of the JavaScript application resource.</typeparam>
/// <param name="builder">The resource builder for the JavaScript application.</param>
Expand All @@ -1016,8 +1017,8 @@ internal static IResourceBuilder<T> WithVSCodeDebugging<T>(this IResourceBuilder
/// The parent resource must have at least one HTTP or HTTPS endpoint configured.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown when the parent resource does not have an HTTP or HTTPS endpoint, or when the IDE extension
/// does not support browser debugging.
/// Thrown when the parent resource does not have an HTTP or HTTPS endpoint, or when the IDE capability
/// information is present but does not include browser debugging support.
/// </exception>
/// <example>
/// Add browser debugging to a JavaScript application:
Expand All @@ -1037,7 +1038,10 @@ public static IResourceBuilder<T> WithBrowserDebugger<T>(
ArgumentNullException.ThrowIfNull(builder);

// Validate that the extension supports browser debugging if we're running in an extension context
ValidateBrowserCapability(builder);
if (!HasBrowserCapability(builder))
{
return builder;
}

var parentResource = builder.Resource;
var debuggerResourceName = $"{parentResource.Name}-browser";
Expand Down Expand Up @@ -1080,24 +1084,36 @@ public static IResourceBuilder<T> WithBrowserDebugger<T>(
return builder;
}

private static void ValidateBrowserCapability<T>(IResourceBuilder<T> builder) where T : IResource
private static bool HasBrowserCapability<T>(IResourceBuilder<T> builder) where T : IResource
{
var configuration = builder.ApplicationBuilder.Configuration;

try
{
if (configuration["DEBUG_SESSION_INFO"] is { } debugSessionInfoJson
&& JsonSerializer.Deserialize<DebugSessionCapabilities>(debugSessionInfoJson) is { } info
&& info.SupportedLaunchConfigurations is not null
&& !info.SupportedLaunchConfigurations.Contains(BrowserCapability))
var debugSessionInfoJson = configuration[KnownConfigNames.DebugSessionInfo];
if (debugSessionInfoJson is null)
{
return false;
}

if (JsonSerializer.Deserialize<DebugSessionCapabilities>(debugSessionInfoJson) is not { } info)
{
// If we can't deserialize the capabilities object, skip validation
return false;
}

if (info.SupportedLaunchConfigurations is null || !info.SupportedLaunchConfigurations.Contains(BrowserCapability))
{
throw new InvalidOperationException(
"This version of the Aspire extension does not support browser debugging. Please update the Aspire extension to use browser debugging support with WithBrowserDebugger().");
}

return true;
}
catch (JsonException)
{
// If we can't parse the debug session info, skip validation
return false;
}
}

Expand Down
80 changes: 80 additions & 0 deletions tests/Aspire.Hosting.JavaScript.Tests/AddNodeAppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

using System.Reflection;
using Aspire.Hosting.ApplicationModel;
using System.Text.Json;
using Aspire.Hosting.Dcp.Model;
using Aspire.Hosting.Tests.Utils;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -474,6 +476,12 @@ public void ViteApp_WithBrowserDebugger_CreatesChildResource()
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
using var tempDir = new TestTempDirectory();

builder.Configuration["DEBUG_SESSION_INFO"] = JsonSerializer.Serialize(new RunSessionInfo
{
ProtocolsSupported = ["test"],
SupportedLaunchConfigurations = ["browser"]
});

var viteApp = builder.AddViteApp("viteapp", tempDir.Path)
.WithBrowserDebugger();

Expand Down Expand Up @@ -501,6 +509,12 @@ public void ViteApp_WithBrowserDebugger_DefaultsToEdgeBrowser()
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
using var tempDir = new TestTempDirectory();

builder.Configuration["DEBUG_SESSION_INFO"] = JsonSerializer.Serialize(new RunSessionInfo
{
ProtocolsSupported = ["test"],
SupportedLaunchConfigurations = ["browser"]
});

var viteApp = builder.AddViteApp("viteapp", tempDir.Path)
.WithBrowserDebugger();

Expand All @@ -518,6 +532,12 @@ public void ViteApp_WithBrowserDebugger_UsesSpecifiedBrowser()
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
using var tempDir = new TestTempDirectory();

builder.Configuration["DEBUG_SESSION_INFO"] = JsonSerializer.Serialize(new RunSessionInfo
{
ProtocolsSupported = ["test"],
SupportedLaunchConfigurations = ["browser"]
});

var viteApp = builder.AddViteApp("viteapp", tempDir.Path)
.WithBrowserDebugger(browser: "chrome");

Expand All @@ -534,6 +554,12 @@ public void ViteApp_WithBrowserDebugger_WithoutEndpoint_DeferredValidation()
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
using var tempDir = new TestTempDirectory();

builder.Configuration["DEBUG_SESSION_INFO"] = JsonSerializer.Serialize(new RunSessionInfo
{
ProtocolsSupported = ["test"],
SupportedLaunchConfigurations = ["browser"]
});

// Create a minimal JavaScriptAppResource without endpoints
var resource = new JavaScriptAppResource("jsapp", "npm", tempDir.Path);
var jsApp = builder.AddResource(resource);
Expand All @@ -550,6 +576,60 @@ public void ViteApp_WithBrowserDebugger_WithoutEndpoint_DeferredValidation()
Assert.NotNull(browserDebuggerResource);
}

[Fact]
public void ViteApp_WithBrowserDebugger_NoOps_WhenDebugSessionInfoNotSet()
{
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
using var tempDir = new TestTempDirectory();

// Do not set DEBUG_SESSION_INFO — HasBrowserCapability should return false
var viteApp = builder.AddViteApp("viteapp", tempDir.Path)
.WithBrowserDebugger();

using var app = builder.Build();
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

// No browser debugger resource should be created
var browserDebuggerResource = appModel.Resources.OfType<BrowserDebuggerResource>().SingleOrDefault();
Assert.Null(browserDebuggerResource);
}

[Fact]
public void ViteApp_WithBrowserDebugger_Throws_WhenBrowserCapabilityNotListed()
{
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
using var tempDir = new TestTempDirectory();

// Set DEBUG_SESSION_INFO without "browser" in supported configurations
builder.Configuration["DEBUG_SESSION_INFO"] = JsonSerializer.Serialize(new RunSessionInfo
{
ProtocolsSupported = ["test"],
SupportedLaunchConfigurations = ["node"]
});

var viteApp = builder.AddViteApp("viteapp", tempDir.Path);

Assert.Throws<InvalidOperationException>(() => viteApp.WithBrowserDebugger());
}

[Fact]
public void ViteApp_WithBrowserDebugger_Throws_WhenSupportedLaunchConfigurationsOmitted()
{
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
using var tempDir = new TestTempDirectory();

// Set DEBUG_SESSION_INFO without supported_launch_configurations property
builder.Configuration["DEBUG_SESSION_INFO"] = JsonSerializer.Serialize(new RunSessionInfo
{
ProtocolsSupported = ["test"],
SupportedLaunchConfigurations = null
});

var viteApp = builder.AddViteApp("viteapp", tempDir.Path);

Assert.Throws<InvalidOperationException>(() => viteApp.WithBrowserDebugger());
}

[Fact]
public async Task WithReferenceDispatchesNodeAppServiceReference()
{
Expand Down
Loading