Merge pull request #12775 from Reptarsrage/master
Adding optional PackageManagerName Option to use alternative Package Managers like Yarn
This commit is contained in:
commit
27996712af
|
|
@ -38,6 +38,7 @@ namespace Microsoft.AspNetCore.SpaServices
|
||||||
public SpaOptions() { }
|
public SpaOptions() { }
|
||||||
public Microsoft.AspNetCore.Http.PathString DefaultPage { get { throw null; } set { } }
|
public Microsoft.AspNetCore.Http.PathString DefaultPage { get { throw null; } set { } }
|
||||||
public Microsoft.AspNetCore.Builder.StaticFileOptions DefaultPageStaticFileOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public Microsoft.AspNetCore.Builder.StaticFileOptions DefaultPageStaticFileOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
|
public string PackageManagerCommand { get { throw null; } set { } }
|
||||||
public string SourcePath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public string SourcePath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
public System.TimeSpan StartupTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public System.TimeSpan StartupTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
|
||||||
{
|
{
|
||||||
private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine
|
private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine
|
||||||
|
|
||||||
private readonly string _npmScriptName;
|
private readonly string _scriptName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs an instance of <see cref="AngularCliBuilder"/>.
|
/// Constructs an instance of <see cref="AngularCliBuilder"/>.
|
||||||
|
|
@ -34,12 +34,13 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
|
||||||
throw new ArgumentException("Cannot be null or empty.", nameof(npmScript));
|
throw new ArgumentException("Cannot be null or empty.", nameof(npmScript));
|
||||||
}
|
}
|
||||||
|
|
||||||
_npmScriptName = npmScript;
|
_scriptName = npmScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task Build(ISpaBuilder spaBuilder)
|
public async Task Build(ISpaBuilder spaBuilder)
|
||||||
{
|
{
|
||||||
|
var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand;
|
||||||
var sourcePath = spaBuilder.Options.SourcePath;
|
var sourcePath = spaBuilder.Options.SourcePath;
|
||||||
if (string.IsNullOrEmpty(sourcePath))
|
if (string.IsNullOrEmpty(sourcePath))
|
||||||
{
|
{
|
||||||
|
|
@ -49,32 +50,33 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
|
||||||
var logger = LoggerFinder.GetOrCreateLogger(
|
var logger = LoggerFinder.GetOrCreateLogger(
|
||||||
spaBuilder.ApplicationBuilder,
|
spaBuilder.ApplicationBuilder,
|
||||||
nameof(AngularCliBuilder));
|
nameof(AngularCliBuilder));
|
||||||
var npmScriptRunner = new NpmScriptRunner(
|
var scriptRunner = new NodeScriptRunner(
|
||||||
sourcePath,
|
sourcePath,
|
||||||
_npmScriptName,
|
_scriptName,
|
||||||
"--watch",
|
"--watch",
|
||||||
null);
|
null,
|
||||||
npmScriptRunner.AttachToLogger(logger);
|
pkgManagerCommand);
|
||||||
|
scriptRunner.AttachToLogger(logger);
|
||||||
|
|
||||||
using (var stdOutReader = new EventedStreamStringReader(npmScriptRunner.StdOut))
|
using (var stdOutReader = new EventedStreamStringReader(scriptRunner.StdOut))
|
||||||
using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
|
using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await npmScriptRunner.StdOut.WaitForMatch(
|
await scriptRunner.StdOut.WaitForMatch(
|
||||||
new Regex("Date", RegexOptions.None, RegexMatchTimeout));
|
new Regex("Date", RegexOptions.None, RegexMatchTimeout));
|
||||||
}
|
}
|
||||||
catch (EndOfStreamException ex)
|
catch (EndOfStreamException ex)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"The NPM script '{_npmScriptName}' exited without indicating success.\n" +
|
$"The {pkgManagerCommand} script '{_scriptName}' exited without indicating success.\n" +
|
||||||
$"Output was: {stdOutReader.ReadAsString()}\n" +
|
$"Output was: {stdOutReader.ReadAsString()}\n" +
|
||||||
$"Error output was: {stdErrReader.ReadAsString()}", ex);
|
$"Error output was: {stdErrReader.ReadAsString()}", ex);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException ex)
|
catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"The NPM script '{_npmScriptName}' timed out without indicating success. " +
|
$"The {pkgManagerCommand} script '{_scriptName}' timed out without indicating success. " +
|
||||||
$"Output was: {stdOutReader.ReadAsString()}\n" +
|
$"Output was: {stdOutReader.ReadAsString()}\n" +
|
||||||
$"Error output was: {stdErrReader.ReadAsString()}", ex);
|
$"Error output was: {stdErrReader.ReadAsString()}", ex);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,23 +23,24 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
|
||||||
|
|
||||||
public static void Attach(
|
public static void Attach(
|
||||||
ISpaBuilder spaBuilder,
|
ISpaBuilder spaBuilder,
|
||||||
string npmScriptName)
|
string scriptName)
|
||||||
{
|
{
|
||||||
|
var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand;
|
||||||
var sourcePath = spaBuilder.Options.SourcePath;
|
var sourcePath = spaBuilder.Options.SourcePath;
|
||||||
if (string.IsNullOrEmpty(sourcePath))
|
if (string.IsNullOrEmpty(sourcePath))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Cannot be null or empty", nameof(sourcePath));
|
throw new ArgumentException("Cannot be null or empty", nameof(sourcePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(npmScriptName))
|
if (string.IsNullOrEmpty(scriptName))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName));
|
throw new ArgumentException("Cannot be null or empty", nameof(scriptName));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start Angular CLI and attach to middleware pipeline
|
// Start Angular CLI and attach to middleware pipeline
|
||||||
var appBuilder = spaBuilder.ApplicationBuilder;
|
var appBuilder = spaBuilder.ApplicationBuilder;
|
||||||
var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName);
|
var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName);
|
||||||
var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, npmScriptName, logger);
|
var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, scriptName, pkgManagerCommand, logger);
|
||||||
|
|
||||||
// Everything we proxy is hardcoded to target http://localhost because:
|
// Everything we proxy is hardcoded to target http://localhost because:
|
||||||
// - the requests are always from the local machine (we're not accepting remote
|
// - the requests are always from the local machine (we're not accepting remote
|
||||||
|
|
@ -62,27 +63,27 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<AngularCliServerInfo> StartAngularCliServerAsync(
|
private static async Task<AngularCliServerInfo> StartAngularCliServerAsync(
|
||||||
string sourcePath, string npmScriptName, ILogger logger)
|
string sourcePath, string scriptName, string pkgManagerCommand, ILogger logger)
|
||||||
{
|
{
|
||||||
var portNumber = TcpPortFinder.FindAvailablePort();
|
var portNumber = TcpPortFinder.FindAvailablePort();
|
||||||
logger.LogInformation($"Starting @angular/cli on port {portNumber}...");
|
logger.LogInformation($"Starting @angular/cli on port {portNumber}...");
|
||||||
|
|
||||||
var npmScriptRunner = new NpmScriptRunner(
|
var scriptRunner = new NodeScriptRunner(
|
||||||
sourcePath, npmScriptName, $"--port {portNumber}", null);
|
sourcePath, scriptName, $"--port {portNumber}", null, pkgManagerCommand);
|
||||||
npmScriptRunner.AttachToLogger(logger);
|
scriptRunner.AttachToLogger(logger);
|
||||||
|
|
||||||
Match openBrowserLine;
|
Match openBrowserLine;
|
||||||
using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
|
using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch(
|
openBrowserLine = await scriptRunner.StdOut.WaitForMatch(
|
||||||
new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout));
|
new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout));
|
||||||
}
|
}
|
||||||
catch (EndOfStreamException ex)
|
catch (EndOfStreamException ex)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"The NPM script '{npmScriptName}' exited without indicating that the " +
|
$"The {pkgManagerCommand} script '{scriptName}' exited without indicating that the " +
|
||||||
$"Angular CLI was listening for requests. The error output was: " +
|
$"Angular CLI was listening for requests. The error output was: " +
|
||||||
$"{stdErrReader.ReadAsString()}", ex);
|
$"{stdErrReader.ReadAsString()}", ex);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -16,14 +16,14 @@ namespace Microsoft.AspNetCore.NodeServices.Npm
|
||||||
/// Executes the <c>script</c> entries defined in a <c>package.json</c> file,
|
/// Executes the <c>script</c> entries defined in a <c>package.json</c> file,
|
||||||
/// capturing any output written to stdio.
|
/// capturing any output written to stdio.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class NpmScriptRunner
|
internal class NodeScriptRunner
|
||||||
{
|
{
|
||||||
public EventedStreamReader StdOut { get; }
|
public EventedStreamReader StdOut { get; }
|
||||||
public EventedStreamReader StdErr { get; }
|
public EventedStreamReader StdErr { get; }
|
||||||
|
|
||||||
private static Regex AnsiColorRegex = new Regex("\x001b\\[[0-9;]*m", RegexOptions.None, TimeSpan.FromSeconds(1));
|
private static Regex AnsiColorRegex = new Regex("\x001b\\[[0-9;]*m", RegexOptions.None, TimeSpan.FromSeconds(1));
|
||||||
|
|
||||||
public NpmScriptRunner(string workingDirectory, string scriptName, string arguments, IDictionary<string, string> envVars)
|
public NodeScriptRunner(string workingDirectory, string scriptName, string arguments, IDictionary<string, string> envVars, string pkgManagerCommand)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(workingDirectory))
|
if (string.IsNullOrEmpty(workingDirectory))
|
||||||
{
|
{
|
||||||
|
|
@ -35,18 +35,23 @@ namespace Microsoft.AspNetCore.NodeServices.Npm
|
||||||
throw new ArgumentException("Cannot be null or empty.", nameof(scriptName));
|
throw new ArgumentException("Cannot be null or empty.", nameof(scriptName));
|
||||||
}
|
}
|
||||||
|
|
||||||
var npmExe = "npm";
|
if (string.IsNullOrEmpty(pkgManagerCommand))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot be null or empty.", nameof(pkgManagerCommand));
|
||||||
|
}
|
||||||
|
|
||||||
|
var exeToRun = pkgManagerCommand;
|
||||||
var completeArguments = $"run {scriptName} -- {arguments ?? string.Empty}";
|
var completeArguments = $"run {scriptName} -- {arguments ?? string.Empty}";
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
// On Windows, the NPM executable is a .cmd file, so it can't be executed
|
// On Windows, the node executable is a .cmd file, so it can't be executed
|
||||||
// directly (except with UseShellExecute=true, but that's no good, because
|
// directly (except with UseShellExecute=true, but that's no good, because
|
||||||
// it prevents capturing stdio). So we need to invoke it via "cmd /c".
|
// it prevents capturing stdio). So we need to invoke it via "cmd /c".
|
||||||
npmExe = "cmd";
|
exeToRun = "cmd";
|
||||||
completeArguments = $"/c npm {completeArguments}";
|
completeArguments = $"/c {pkgManagerCommand} {completeArguments}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var processStartInfo = new ProcessStartInfo(npmExe)
|
var processStartInfo = new ProcessStartInfo(exeToRun)
|
||||||
{
|
{
|
||||||
Arguments = completeArguments,
|
Arguments = completeArguments,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
|
|
@ -64,19 +69,19 @@ namespace Microsoft.AspNetCore.NodeServices.Npm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = LaunchNodeProcess(processStartInfo);
|
var process = LaunchNodeProcess(processStartInfo, pkgManagerCommand);
|
||||||
StdOut = new EventedStreamReader(process.StandardOutput);
|
StdOut = new EventedStreamReader(process.StandardOutput);
|
||||||
StdErr = new EventedStreamReader(process.StandardError);
|
StdErr = new EventedStreamReader(process.StandardError);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AttachToLogger(ILogger logger)
|
public void AttachToLogger(ILogger logger)
|
||||||
{
|
{
|
||||||
// When the NPM task emits complete lines, pass them through to the real logger
|
// When the node task emits complete lines, pass them through to the real logger
|
||||||
StdOut.OnReceivedLine += line =>
|
StdOut.OnReceivedLine += line =>
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(line))
|
if (!string.IsNullOrWhiteSpace(line))
|
||||||
{
|
{
|
||||||
// NPM tasks commonly emit ANSI colors, but it wouldn't make sense to forward
|
// Node tasks commonly emit ANSI colors, but it wouldn't make sense to forward
|
||||||
// those to loggers (because a logger isn't necessarily any kind of terminal)
|
// those to loggers (because a logger isn't necessarily any kind of terminal)
|
||||||
logger.LogInformation(StripAnsiColors(line));
|
logger.LogInformation(StripAnsiColors(line));
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +111,7 @@ namespace Microsoft.AspNetCore.NodeServices.Npm
|
||||||
private static string StripAnsiColors(string line)
|
private static string StripAnsiColors(string line)
|
||||||
=> AnsiColorRegex.Replace(line, string.Empty);
|
=> AnsiColorRegex.Replace(line, string.Empty);
|
||||||
|
|
||||||
private static Process LaunchNodeProcess(ProcessStartInfo startInfo)
|
private static Process LaunchNodeProcess(ProcessStartInfo startInfo, string commandName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -119,8 +124,8 @@ namespace Microsoft.AspNetCore.NodeServices.Npm
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var message = $"Failed to start 'npm'. To resolve this:.\n\n"
|
var message = $"Failed to start '{commandName}'. To resolve this:.\n\n"
|
||||||
+ "[1] Ensure that 'npm' is installed and can be found in one of the PATH directories.\n"
|
+ $"[1] Ensure that '{commandName}' is installed and can be found in one of the PATH directories.\n"
|
||||||
+ $" Current PATH enviroment variable is: { Environment.GetEnvironmentVariable("PATH") }\n"
|
+ $" Current PATH enviroment variable is: { Environment.GetEnvironmentVariable("PATH") }\n"
|
||||||
+ " Make sure the executable is in one of those directories, or update your PATH.\n\n"
|
+ " Make sure the executable is in one of those directories, or update your PATH.\n\n"
|
||||||
+ "[2] See the InnerException for further details of the cause.";
|
+ "[2] See the InnerException for further details of the cause.";
|
||||||
|
|
@ -22,23 +22,24 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
|
||||||
|
|
||||||
public static void Attach(
|
public static void Attach(
|
||||||
ISpaBuilder spaBuilder,
|
ISpaBuilder spaBuilder,
|
||||||
string npmScriptName)
|
string scriptName)
|
||||||
{
|
{
|
||||||
|
var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand;
|
||||||
var sourcePath = spaBuilder.Options.SourcePath;
|
var sourcePath = spaBuilder.Options.SourcePath;
|
||||||
if (string.IsNullOrEmpty(sourcePath))
|
if (string.IsNullOrEmpty(sourcePath))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Cannot be null or empty", nameof(sourcePath));
|
throw new ArgumentException("Cannot be null or empty", nameof(sourcePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(npmScriptName))
|
if (string.IsNullOrEmpty(scriptName))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName));
|
throw new ArgumentException("Cannot be null or empty", nameof(scriptName));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start create-react-app and attach to middleware pipeline
|
// Start create-react-app and attach to middleware pipeline
|
||||||
var appBuilder = spaBuilder.ApplicationBuilder;
|
var appBuilder = spaBuilder.ApplicationBuilder;
|
||||||
var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName);
|
var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName);
|
||||||
var portTask = StartCreateReactAppServerAsync(sourcePath, npmScriptName, logger);
|
var portTask = StartCreateReactAppServerAsync(sourcePath, scriptName, pkgManagerCommand, logger);
|
||||||
|
|
||||||
// Everything we proxy is hardcoded to target http://localhost because:
|
// Everything we proxy is hardcoded to target http://localhost because:
|
||||||
// - the requests are always from the local machine (we're not accepting remote
|
// - the requests are always from the local machine (we're not accepting remote
|
||||||
|
|
@ -61,7 +62,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<int> StartCreateReactAppServerAsync(
|
private static async Task<int> StartCreateReactAppServerAsync(
|
||||||
string sourcePath, string npmScriptName, ILogger logger)
|
string sourcePath, string scriptName, string pkgManagerCommand, ILogger logger)
|
||||||
{
|
{
|
||||||
var portNumber = TcpPortFinder.FindAvailablePort();
|
var portNumber = TcpPortFinder.FindAvailablePort();
|
||||||
logger.LogInformation($"Starting create-react-app server on port {portNumber}...");
|
logger.LogInformation($"Starting create-react-app server on port {portNumber}...");
|
||||||
|
|
@ -71,11 +72,11 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
|
||||||
{ "PORT", portNumber.ToString() },
|
{ "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
|
{ "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(
|
var scriptRunner = new NodeScriptRunner(
|
||||||
sourcePath, npmScriptName, null, envVars);
|
sourcePath, scriptName, null, envVars, pkgManagerCommand);
|
||||||
npmScriptRunner.AttachToLogger(logger);
|
scriptRunner.AttachToLogger(logger);
|
||||||
|
|
||||||
using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
|
using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -83,13 +84,13 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer
|
||||||
// it doesn't do so until it's finished compiling, and even then only if there were
|
// 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
|
// no compiler warnings. So instead of waiting for that, consider it ready as soon
|
||||||
// as it starts listening for requests.
|
// as it starts listening for requests.
|
||||||
await npmScriptRunner.StdOut.WaitForMatch(
|
await scriptRunner.StdOut.WaitForMatch(
|
||||||
new Regex("Starting the development server", RegexOptions.None, RegexMatchTimeout));
|
new Regex("Starting the development server", RegexOptions.None, RegexMatchTimeout));
|
||||||
}
|
}
|
||||||
catch (EndOfStreamException ex)
|
catch (EndOfStreamException ex)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"The NPM script '{npmScriptName}' exited without indicating that the " +
|
$"The {pkgManagerCommand} script '{scriptName}' exited without indicating that the " +
|
||||||
$"create-react-app server was listening for requests. The error output was: " +
|
$"create-react-app server was listening for requests. The error output was: " +
|
||||||
$"{stdErrReader.ReadAsString()}", ex);
|
$"{stdErrReader.ReadAsString()}", ex);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SpaServices
|
namespace Microsoft.AspNetCore.SpaServices
|
||||||
{
|
{
|
||||||
|
|
@ -15,6 +14,7 @@ namespace Microsoft.AspNetCore.SpaServices
|
||||||
public class SpaOptions
|
public class SpaOptions
|
||||||
{
|
{
|
||||||
private PathString _defaultPage = "/index.html";
|
private PathString _defaultPage = "/index.html";
|
||||||
|
private string _packageManagerCommand = "npm";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new instance of <see cref="SpaOptions"/>.
|
/// Constructs a new instance of <see cref="SpaOptions"/>.
|
||||||
|
|
@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.SpaServices
|
||||||
internal SpaOptions(SpaOptions copyFromOptions)
|
internal SpaOptions(SpaOptions copyFromOptions)
|
||||||
{
|
{
|
||||||
_defaultPage = copyFromOptions.DefaultPage;
|
_defaultPage = copyFromOptions.DefaultPage;
|
||||||
|
_packageManagerCommand = copyFromOptions.PackageManagerCommand;
|
||||||
DefaultPageStaticFileOptions = copyFromOptions.DefaultPageStaticFileOptions;
|
DefaultPageStaticFileOptions = copyFromOptions.DefaultPageStaticFileOptions;
|
||||||
SourcePath = copyFromOptions.SourcePath;
|
SourcePath = copyFromOptions.SourcePath;
|
||||||
}
|
}
|
||||||
|
|
@ -69,6 +70,26 @@ namespace Microsoft.AspNetCore.SpaServices
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SourcePath { get; set; }
|
public string SourcePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the package manager executible, (e.g npm,
|
||||||
|
/// yarn) to run the SPA.
|
||||||
|
///
|
||||||
|
/// The default value is 'npm'.
|
||||||
|
/// </summary>
|
||||||
|
public string PackageManagerCommand
|
||||||
|
{
|
||||||
|
get => _packageManagerCommand;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"The value for {nameof(PackageManagerCommand)} cannot be null or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_packageManagerCommand = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the maximum duration that a request will wait for the SPA
|
/// Gets or sets the maximum duration that a request will wait for the SPA
|
||||||
/// to become ready to serve to the client.
|
/// to become ready to serve to the client.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue