Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit be0d630

Browse files
Better logging if an NPM task exits with an error
1 parent 54ac222 commit be0d630

File tree

4 files changed

+99
-7
lines changed

4 files changed

+99
-7
lines changed

src/Microsoft.AspNetCore.SpaServices.Extensions/AngularCli/AngularCliBuilder.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
using Microsoft.AspNetCore.Builder;
55
using Microsoft.AspNetCore.NodeServices.Npm;
6+
using Microsoft.AspNetCore.NodeServices.Util;
67
using Microsoft.AspNetCore.SpaServices.Prerendering;
78
using Microsoft.Extensions.Logging;
89
using System;
10+
using System.IO;
911
using System.Text.RegularExpressions;
1012
using System.Threading.Tasks;
1113

@@ -58,9 +60,19 @@ internal Task StartAngularCliBuilderAsync(
5860
"--watch");
5961
npmScriptRunner.AttachToLogger(logger);
6062

61-
return npmScriptRunner.StdOut.WaitForMatch(
62-
new Regex("chunk"),
63-
TimeoutMilliseconds);
63+
using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
64+
{
65+
try
66+
{
67+
return npmScriptRunner.StdOut.WaitForMatch(
68+
new Regex("chunk"),
69+
TimeoutMilliseconds);
70+
}
71+
catch (EndOfStreamException ex)
72+
{
73+
throw new InvalidOperationException($"The NPM script '{npmScriptName}' exited without indicating success. Error output was: {stdErrReader.ReadAsString()}", ex);
74+
}
75+
}
6476
}
6577
}
6678
}

src/Microsoft.AspNetCore.SpaServices.Extensions/AngularCli/AngularCliMiddleware.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
using Microsoft.Extensions.Logging.Console;
1616
using System.Net.Sockets;
1717
using System.Net;
18+
using System.IO;
19+
using Microsoft.AspNetCore.NodeServices.Util;
1820

1921
namespace Microsoft.AspNetCore.SpaServices.AngularCli
2022
{
@@ -126,9 +128,21 @@ private async Task<AngularCliServerInfo> StartAngularCliServerAsync(string npmSc
126128
_sourcePath, npmScriptName, $"--port {portNumber}");
127129
npmScriptRunner.AttachToLogger(_logger);
128130

129-
var openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch(
130-
new Regex("open your browser on (http\\S+)"),
131-
TimeoutMilliseconds);
131+
Match openBrowserLine;
132+
using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
133+
{
134+
try
135+
{
136+
openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch(
137+
new Regex("open your browser on (http\\S+)"),
138+
TimeoutMilliseconds);
139+
}
140+
catch (EndOfStreamException ex)
141+
{
142+
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);
143+
}
144+
}
145+
132146
var uri = new Uri(openBrowserLine.Groups[1].Value);
133147
var serverInfo = new AngularCliServerInfo { Port = uri.Port };
134148

src/Microsoft.AspNetCore.SpaServices.Extensions/Npm/EventedStreamReader.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using System;
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
25
using System.IO;
36
using System.Text;
47
using System.Text.RegularExpressions;
@@ -11,9 +14,11 @@ class EventedStreamReader
1114
{
1215
public delegate void OnReceivedChunkHandler(ArraySegment<char> chunk);
1316
public delegate void OnReceivedLineHandler(string line);
17+
public delegate void OnStreamClosedHandler();
1418

1519
public event OnReceivedChunkHandler OnReceivedChunk;
1620
public event OnReceivedLineHandler OnReceivedLine;
21+
public event OnStreamClosedHandler OnStreamClosed;
1722

1823
private readonly StreamReader _streamReader;
1924
private readonly StringBuilder _linesBuffer;
@@ -31,6 +36,8 @@ public Task<Match> WaitForMatch(Regex regex, int timeoutMilliseconds = 0)
3136
var completionLock = new object();
3237

3338
OnReceivedLineHandler onReceivedLineHandler = null;
39+
OnStreamClosedHandler onStreamClosedHandler = null;
40+
3441
onReceivedLineHandler = line =>
3542
{
3643
var match = regex.Match(line);
@@ -41,13 +48,28 @@ public Task<Match> WaitForMatch(Regex regex, int timeoutMilliseconds = 0)
4148
if (!tcs.Task.IsCompleted)
4249
{
4350
OnReceivedLine -= onReceivedLineHandler;
51+
OnStreamClosed -= onStreamClosedHandler;
4452
tcs.SetResult(match);
4553
}
4654
}
4755
}
4856
};
4957

58+
onStreamClosedHandler = () =>
59+
{
60+
lock (completionLock)
61+
{
62+
if (!tcs.Task.IsCompleted)
63+
{
64+
OnReceivedLine -= onReceivedLineHandler;
65+
OnStreamClosed -= onStreamClosedHandler;
66+
tcs.SetException(new EndOfStreamException());
67+
}
68+
}
69+
};
70+
5071
OnReceivedLine += onReceivedLineHandler;
72+
OnStreamClosed += onStreamClosedHandler;
5173

5274
if (timeoutMilliseconds > 0)
5375
{
@@ -59,6 +81,7 @@ public Task<Match> WaitForMatch(Regex regex, int timeoutMilliseconds = 0)
5981
if (!tcs.Task.IsCompleted)
6082
{
6183
OnReceivedLine -= onReceivedLineHandler;
84+
OnStreamClosed -= onStreamClosedHandler;
6285
tcs.SetCanceled();
6386
}
6487
}
@@ -76,6 +99,7 @@ private async Task Run()
7699
var chunkLength = await _streamReader.ReadAsync(buf, 0, buf.Length);
77100
if (chunkLength == 0)
78101
{
102+
OnClosed();
79103
break;
80104
}
81105

@@ -107,5 +131,11 @@ private void OnCompleteLine(string line)
107131
var dlg = OnReceivedLine;
108132
dlg?.Invoke(line);
109133
}
134+
135+
private void OnClosed()
136+
{
137+
var dlg = OnStreamClosed;
138+
dlg?.Invoke();
139+
}
110140
}
111141
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Text;
6+
7+
namespace Microsoft.AspNetCore.NodeServices.Util
8+
{
9+
class EventedStreamStringReader : IDisposable
10+
{
11+
private EventedStreamReader _eventedStreamReader;
12+
private bool _isDisposed;
13+
private StringBuilder _stringBuilder = new StringBuilder();
14+
15+
public EventedStreamStringReader(EventedStreamReader eventedStreamReader)
16+
{
17+
_eventedStreamReader = eventedStreamReader
18+
?? throw new ArgumentNullException(nameof(eventedStreamReader));
19+
_eventedStreamReader.OnReceivedLine += OnReceivedLine;
20+
21+
}
22+
23+
public string ReadAsString() => _stringBuilder.ToString();
24+
25+
private void OnReceivedLine(string line) => _stringBuilder.AppendLine(line);
26+
27+
public void Dispose()
28+
{
29+
if (!_isDisposed)
30+
{
31+
_eventedStreamReader.OnReceivedLine -= OnReceivedLine;
32+
_isDisposed = true;
33+
}
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)