Skip to content

Commit

Permalink
Implement workaround for Aspire dashboard launching (#46100)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcpopMSFT authored Jan 21, 2025
2 parents 4f810fb + f32eca6 commit b6240ba
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 11 deletions.
28 changes: 21 additions & 7 deletions src/BuiltInTools/dotnet-watch/Browser/BrowserConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ internal sealed partial class BrowserConnector(DotNetWatchContext context) : IAs
// This needs to be in sync with the version BrowserRefreshMiddleware is compiled against.
private static readonly Version s_minimumSupportedVersion = Versions.Version6_0;

private static readonly Regex s_nowListeningRegex = s_nowListeningOnRegex();
private static readonly Regex s_nowListeningRegex = GetNowListeningOnRegex();
private static readonly Regex s_aspireDashboardUrlRegex = GetAspireDashboardUrlRegex();

[GeneratedRegex(@"Now listening on: (?<url>.*)\s*$", RegexOptions.Compiled)]
private static partial Regex s_nowListeningOnRegex();
private static partial Regex GetNowListeningOnRegex();

[GeneratedRegex(@"Login to the dashboard at (?<url>.*)\s*$", RegexOptions.Compiled)]
private static partial Regex GetAspireDashboardUrlRegex();

private readonly object _serversGuard = new();
private readonly Dictionary<ProjectGraphNode, BrowserRefreshServer?> _servers = [];
Expand Down Expand Up @@ -115,6 +119,10 @@ public bool TryGetRefreshServer(ProjectGraphNode projectNode, [NotNullWhen(true)

bool matchFound = false;

// Workaround for Aspire dashboard launching: scan for "Login to the dashboard at " prefix in the output and use the URL.
// TODO: Share launch profile processing logic as implemented in VS with dotnet-run and implement browser launching there.
var isAspireHost = projectNode.GetCapabilities().Contains(AspireServiceFactory.AppHostProjectCapability);

return handler;

void handler(OutputLine line)
Expand All @@ -127,7 +135,7 @@ void handler(OutputLine line)
return;
}

var match = s_nowListeningRegex.Match(line.Content);
var match = (isAspireHost ? s_aspireDashboardUrlRegex : s_nowListeningRegex).Match(line.Content);
if (!match.Success)
{
return;
Expand All @@ -139,7 +147,8 @@ void handler(OutputLine line)
ImmutableInterlocked.Update(ref _browserLaunchAttempted, static (set, projectNode) => set.Add(projectNode), projectNode))
{
// first build iteration of a root project:
LaunchBrowser(launchProfile, match.Groups["url"].Value, server);
var launchUrl = GetLaunchUrl(launchProfile.LaunchUrl, match.Groups["url"].Value);
LaunchBrowser(launchUrl, server);
}
else if (server != null)
{
Expand All @@ -151,10 +160,15 @@ void handler(OutputLine line)
}
}

private void LaunchBrowser(LaunchSettingsProfile launchProfile, string launchUrl, BrowserRefreshServer? server)
public static string GetLaunchUrl(string? profileLaunchUrl, string outputLaunchUrl)
=> string.IsNullOrWhiteSpace(profileLaunchUrl) ? outputLaunchUrl :
Uri.TryCreate(profileLaunchUrl, UriKind.Absolute, out _) ? profileLaunchUrl :
Uri.TryCreate(outputLaunchUrl, UriKind.Absolute, out var launchUri) ? new Uri(launchUri, profileLaunchUrl).ToString() :
outputLaunchUrl;

private void LaunchBrowser(string launchUrl, BrowserRefreshServer? server)
{
var launchPath = launchProfile.LaunchUrl;
var fileName = Uri.TryCreate(launchPath, UriKind.Absolute, out _) ? launchPath : launchUrl + "/" + launchPath;
var fileName = launchUrl;

var args = string.Empty;
if (EnvironmentVariables.BrowserPath is { } browserPath)
Expand Down
23 changes: 23 additions & 0 deletions test/dotnet-watch.Tests/Browser/BrowserConnectorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

namespace Microsoft.DotNet.Watch.UnitTests;

public class BrowserConnectorTests
{
[Theory]
[InlineData(null, "https://localhost:1234", "https://localhost:1234")]
[InlineData("", "https://localhost:1234", "https://localhost:1234")]
[InlineData(" ", "https://localhost:1234", "https://localhost:1234")]
[InlineData("", "a/b", "a/b")]
[InlineData("x/y", "a/b", "a/b")]
[InlineData("a/b?X=1", "https://localhost:1234", "https://localhost:1234/a/b?X=1")]
[InlineData("https://localhost:1000/a/b", "https://localhost:1234", "https://localhost:1000/a/b")]
[InlineData("https://localhost:1000/x/y?z=u", "https://localhost:1234/a?b=c", "https://localhost:1000/x/y?z=u")]
public void GetLaunchUrl(string? profileLaunchUrl, string outputLaunchUrl, string expected)
{
Assert.Equal(expected, BrowserConnector.GetLaunchUrl(profileLaunchUrl, outputLaunchUrl));
}
}
4 changes: 2 additions & 2 deletions test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ public async Task BlazorWasm(bool projectSpecifiesCapabilities)
App.AssertOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser);

// Browser is launched based on blazor-devserver output "Now listening on: ...".
await App.WaitUntilOutputContains($"dotnet watch ⌚ Launching browser: http://localhost:{port}/");
await App.WaitUntilOutputContains($"dotnet watch ⌚ Launching browser: http://localhost:{port}");

// Middleware should have been loaded to blazor-devserver before the browser is launched:
App.AssertOutputContains("dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BlazorWasmHotReloadMiddleware[0]");
Expand Down Expand Up @@ -395,7 +395,7 @@ public async Task Razor_Component_ScopedCssAndStaticAssets()

App.AssertOutputContains(MessageDescriptor.ConfiguredToUseBrowserRefresh);
App.AssertOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser);
App.AssertOutputContains($"dotnet watch ⌚ Launching browser: http://localhost:{port}/");
App.AssertOutputContains($"dotnet watch ⌚ Launching browser: http://localhost:{port}");
App.Process.ClearOutput();

var scopedCssPath = Path.Combine(testAsset.Path, "RazorClassLibrary", "Components", "Example.razor.css");
Expand Down
4 changes: 2 additions & 2 deletions test/dotnet-watch.Tests/Watch/BrowserLaunchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async Task LaunchesBrowserOnStart()
Assert.Contains(App.Process.Output, line => line.Contains("Hosting environment: Development"));

// Verify we launched the browser.
Assert.Contains(App.Process.Output, line => line.Contains("dotnet watch ⌚ Launching browser: https://localhost:5001/"));
Assert.Contains(App.Process.Output, line => line.Contains("dotnet watch ⌚ Launching browser: https://localhost:5001"));
}

[Fact]
Expand All @@ -43,7 +43,7 @@ public async Task UsesBrowserSpecifiedInEnvironment()
await App.AssertOutputLineStartsWith(MessageDescriptor.ConfiguredToLaunchBrowser);

// Verify we launched the browser.
await App.AssertOutputLineStartsWith("dotnet watch ⌚ Launching browser: mycustombrowser.bat https://localhost:5001/");
await App.AssertOutputLineStartsWith("dotnet watch ⌚ Launching browser: mycustombrowser.bat https://localhost:5001");
}
}
}

0 comments on commit b6240ba

Please sign in to comment.