ava 0.16.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api.js +297 -265
- package/cli.js +15 -179
- package/index.js +5 -98
- package/index.js.flow +201 -0
- package/lib/assert.js +87 -53
- package/lib/ava-error.js +4 -8
- package/lib/ava-files.js +282 -0
- package/lib/babel-config.js +35 -73
- package/lib/beautify-stack.js +17 -16
- package/lib/caching-precompiler.js +72 -87
- package/lib/cli.js +181 -0
- package/lib/code-excerpt.js +57 -0
- package/lib/colors.js +6 -2
- package/lib/concurrent.js +62 -75
- package/lib/enhance-assert.js +57 -49
- package/lib/extract-stack.js +10 -0
- package/lib/fork.js +67 -68
- package/lib/format-assert-error.js +72 -0
- package/lib/globals.js +3 -8
- package/lib/hook.js +15 -20
- package/lib/logger.js +59 -82
- package/lib/main.js +90 -0
- package/lib/prefix-title.js +21 -0
- package/lib/process-adapter.js +108 -0
- package/lib/reporters/mini.js +260 -257
- package/lib/reporters/tap.js +80 -85
- package/lib/reporters/verbose.js +142 -115
- package/lib/run-status.js +110 -152
- package/lib/runner.js +125 -137
- package/lib/sequence.js +68 -84
- package/lib/serialize-error.js +68 -4
- package/lib/snapshot-state.js +30 -0
- package/lib/test-collection.js +144 -156
- package/lib/test-worker.js +45 -95
- package/lib/test.js +289 -318
- package/lib/throws-helper.js +9 -9
- package/lib/validate-test.js +48 -0
- package/lib/watcher.js +258 -297
- package/package.json +63 -53
- package/profile.js +68 -55
- package/readme.md +215 -101
- package/types/generated.d.ts +848 -228
- package/types/make.js +54 -23
- package/lib/send.js +0 -16
package/lib/reporters/mini.js
CHANGED
|
@@ -1,308 +1,311 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Object.keys(colors).forEach(function (key) {
|
|
16
|
-
colors[key].enabled = true;
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
function MiniReporter(options) {
|
|
20
|
-
if (!(this instanceof MiniReporter)) {
|
|
21
|
-
return new MiniReporter(options);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
var spinnerDef = spinners[process.platform === 'win32' ? 'line' : 'dots'];
|
|
25
|
-
this.spinnerFrames = spinnerDef.frames.map(function (c) {
|
|
26
|
-
return chalk.gray.dim(c);
|
|
27
|
-
});
|
|
28
|
-
this.spinnerInterval = spinnerDef.interval;
|
|
29
|
-
|
|
30
|
-
this.options = objectAssign({}, options);
|
|
2
|
+
const StringDecoder = require('string_decoder').StringDecoder;
|
|
3
|
+
const cliCursor = require('cli-cursor');
|
|
4
|
+
const lastLineTracker = require('last-line-stream/tracker');
|
|
5
|
+
const plur = require('plur');
|
|
6
|
+
const spinners = require('cli-spinners');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const cliTruncate = require('cli-truncate');
|
|
9
|
+
const cross = require('figures').cross;
|
|
10
|
+
const indentString = require('indent-string');
|
|
11
|
+
const formatAssertError = require('../format-assert-error');
|
|
12
|
+
const extractStack = require('../extract-stack');
|
|
13
|
+
const codeExcerpt = require('../code-excerpt');
|
|
14
|
+
const colors = require('../colors');
|
|
31
15
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
// TODO(@jamestalamge): This should be fixed in log-update and ansi-escapes once we are confident it's a good solution.
|
|
17
|
+
const CSI = '\u001b[';
|
|
18
|
+
const ERASE_LINE = CSI + '2K';
|
|
19
|
+
const CURSOR_TO_COLUMN_0 = CSI + '0G';
|
|
20
|
+
const CURSOR_UP = CSI + '1A';
|
|
36
21
|
|
|
37
|
-
|
|
22
|
+
// Returns a string that will erase `count` lines from the end of the terminal.
|
|
23
|
+
function eraseLines(count) {
|
|
24
|
+
let clear = '';
|
|
38
25
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.interval = setInterval(function () {
|
|
43
|
-
self.spinnerIndex = (self.spinnerIndex + 1) % self.spinnerFrames.length;
|
|
44
|
-
self.write(self.prefix());
|
|
45
|
-
}, this.spinnerInterval);
|
|
46
|
-
|
|
47
|
-
return this.prefix('');
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
MiniReporter.prototype.reset = function () {
|
|
51
|
-
this.clearInterval();
|
|
52
|
-
this.passCount = 0;
|
|
53
|
-
this.knownFailureCount = 0;
|
|
54
|
-
this.failCount = 0;
|
|
55
|
-
this.skipCount = 0;
|
|
56
|
-
this.todoCount = 0;
|
|
57
|
-
this.rejectionCount = 0;
|
|
58
|
-
this.exceptionCount = 0;
|
|
59
|
-
this.currentStatus = '';
|
|
60
|
-
this.currentTest = '';
|
|
61
|
-
this.statusLineCount = 0;
|
|
62
|
-
this.spinnerIndex = 0;
|
|
63
|
-
this.lastLineTracker = lastLineTracker();
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
MiniReporter.prototype.spinnerChar = function () {
|
|
67
|
-
return this.spinnerFrames[this.spinnerIndex];
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
MiniReporter.prototype.clearInterval = function () {
|
|
71
|
-
clearInterval(this.interval);
|
|
72
|
-
this.interval = null;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
MiniReporter.prototype.test = function (test) {
|
|
76
|
-
if (test.todo) {
|
|
77
|
-
this.todoCount++;
|
|
78
|
-
} else if (test.skip) {
|
|
79
|
-
this.skipCount++;
|
|
80
|
-
} else if (test.error) {
|
|
81
|
-
this.failCount++;
|
|
82
|
-
} else {
|
|
83
|
-
this.passCount++;
|
|
84
|
-
if (test.failing) {
|
|
85
|
-
this.knownFailureCount++;
|
|
86
|
-
}
|
|
26
|
+
for (let i = 0; i < count; i++) {
|
|
27
|
+
clear += ERASE_LINE + (i < count - 1 ? CURSOR_UP : '');
|
|
87
28
|
}
|
|
88
29
|
|
|
89
|
-
if (
|
|
90
|
-
|
|
30
|
+
if (count) {
|
|
31
|
+
clear += CURSOR_TO_COLUMN_0;
|
|
91
32
|
}
|
|
92
33
|
|
|
93
|
-
return
|
|
94
|
-
}
|
|
34
|
+
return clear;
|
|
35
|
+
}
|
|
95
36
|
|
|
96
|
-
MiniReporter
|
|
97
|
-
|
|
98
|
-
|
|
37
|
+
class MiniReporter {
|
|
38
|
+
constructor(options) {
|
|
39
|
+
this.options = Object.assign({}, options);
|
|
99
40
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
41
|
+
chalk.enabled = this.options.color;
|
|
42
|
+
for (const key of Object.keys(colors)) {
|
|
43
|
+
colors[key].enabled = this.options.color;
|
|
44
|
+
}
|
|
103
45
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
var title = cliTruncate(test.title, process.stdout.columns - SPINNER_WIDTH - PADDING);
|
|
46
|
+
const spinnerDef = spinners[process.platform === 'win32' ? 'line' : 'dots'];
|
|
47
|
+
this.spinnerFrames = spinnerDef.frames.map(c => chalk.gray.dim(c));
|
|
48
|
+
this.spinnerInterval = spinnerDef.interval;
|
|
108
49
|
|
|
109
|
-
|
|
110
|
-
|
|
50
|
+
this.reset();
|
|
51
|
+
this.stream = process.stderr;
|
|
52
|
+
this.stringDecoder = new StringDecoder();
|
|
111
53
|
}
|
|
54
|
+
start() {
|
|
55
|
+
this.interval = setInterval(() => {
|
|
56
|
+
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
|
|
57
|
+
this.write(this.prefix());
|
|
58
|
+
}, this.spinnerInterval);
|
|
112
59
|
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.
|
|
119
|
-
|
|
120
|
-
this.
|
|
60
|
+
return this.prefix('');
|
|
61
|
+
}
|
|
62
|
+
reset() {
|
|
63
|
+
this.clearInterval();
|
|
64
|
+
this.passCount = 0;
|
|
65
|
+
this.knownFailureCount = 0;
|
|
66
|
+
this.failCount = 0;
|
|
67
|
+
this.skipCount = 0;
|
|
68
|
+
this.todoCount = 0;
|
|
69
|
+
this.rejectionCount = 0;
|
|
70
|
+
this.exceptionCount = 0;
|
|
71
|
+
this.currentStatus = '';
|
|
72
|
+
this.currentTest = '';
|
|
73
|
+
this.statusLineCount = 0;
|
|
74
|
+
this.spinnerIndex = 0;
|
|
75
|
+
this.lastLineTracker = lastLineTracker();
|
|
121
76
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
MiniReporter.prototype.reportCounts = function (time) {
|
|
125
|
-
var lines = [
|
|
126
|
-
this.passCount > 0 ? '\n ' + colors.pass(this.passCount, 'passed') : '',
|
|
127
|
-
this.knownFailureCount > 0 ? '\n ' + colors.error(this.knownFailureCount, plur('known failure', this.knownFailureCount)) : '',
|
|
128
|
-
this.failCount > 0 ? '\n ' + colors.error(this.failCount, 'failed') : '',
|
|
129
|
-
this.skipCount > 0 ? '\n ' + colors.skip(this.skipCount, 'skipped') : '',
|
|
130
|
-
this.todoCount > 0 ? '\n ' + colors.todo(this.todoCount, 'todo') : ''
|
|
131
|
-
].filter(Boolean);
|
|
132
|
-
|
|
133
|
-
if (time && lines.length > 0) {
|
|
134
|
-
lines[0] += ' ' + time;
|
|
77
|
+
spinnerChar() {
|
|
78
|
+
return this.spinnerFrames[this.spinnerIndex];
|
|
135
79
|
}
|
|
80
|
+
clearInterval() {
|
|
81
|
+
clearInterval(this.interval);
|
|
82
|
+
this.interval = null;
|
|
83
|
+
}
|
|
84
|
+
test(test) {
|
|
85
|
+
if (test.todo) {
|
|
86
|
+
this.todoCount++;
|
|
87
|
+
} else if (test.skip) {
|
|
88
|
+
this.skipCount++;
|
|
89
|
+
} else if (test.error) {
|
|
90
|
+
this.failCount++;
|
|
91
|
+
} else {
|
|
92
|
+
this.passCount++;
|
|
93
|
+
if (test.failing) {
|
|
94
|
+
this.knownFailureCount++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
136
97
|
|
|
137
|
-
|
|
138
|
-
|
|
98
|
+
if (test.todo || test.skip) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
139
101
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
102
|
+
return this.prefix(this._test(test));
|
|
103
|
+
}
|
|
104
|
+
prefix(str) {
|
|
105
|
+
str = str || this.currentTest;
|
|
106
|
+
this.currentTest = str;
|
|
143
107
|
|
|
144
|
-
|
|
145
|
-
|
|
108
|
+
// The space before the newline is required for proper formatting
|
|
109
|
+
// TODO(jamestalmage): Figure out why it's needed and document it here
|
|
110
|
+
return ` \n ${this.spinnerChar()} ${str}`;
|
|
146
111
|
}
|
|
112
|
+
_test(test) {
|
|
113
|
+
const SPINNER_WIDTH = 3;
|
|
114
|
+
const PADDING = 1;
|
|
115
|
+
let title = cliTruncate(test.title, process.stdout.columns - SPINNER_WIDTH - PADDING);
|
|
147
116
|
|
|
148
|
-
|
|
117
|
+
if (test.error || test.failing) {
|
|
118
|
+
title = colors.error(test.title);
|
|
119
|
+
}
|
|
149
120
|
|
|
150
|
-
|
|
151
|
-
status += '\n ' + colors.error(this.rejectionCount, plur('rejection', this.rejectionCount));
|
|
121
|
+
return title + '\n' + this.reportCounts();
|
|
152
122
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
123
|
+
unhandledError(err) {
|
|
124
|
+
if (err.type === 'exception') {
|
|
125
|
+
this.exceptionCount++;
|
|
126
|
+
} else {
|
|
127
|
+
this.rejectionCount++;
|
|
128
|
+
}
|
|
156
129
|
}
|
|
130
|
+
reportCounts(time) {
|
|
131
|
+
const lines = [
|
|
132
|
+
this.passCount > 0 ? '\n ' + colors.pass(this.passCount, 'passed') : '',
|
|
133
|
+
this.knownFailureCount > 0 ? '\n ' + colors.error(this.knownFailureCount, plur('known failure', this.knownFailureCount)) : '',
|
|
134
|
+
this.failCount > 0 ? '\n ' + colors.error(this.failCount, 'failed') : '',
|
|
135
|
+
this.skipCount > 0 ? '\n ' + colors.skip(this.skipCount, 'skipped') : '',
|
|
136
|
+
this.todoCount > 0 ? '\n ' + colors.todo(this.todoCount, 'todo') : ''
|
|
137
|
+
].filter(Boolean);
|
|
138
|
+
|
|
139
|
+
if (time && lines.length > 0) {
|
|
140
|
+
lines[0] += ' ' + time;
|
|
141
|
+
}
|
|
157
142
|
|
|
158
|
-
|
|
159
|
-
status += '\n ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun');
|
|
143
|
+
return lines.join('');
|
|
160
144
|
}
|
|
145
|
+
finish(runStatus) {
|
|
146
|
+
this.clearInterval();
|
|
147
|
+
let time;
|
|
161
148
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
runStatus.knownFailures.forEach(function (test) {
|
|
166
|
-
i++;
|
|
167
|
-
|
|
168
|
-
var title = test.title;
|
|
149
|
+
if (this.options.watching) {
|
|
150
|
+
time = chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']');
|
|
151
|
+
}
|
|
169
152
|
|
|
170
|
-
|
|
171
|
-
// TODO output description with link
|
|
172
|
-
// status += colors.stack(description);
|
|
173
|
-
});
|
|
174
|
-
}
|
|
153
|
+
let status = this.reportCounts(time);
|
|
175
154
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
155
|
+
if (this.rejectionCount > 0) {
|
|
156
|
+
status += '\n ' + colors.error(this.rejectionCount, plur('rejection', this.rejectionCount));
|
|
157
|
+
}
|
|
181
158
|
|
|
182
|
-
|
|
159
|
+
if (this.exceptionCount > 0) {
|
|
160
|
+
status += '\n ' + colors.error(this.exceptionCount, plur('exception', this.exceptionCount));
|
|
161
|
+
}
|
|
183
162
|
|
|
184
|
-
|
|
185
|
-
|
|
163
|
+
if (runStatus.previousFailCount > 0) {
|
|
164
|
+
status += '\n ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun');
|
|
165
|
+
}
|
|
186
166
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
167
|
+
if (this.knownFailureCount > 0) {
|
|
168
|
+
for (const test of runStatus.knownFailures) {
|
|
169
|
+
const title = test.title;
|
|
170
|
+
status += '\n\n ' + colors.title(title);
|
|
171
|
+
// TODO: Output description with link
|
|
172
|
+
// status += colors.stack(description);
|
|
191
173
|
}
|
|
174
|
+
}
|
|
192
175
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
176
|
+
if (this.failCount > 0) {
|
|
177
|
+
runStatus.errors.forEach((test, index) => {
|
|
178
|
+
if (!test.error) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const title = test.error ? test.title : 'Unhandled Error';
|
|
183
|
+
const beforeSpacing = index === 0 ? '\n\n' : '\n\n\n\n';
|
|
184
|
+
|
|
185
|
+
status += beforeSpacing + ' ' + colors.title(title) + '\n';
|
|
186
|
+
if (test.error.source) {
|
|
187
|
+
status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n';
|
|
188
|
+
|
|
189
|
+
const excerpt = codeExcerpt(test.error.source, {maxWidth: process.stdout.columns});
|
|
190
|
+
if (excerpt) {
|
|
191
|
+
status += '\n' + indentString(excerpt, 2) + '\n';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (test.error.showOutput) {
|
|
196
|
+
status += '\n' + indentString(formatAssertError(test.error), 2);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// `.trim()` is needed, because default `err.message` is ' ' (see lib/assert.js)
|
|
200
|
+
if (test.error.message.trim()) {
|
|
201
|
+
status += '\n' + indentString(test.error.message, 2) + '\n';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (test.error.stack) {
|
|
205
|
+
const extracted = extractStack(test.error.stack);
|
|
206
|
+
if (extracted.includes('\n')) {
|
|
207
|
+
status += '\n' + indentString(colors.errorStack(extracted), 2);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
197
212
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
213
|
+
if (this.rejectionCount > 0 || this.exceptionCount > 0) {
|
|
214
|
+
// TODO(sindresorhus): Figure out why this causes a test failure when switched to a for-of loop
|
|
215
|
+
runStatus.errors.forEach(err => {
|
|
216
|
+
if (err.title) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (err.type === 'exception' && err.name === 'AvaError') {
|
|
221
|
+
status += '\n\n ' + colors.error(cross + ' ' + err.message);
|
|
222
|
+
} else {
|
|
223
|
+
const title = err.type === 'rejection' ? 'Unhandled Rejection' : 'Uncaught Exception';
|
|
224
|
+
let description = err.stack ? err.stack.trimRight() : JSON.stringify(err);
|
|
225
|
+
description = description.split('\n');
|
|
226
|
+
const errorTitle = err.name ? description[0] : 'Threw non-error: ' + description[0];
|
|
227
|
+
const errorStack = description.slice(1).join('\n');
|
|
228
|
+
|
|
229
|
+
status += '\n\n ' + colors.title(title) + '\n';
|
|
230
|
+
status += ' ' + colors.stack(errorTitle) + '\n';
|
|
231
|
+
status += colors.errorStack(errorStack);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
203
235
|
|
|
204
|
-
|
|
236
|
+
if (runStatus.failFastEnabled === true && runStatus.remainingCount > 0 && runStatus.failCount > 0) {
|
|
237
|
+
const remaining = 'At least ' + runStatus.remainingCount + ' ' + plur('test was', 'tests were', runStatus.remainingCount) + ' skipped.';
|
|
238
|
+
status += '\n\n ' + colors.information('`--fail-fast` is on. ' + remaining);
|
|
239
|
+
}
|
|
205
240
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
var title = err.type === 'rejection' ? 'Unhandled Rejection' : 'Uncaught Exception';
|
|
210
|
-
var description = err.stack ? err.stack.trimRight() : JSON.stringify(err);
|
|
241
|
+
if (runStatus.hasExclusive === true && runStatus.remainingCount > 0) {
|
|
242
|
+
status += '\n\n ' + colors.information('The .only() modifier is used in some tests.', runStatus.remainingCount, plur('test', runStatus.remainingCount), plur('was', 'were', runStatus.remainingCount), 'not run');
|
|
243
|
+
}
|
|
211
244
|
|
|
212
|
-
|
|
213
|
-
status += ' ' + colors.stack(description);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
245
|
+
return status + '\n\n';
|
|
216
246
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
MiniReporter.prototype.section = function () {
|
|
222
|
-
return '\n' + chalk.gray.dim(repeating('\u2500', process.stdout.columns || 80));
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
MiniReporter.prototype.clear = function () {
|
|
226
|
-
return '';
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
MiniReporter.prototype.write = function (str) {
|
|
230
|
-
cliCursor.hide();
|
|
231
|
-
this.currentStatus = str;
|
|
232
|
-
this._update();
|
|
233
|
-
this.statusLineCount = this.currentStatus.split('\n').length;
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
MiniReporter.prototype.stdout = MiniReporter.prototype.stderr = function (data) {
|
|
237
|
-
this._update(data);
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
MiniReporter.prototype._update = function (data) {
|
|
241
|
-
var str = '';
|
|
242
|
-
var ct = this.statusLineCount;
|
|
243
|
-
var columns = process.stdout.columns;
|
|
244
|
-
var lastLine = this.lastLineTracker.lastLine();
|
|
245
|
-
|
|
246
|
-
// Terminals automatically wrap text. We only need the last log line as seen on the screen.
|
|
247
|
-
lastLine = lastLine.substring(lastLine.length - (lastLine.length % columns));
|
|
248
|
-
|
|
249
|
-
// Don't delete the last log line if it's completely empty.
|
|
250
|
-
if (lastLine.length) {
|
|
251
|
-
ct++;
|
|
247
|
+
section() {
|
|
248
|
+
return '\n' + chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80));
|
|
252
249
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
str += eraseLines(ct);
|
|
256
|
-
|
|
257
|
-
// Rewrite the last log line.
|
|
258
|
-
str += lastLine;
|
|
259
|
-
|
|
260
|
-
if (str.length) {
|
|
261
|
-
this.stream.write(str);
|
|
250
|
+
clear() {
|
|
251
|
+
return '';
|
|
262
252
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
this.
|
|
267
|
-
this.
|
|
253
|
+
write(str) {
|
|
254
|
+
cliCursor.hide();
|
|
255
|
+
this.currentStatus = str;
|
|
256
|
+
this._update();
|
|
257
|
+
this.statusLineCount = this.currentStatus.split('\n').length;
|
|
258
|
+
}
|
|
259
|
+
stdout(data) {
|
|
260
|
+
this._update(data);
|
|
261
|
+
}
|
|
262
|
+
stderr(data) {
|
|
263
|
+
this._update(data);
|
|
268
264
|
}
|
|
265
|
+
_update(data) {
|
|
266
|
+
let str = '';
|
|
267
|
+
let ct = this.statusLineCount;
|
|
268
|
+
const columns = process.stdout.columns;
|
|
269
|
+
let lastLine = this.lastLineTracker.lastLine();
|
|
270
|
+
|
|
271
|
+
// Terminals automatically wrap text. We only need the last log line as seen on the screen.
|
|
272
|
+
lastLine = lastLine.substring(lastLine.length - (lastLine.length % columns));
|
|
273
|
+
|
|
274
|
+
// Don't delete the last log line if it's completely empty.
|
|
275
|
+
if (lastLine.length > 0) {
|
|
276
|
+
ct++;
|
|
277
|
+
}
|
|
269
278
|
|
|
270
|
-
|
|
279
|
+
// Erase the existing status message, plus the last log line.
|
|
280
|
+
str += eraseLines(ct);
|
|
271
281
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// We need a newline at the end of the last log line, before the status message.
|
|
275
|
-
// However, if the last log line is the exact width of the terminal a newline is implied,
|
|
276
|
-
// and adding a second will cause problems.
|
|
277
|
-
if (lastLine.length % columns) {
|
|
278
|
-
currentStatus = '\n' + currentStatus;
|
|
279
|
-
}
|
|
280
|
-
// rewrite the status message.
|
|
281
|
-
this.stream.write(currentStatus);
|
|
282
|
-
}
|
|
283
|
-
};
|
|
282
|
+
// Rewrite the last log line.
|
|
283
|
+
str += lastLine;
|
|
284
284
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
var CURSOR_TO_COLUMN_0 = CSI + '0G';
|
|
289
|
-
var CURSOR_UP = CSI + '1A';
|
|
285
|
+
if (str.length > 0) {
|
|
286
|
+
this.stream.write(str);
|
|
287
|
+
}
|
|
290
288
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
289
|
+
if (data) {
|
|
290
|
+
// Send new log data to the terminal, and update the last line status.
|
|
291
|
+
this.lastLineTracker.update(this.stringDecoder.write(data));
|
|
292
|
+
this.stream.write(data);
|
|
293
|
+
}
|
|
294
294
|
|
|
295
|
-
|
|
296
|
-
clear += ERASE_LINE + (i < count - 1 ? CURSOR_UP : '');
|
|
297
|
-
}
|
|
295
|
+
let currentStatus = this.currentStatus;
|
|
298
296
|
|
|
299
|
-
|
|
300
|
-
|
|
297
|
+
if (currentStatus.length > 0) {
|
|
298
|
+
lastLine = this.lastLineTracker.lastLine();
|
|
299
|
+
// We need a newline at the end of the last log line, before the status message.
|
|
300
|
+
// However, if the last log line is the exact width of the terminal a newline is implied,
|
|
301
|
+
// and adding a second will cause problems.
|
|
302
|
+
if (lastLine.length % columns) {
|
|
303
|
+
currentStatus = '\n' + currentStatus;
|
|
304
|
+
}
|
|
305
|
+
// Rewrite the status message.
|
|
306
|
+
this.stream.write(currentStatus);
|
|
307
|
+
}
|
|
301
308
|
}
|
|
302
|
-
|
|
303
|
-
return clear;
|
|
304
309
|
}
|
|
305
310
|
|
|
306
|
-
|
|
307
|
-
return message.replace(/^[^\n]*\n/, '');
|
|
308
|
-
}
|
|
311
|
+
module.exports = MiniReporter;
|