1 | const chalk = require('chalk');
|
2 | const readline = require('readline');
|
3 | const prettyError = require('./utils/prettyError');
|
4 | const emoji = require('./utils/emoji');
|
5 | const {countBreaks} = require('grapheme-breaker');
|
6 | const stripAnsi = require('strip-ansi');
|
7 |
|
8 | class Logger {
|
9 | constructor(options) {
|
10 | this.lines = 0;
|
11 | this.statusLine = null;
|
12 | this.setOptions(options);
|
13 | }
|
14 |
|
15 | setOptions(options) {
|
16 | this.logLevel =
|
17 | options && isNaN(options.logLevel) === false
|
18 | ? Number(options.logLevel)
|
19 | : 3;
|
20 | this.color =
|
21 | options && typeof options.color === 'boolean'
|
22 | ? options.color
|
23 | : chalk.supportsColor;
|
24 | this.chalk = new chalk.constructor({enabled: this.color});
|
25 | this.isTest =
|
26 | options && typeof options.isTest === 'boolean'
|
27 | ? options.isTest
|
28 | : process.env.NODE_ENV === 'test';
|
29 | }
|
30 |
|
31 | countLines(message) {
|
32 | return message.split('\n').reduce((p, line) => {
|
33 | if (process.stdout.columns) {
|
34 | return p + Math.ceil((line.length || 1) / process.stdout.columns);
|
35 | }
|
36 |
|
37 | return p + 1;
|
38 | }, 0);
|
39 | }
|
40 |
|
41 | writeRaw(message) {
|
42 | this.lines += this.countLines(message) - 1;
|
43 | process.stdout.write(message);
|
44 | }
|
45 |
|
46 | write(message, persistent = false) {
|
47 | if (!persistent) {
|
48 | this.lines += this.countLines(message);
|
49 | }
|
50 |
|
51 | this._log(message);
|
52 | }
|
53 |
|
54 | log(message) {
|
55 | if (this.logLevel < 3) {
|
56 | return;
|
57 | }
|
58 |
|
59 | this.write(message);
|
60 | }
|
61 |
|
62 | persistent(message) {
|
63 | if (this.logLevel < 3) {
|
64 | return;
|
65 | }
|
66 |
|
67 | this.write(this.chalk.bold(message), true);
|
68 | }
|
69 |
|
70 | warn(err) {
|
71 | if (this.logLevel < 2) {
|
72 | return;
|
73 | }
|
74 |
|
75 | let {message, stack} = prettyError(err, {color: this.color});
|
76 | this.write(this.chalk.yellow(`${emoji.warning} ${message}`));
|
77 | if (stack) {
|
78 | this.write(stack);
|
79 | }
|
80 | }
|
81 |
|
82 | error(err) {
|
83 | if (this.logLevel < 1) {
|
84 | return;
|
85 | }
|
86 |
|
87 | let {message, stack} = prettyError(err, {color: this.color});
|
88 |
|
89 | this.status(emoji.error, message, 'red');
|
90 | if (stack) {
|
91 | this.write(stack);
|
92 | }
|
93 | }
|
94 |
|
95 | clear() {
|
96 | if (!this.color || this.isTest) {
|
97 | return;
|
98 | }
|
99 |
|
100 | while (this.lines > 0) {
|
101 | readline.clearLine(process.stdout, 0);
|
102 | readline.moveCursor(process.stdout, 0, -1);
|
103 | this.lines--;
|
104 | }
|
105 |
|
106 | readline.cursorTo(process.stdout, 0);
|
107 | this.statusLine = null;
|
108 | }
|
109 |
|
110 | writeLine(line, msg) {
|
111 | if (!this.color) {
|
112 | return this.log(msg);
|
113 | }
|
114 |
|
115 | let n = this.lines - line;
|
116 | let stdout = process.stdout;
|
117 | readline.cursorTo(stdout, 0);
|
118 | readline.moveCursor(stdout, 0, -n);
|
119 | stdout.write(msg);
|
120 | readline.clearLine(stdout, 1);
|
121 | readline.cursorTo(stdout, 0);
|
122 | readline.moveCursor(stdout, 0, n);
|
123 | }
|
124 |
|
125 | status(emoji, message, color = 'gray') {
|
126 | if (this.logLevel < 3) {
|
127 | return;
|
128 | }
|
129 |
|
130 | let hasStatusLine = this.statusLine != null;
|
131 | if (!hasStatusLine) {
|
132 | this.statusLine = this.lines;
|
133 | }
|
134 |
|
135 | this.writeLine(
|
136 | this.statusLine,
|
137 | this.chalk[color].bold(`${emoji} ${message}`)
|
138 | );
|
139 |
|
140 | if (!hasStatusLine) {
|
141 | process.stdout.write('\n');
|
142 | this.lines++;
|
143 | }
|
144 | }
|
145 |
|
146 | handleMessage(options) {
|
147 | this[options.method](...options.args);
|
148 | }
|
149 |
|
150 | _log(message) {
|
151 | console.log(message);
|
152 | }
|
153 |
|
154 | table(columns, table) {
|
155 |
|
156 | let colWidths = [];
|
157 | for (let row of table) {
|
158 | let i = 0;
|
159 | for (let item of row) {
|
160 | colWidths[i] = Math.max(colWidths[i] || 0, stringWidth(item));
|
161 | i++;
|
162 | }
|
163 | }
|
164 |
|
165 |
|
166 | for (let row of table) {
|
167 | let items = row.map((item, i) => {
|
168 |
|
169 |
|
170 | let padding =
|
171 | !columns[i + 1] || columns[i + 1].align === columns[i].align ? 4 : 0;
|
172 | return pad(item, colWidths[i] + padding, columns[i].align);
|
173 | });
|
174 |
|
175 | this.log(items.join(''));
|
176 | }
|
177 | }
|
178 | }
|
179 |
|
180 |
|
181 | function pad(text, length, align = 'left') {
|
182 | let pad = ' '.repeat(length - stringWidth(text));
|
183 | if (align === 'right') {
|
184 | return pad + text;
|
185 | }
|
186 |
|
187 | return text + pad;
|
188 | }
|
189 |
|
190 |
|
191 | function stringWidth(string) {
|
192 | return countBreaks(stripAnsi('' + string));
|
193 | }
|
194 |
|
195 |
|
196 |
|
197 |
|
198 | if (process.send) {
|
199 | class LoggerProxy {}
|
200 | for (let method of Object.getOwnPropertyNames(Logger.prototype)) {
|
201 | LoggerProxy.prototype[method] = (...args) => {
|
202 | process.send({
|
203 | type: 'logger',
|
204 | method,
|
205 | args
|
206 | });
|
207 | };
|
208 | }
|
209 |
|
210 | module.exports = new LoggerProxy();
|
211 | } else {
|
212 | module.exports = new Logger();
|
213 | }
|