示例#1
0
        private static async Task <int> StartVueDevServerAsync(
            string sourcePath, string npmScriptName, ILogger logger)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting Vue dev server on port {portNumber}...");

            var npmScriptRunner = new NpmScriptRunner(
                sourcePath, npmScriptName, $"--port {portNumber} --host localhost", null);

            npmScriptRunner.AttachToLogger(logger);

            Match startDevelopmentServerLine;

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    startDevelopmentServerLine = await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("DONE", RegexOptions.None, RegexMatchTimeout));
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"vue development server was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            return(portNumber);
        }
        private static async Task <int> StartCreateVueCliAppServerAsync(
            string sourcePath, string nodeScriptName, ILogger logger, string packageManager)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting vue-cli-service server on port {portNumber}...");

            string arguments     = $"--port {portNumber}";
            var    envVars       = new Dictionary <string, string> {
            };
            var nodeScriptRunner = new NodeScriptRunner(sourcePath, nodeScriptName, arguments, envVars, packageManager);

            using (var stdErrReader = new EventedStreamStringReader(nodeScriptRunner.StdErr))
            {
                try
                {
                    // Although the dev server may eventually tell us the URL it's listening on,
                    // it doesn't do so until it's finished compiling, and even then only if there were
                    // no compiler warnings. So instead of waiting for that, consider it ready as soon
                    // as it starts listening for requests.
                    await nodeScriptRunner.StdOut.WaitForMatch(new Regex("App running at:", RegexOptions.None, RegexMatchTimeout));
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The node script '{nodeScriptName}' exited without indicating that the " +
                              $"node server was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            return(portNumber);
        }
        private static async Task <(string scheme, int port)> StartServerAsync(
            IApplicationBuilder appBuilder, string sourcePath, string npmScriptName, string packageManager, ILogger logger)
        {
            var scheme     = "http";
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting vue-cli-service server on port {portNumber}...");

            //var envVars = new Dictionary<string, string>
            //{
            //    { "PORT", portNumber.ToString() },
            //    { "BROWSER", "none" }, // We don't want vue-cli-service to open its own extra browser window pointing to the internal dev server port
            //};

            var diagnosticSource = appBuilder.ApplicationServices.GetRequiredService <DiagnosticSource>();

#if NETCOREAPP2_1 || NETCOREAPP2_2
            var applicationStoppingToken = appBuilder.ApplicationServices.GetRequiredService <IApplicationLifetime>().ApplicationStopping;
#else
            var applicationStoppingToken = appBuilder.ApplicationServices.GetRequiredService <IHostApplicationLifetime>().ApplicationStopping;
#endif
            var npmScriptRunner = new NodeScriptRunner(
                sourcePath, npmScriptName, $"--port {portNumber}", null, packageManager, diagnosticSource, applicationStoppingToken);

            npmScriptRunner.AttachToLogger(logger);

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    // Although the dev server may eventually tell us the URL it's listening on,
                    // it doesn't do so until it's finished compiling, and even then only if there were
                    // no compiler warnings. So instead of waiting for that, consider it ready as soon
                    // as it starts listening for requests.
                    await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("App running at", RegexOptions.None, RegexMatchTimeout));

                    string nextLine = await npmScriptRunner.StdOut.ReadLine();

                    if (nextLine.Contains("https://"))
                    {
                        scheme = "https";
                    }
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The {packageManager} script '{npmScriptName}' exited without indicating that the " +
                              $"vue-cli-service server was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            return(scheme, portNumber);
        }
        private static async Task <int> StartVueDevServerAsync(
            IApplicationBuilder appBuilder,
            string sourcePath,
            string npmScriptName,
            ILogger logger)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting Vue dev server on port {portNumber}...");

            // Inject address of .NET app as the ASPNET_URL env variable and read it in vue.config.js from process.env
            // as opposed to hardcoding https://localhost:5001 as the address of the dotnet web server
            // NOTE: When running with IISExpress this will be empty, so you will need to hardcode the URL in IISExpress as a fallbacl
            // when ASPNET_URL is empty
            var addresses = appBuilder.ServerFeatures.Get <IServerAddressesFeature>().Addresses;
            var envVars   = new Dictionary <string, string>
            {
                { "ASPNET_URL", addresses.Count > 0 ? addresses.First() : "" },
            };
            var npmScriptRunner = new NpmScriptRunner(
                sourcePath, npmScriptName, $"--port {portNumber} --host localhost", envVars);

            npmScriptRunner.AttachToLogger(logger);

            Match startDevelopmentServerLine;

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    // Although the Vue dev server may eventually tell us the URL it's listening on,
                    // it doesn't do so until it's finished compiling, and even then only if there were
                    // no compiler warnings. So instead of waiting for that, consider it ready as soon
                    // as it starts listening for requests.
                    //startDevelopmentServerLine = await npmScriptRunner.StdOut.WaitForMatch(
                    //    new Regex("  - Local:   (http\\S+)", RegexOptions.None, RegexMatchTimeout));
                    startDevelopmentServerLine = await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("DONE", RegexOptions.None, RegexMatchTimeout));

                    logger.LogInformation($"Vue dev server ready at http://localhost:{portNumber}");
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"vue development server was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            return(portNumber);
        }
        private static async Task <AngularCliServerInfo> StartAngularCliServerAsync(
            string sourcePath, string npmScriptName, ILogger logger)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting @angular/cli on port {portNumber}...");

            var npmScriptRunner = new NpmScriptRunner(
                sourcePath, npmScriptName, $"--port {portNumber}", null);

            npmScriptRunner.AttachToLogger(logger);

            Match openBrowserLine;

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout),
                        StartupTimeout);
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"Angular CLI was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
                catch (TaskCanceledException ex)
                {
                    throw new InvalidOperationException(
                              $"The Angular CLI process did not start listening for requests " +
                              $"within the timeout period of {StartupTimeout.Seconds} seconds. " +
                              $"Check the log output for error information.", ex);
                }
            }

            var uri        = new Uri(openBrowserLine.Groups[1].Value);
            var serverInfo = new AngularCliServerInfo {
                Port = uri.Port
            };

            // Even after the Angular CLI claims to be listening for requests, there's a short
            // period where it will give an error if you make a request too quickly
            await WaitForAngularCliServerToAcceptRequests(uri);

            return(serverInfo);
        }
        private static async Task <int> StartCreateReactAppServerAsync(
            string sourcePath, string npmScriptName, ILogger logger)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting create-react-app server on port {portNumber}...");

            var envVars = new Dictionary <string, string>
            {
                { "PORT", portNumber.ToString() },
                { "BROWSER", "none" }, // We don't want create-react-app to open its own extra browser window pointing to the internal dev server port
            };
            var npmScriptRunner = new NpmScriptRunner(
                sourcePath, npmScriptName, null, envVars);

            npmScriptRunner.AttachToLogger(logger);

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    // Although the React dev server may eventually tell us the URL it's listening on,
                    // it doesn't do so until it's finished compiling, and even then only if there were
                    // no compiler warnings. So instead of waiting for that, consider it ready as soon
                    // as it starts listening for requests.
                    await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("Starting the development server", RegexOptions.None, RegexMatchTimeout),
                        StartupTimeout);
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"create-react-app server was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
                catch (TaskCanceledException ex)
                {
                    throw new InvalidOperationException(
                              $"The create-react-app server did not start listening for requests " +
                              $"within the timeout period of {StartupTimeout.Seconds} seconds. " +
                              $"Check the log output for error information.", ex);
                }
            }

            return(portNumber);
        }
        private static async Task <int> StartWebpackDevServerAsync(
            string sourcePath, string npmScriptName, ILogger logger, int socketPortNumber)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting webpack-dev-server on port {portNumber}...");

            // Use option "--stdin" so nodejs process with webpack-dev-server stops when the webapp stops:
            // https://webpack.js.org/configuration/dev-server/#devserverstdin---cli-only
            var arguments       = $"--port {portNumber} --sockPort {socketPortNumber} --stdin";
            var envVars         = new Dictionary <string, string> {
            };
            var npmScriptRunner = new NpmScriptRunner(
                sourcePath, npmScriptName,
                arguments,
                envVars);

            npmScriptRunner.AttachToLogger(logger);

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    // Although the webpack-dev-server may eventually tell us the URL it's listening on,
                    // it doesn't do so until it's finished compiling, and even then only if there were
                    // no compiler warnings. So instead of waiting for that, consider it ready as soon
                    // as it starts listening for requests.
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
                    await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("Project is running at",
                                  RegexOptions.None, RegexMatchTimeout));

#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"webpack-development server was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            return(portNumber);
        }
        private static async Task <AngularCliServerInfo> StartAngularCliServerAsync(
            string sourcePath, string scriptName, string pkgManagerCommand, int portNumber, ILogger logger)
        {
            if (portNumber == default(int))
            {
                portNumber = TcpPortFinder.FindAvailablePort();
            }
            logger.LogInformation($"Starting @angular/cli on port {portNumber}...");

            var scriptRunner = new NodeScriptRunner(
                sourcePath, scriptName, $"--port {portNumber}", null, pkgManagerCommand);

            scriptRunner.AttachToLogger(logger);

            Match openBrowserLine;

            using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr))
            {
                try
                {
                    openBrowserLine = await scriptRunner.StdOut.WaitForMatch(
                        new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout));
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The {pkgManagerCommand} script '{scriptName}' exited without indicating that the " +
                              $"Angular CLI was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            var uri        = new Uri(openBrowserLine.Groups[1].Value);
            var serverInfo = new AngularCliServerInfo {
                Port = uri.Port
            };

            // Even after the Angular CLI claims to be listening for requests, there's a short
            // period where it will give an error if you make a request too quickly
            await WaitForAngularCliServerToAcceptRequests(uri);

            return(serverInfo);
        }
        private static async Task <int> StartCreateVueAppServerAsync(
            string sourcePath, string scriptName, string pkgManagerCommand, int portNumber, ILogger logger, DiagnosticSource diagnosticSource, CancellationToken applicationStoppingToken)
        {
            if (portNumber == default)
            {
                portNumber = TcpPortFinder.FindAvailablePort();
            }

            logger.LogInformation($"Starting vue-cli server on port {portNumber}...");

            var envVars = new Dictionary <string, string>
            {
                { "PORT", portNumber.ToString() },
                { "BROWSER", "none" }, // We don't want vue-cli to open its own extra browser window pointing to the internal dev server port
            };
            var scriptRunner = new NodeScriptRunner(
                sourcePath, scriptName, null, envVars, pkgManagerCommand, diagnosticSource, applicationStoppingToken);

            scriptRunner.AttachToLogger(logger);

            using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr))
            {
                try
                {
                    // Although the Vue dev server may eventually tell us the URL it's listening on,
                    // it doesn't do so until it's finished compiling, and even then only if there were
                    // no compiler warnings. So instead of waiting for that, consider it ready as soon
                    // as it starts listening for requests.
                    await scriptRunner.StdOut.WaitForMatch(
                        new Regex(CLI_SERVE_COMPLETION_REGEX, RegexOptions.None, RegexMatchTimeout));
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The {pkgManagerCommand} script '{scriptName}' exited without indicating that the " +
                              $"vue-cli server was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            return(portNumber);
        }
        private static async Task <AureliaCliServerInfo> StartAureliaCliServerAsync(
            string sourcePath, string npmScriptName, ILogger logger)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting aurelia-cli on port {portNumber}...");

            var npmScriptRunner = new NpmScriptRunner(
                sourcePath, npmScriptName, $" --port {portNumber}", null);

            npmScriptRunner.AttachToLogger(logger);

            Match openBrowserLine;

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex(".*Compiled.*successfully.*", RegexOptions.None, RegexMatchTimeout))
                                      .ConfigureAwait(false);
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"Aurelia CLI was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            var uri        = new Uri($"http://localhost:{portNumber}");//openBrowserLine.Groups[1].Value);
            var serverInfo = new AureliaCliServerInfo {
                Port = uri.Port
            };

            // Even after the Aurelia CLI claims to be listening for requests, there's a short
            // period where it will give an error if you make a request too quickly
            await WaitForAureliaCliServerToAcceptRequests(uri).ConfigureAwait(false);

            return(serverInfo);
        }
        private static async Task <VueCliServerInfo> StartVueCliServerAsync(
            string sourcePath, string npmScriptName, ILogger logger)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting @Vue/cli on port {portNumber}...");

            var npmScriptRunner = new NpmScriptRunner(
                sourcePath, npmScriptName, $"--port {portNumber}", null);

            npmScriptRunner.AttachToLogger(logger);

            Match openBrowserLine;

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("  - Local:   (http\\S+)", RegexOptions.None, RegexMatchTimeout));
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"Vue CLI was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            var uri        = new Uri(openBrowserLine.Groups[1].Value);
            var serverInfo = new VueCliServerInfo {
                Port = uri.Port, Host = uri.Host, Scheme = uri.Scheme
            };

            // Even after the Vue CLI claims to be listening for requests, there may be a short
            // period where it will give an error if you make a request too quickly
            await WaitForVueCliServerToAcceptRequests(uri);

            return(serverInfo);
        }
示例#12
0
        private static async Task <CliServerInfo> StartBlazorWasmCliServerAsync(string sourcePath, string startScript,
                                                                                ILogger logger)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting Blazor Wasm Cli on port {portNumber}...");

            var scriptRunner = new DotnetScriptRunner(
                sourcePath, startScript, $"--urls=\"http://localhost:{portNumber}\"", null);

            scriptRunner.AttachToLogger(logger);

            Match openBrowserLine;

            using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr))
            {
                try
                {
                    openBrowserLine = await scriptRunner.StdOut.WaitForMatch(new Regex("Now listening on: (http\\S+)",
                                                                                       RegexOptions.None, RegexMatchTimeout));
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{startScript}' exited without indicating that the " +
                              $"Angular CLI was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            var uri        = new Uri(openBrowserLine.Groups[1].Value);
            var serverInfo = new CliServerInfo {
                Port = uri.Port, PublicPath = uri.AbsolutePath.TrimEnd('/')
            };

            // Even after the Angular CLI claims to be listening for requests, there's a short
            // period where it will give an error if you make a request too quickly
            await WaitForCliServerToAcceptRequests(uri);

            return(serverInfo);
        }
        private static async Task <int> StartVueCliServerAsync(
            string sourcePath, string npmScriptName, ILogger logger, int portNumber)
        {
            if (portNumber < 80)
            {
                portNumber = TcpPortFinder.FindAvailablePort();
            }
            logger.LogInformation($"Starting server on port {portNumber}...");

            var envVars = new Dictionary <string, string>
            {
                { "PORT", portNumber.ToString() },
                { "DEV_SERVER_PORT", portNumber.ToString() }, // vue cli 3 uses --port {number}, included below
                { "BROWSER", "none" },                        // We don't want vue-cli to open its own extra browser window pointing to the internal dev server port
            };
            var npmScriptRunner = new NpmScriptRunner(sourcePath, npmScriptName, $"--port {portNumber}", envVars);

            npmScriptRunner.AttachToLogger(logger);

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    // Although the Vue dev server may eventually tell us the URL it's listening on,
                    // it doesn't do so until it's finished compiling, and even then only if there were
                    // no compiler warnings. So instead of waiting for that, consider it ready as soon
                    // as it starts listening for requests.
                    await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("running at", RegexOptions.None, RegexMatchTimeout));
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"server was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            return(portNumber);
        }
        private static async Task <int> StartCreateVueAppServerAsync(
            string sourcePath, string npmExe, string npmScriptName, ILogger logger)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting create-vue-app server on port {portNumber}...");

            var envVars = new Dictionary <string, string>
            {
                { "PORT", portNumber.ToString() },
                { "BROWSER", "none" }, // We don't want create-vue-app to open its own extra browser window pointing to the internal dev server port
            };
            //var arguments = $"--port={portNumber}";
            var npmScriptRunner = new NpmScriptRunner(
                sourcePath, npmExe, npmScriptName, null, envVars);

            npmScriptRunner.AttachToLogger(logger);

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    // 尽管vue dev服务器最终可能会告诉我们它监听的URL,
                    // 在编译完成之前,它不会这样做,即使在没有编译器警告的情况下也不会这样做。
                    // 所以与其等着,还不如在它开始倾听请求时尽快准备好。
                    await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("App running at", RegexOptions.None, RegexMatchTimeout));
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"create-vue-app server was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            return(portNumber);
        }
        private static async Task <int> StartVueDevServerAsync(
            string sourcePath, string npmScriptName, ILogger logger)
        {
            var portNumber = TcpPortFinder.FindAvailablePort();

            logger.LogInformation($"Starting Vue dev server on port {portNumber}...");

            var npmScriptRunner = new NpmScriptRunner(
                sourcePath, npmScriptName, $"--port {portNumber} --host localhost", null);

            npmScriptRunner.AttachToLogger(logger);

            Match startDevelopmentServerLine;

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    // Although the Vue dev server may eventually tell us the URL it's listening on,
                    // it doesn't do so until it's finished compiling, and even then only if there were
                    // no compiler warnings. So instead of waiting for that, consider it ready as soon
                    // as it starts listening for requests.
                    //startDevelopmentServerLine = await npmScriptRunner.StdOut.WaitForMatch(
                    //    new Regex("  - Local:   (http\\S+)", RegexOptions.None, RegexMatchTimeout));
                    startDevelopmentServerLine = await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("DONE", RegexOptions.None, RegexMatchTimeout));
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"vue development server was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            return(portNumber);
        }
示例#16
0
        private static async Task <VueCliServerInfo> StartVueCliServerAsync(
            string sourcePath, string npmScriptName, ILogger logger)
        {
            var portNumber = 8080;//default port for vue cli: 8080

            if (portNumber < 80)
            {
                portNumber = TcpPortFinder.FindAvailablePort();
            }
            else
            {
                // if the port we want to use is occupied, terminate the process utilizing that port.
                // this occurs when "stop" is used from the debugger and the middleware does not have the opportunity to kill the process
                PidUtils.KillPort((ushort)portNumber);
            }

            logger.LogInformation($"Starting @Vue/cli on port {portNumber}...");

            var envVars = new Dictionary <string, string>
            {
                { "PORT", portNumber.ToString() },
                { "DEV_SERVER_PORT", portNumber.ToString() }, // vue cli 3 uses --port {number}, included below
                { "BROWSER", "none" },                        // We don't want vue-cli to open its own extra browser window pointing to the internal dev server port
            };

            var npmScriptRunner = new NpmScriptRunner(
                sourcePath, npmScriptName, $"--port {portNumber}", envVars);

            AppDomain.CurrentDomain.DomainUnload       += (s, e) => npmScriptRunner?.Kill();
            AppDomain.CurrentDomain.ProcessExit        += (s, e) => npmScriptRunner?.Kill();
            AppDomain.CurrentDomain.UnhandledException += (s, e) => npmScriptRunner?.Kill();
            npmScriptRunner.AttachToLogger(logger);

            Match openBrowserLine;

            using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
            {
                try
                {
                    openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch(
                        new Regex("  - Local:   (http\\S+)", RegexOptions.None, RegexMatchTimeout));
                }
                catch (EndOfStreamException ex)
                {
                    throw new InvalidOperationException(
                              $"The NPM script '{npmScriptName}' exited without indicating that the " +
                              $"Vue CLI was listening for requests. The error output was: " +
                              $"{stdErrReader.ReadAsString()}", ex);
                }
            }

            var uri        = new Uri(openBrowserLine.Groups[1].Value);
            var serverInfo = new VueCliServerInfo {
                Port = uri.Port, Host = uri.Host, Scheme = uri.Scheme
            };

            // Even after the Vue CLI claims to be listening for requests, there may be a short
            // period where it will give an error if you make a request too quickly
            await WaitForVueCliServerToAcceptRequests(uri);

            return(serverInfo);
        }