File: Process\WebServerProcessStateObserver.cs
Web Access
Project: ..\..\..\src\BuiltInTools\dotnet-watch\dotnet-watch.csproj (dotnet-watch)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Text.RegularExpressions;
using Microsoft.Build.Graph;
 
namespace Microsoft.DotNet.Watch;
 
/// <summary>
/// Observes the state of the web server by scanning its standard output for known patterns.
/// Notifies when the server starts listening.
/// </summary>
internal static partial class WebServerProcessStateObserver
{
    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 GetNowListeningOnRegex();
 
    [GeneratedRegex(@"Login to the dashboard at (?<url>.*)\s*$", RegexOptions.Compiled)]
    private static partial Regex GetAspireDashboardUrlRegex();
 
    public static void Observe(ProjectGraphNode serverProject, ProcessSpec serverProcessSpec, Action<string> onServerListening)
    {
        // Workaround for Aspire dashboard launching: scan for "Login to the dashboard at " prefix in the output and use the URL.
        // TODO: https://github.com/dotnet/sdk/issues/9038
        // Share launch profile processing logic as implemented in VS with dotnet-run and implement browser launching there.
        bool isAspireHost = serverProject.GetCapabilities().Contains(AspireServiceFactory.AppHostProjectCapability);
 
        var _notified = false;
 
        serverProcessSpec.OnOutput += line =>
        {
            if (_notified)
            {
                return;
            }
 
            var match = (isAspireHost ? s_aspireDashboardUrlRegex : s_nowListeningRegex).Match(line.Content);
            if (!match.Success)
            {
                return;
            }
 
            _notified = true;
            onServerListening(match.Groups["url"].Value);
        };
    }
}