UNPKG

4.79 kBJavaScriptView Raw
1const chalk = require('chalk');
2const readline = require('readline');
3const prettyError = require('./utils/prettyError');
4const emoji = require('./utils/emoji');
5const {countBreaks} = require('grapheme-breaker');
6const stripAnsi = require('strip-ansi');
7
8class 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 // Measure column widths
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 // Render rows
166 for (let row of table) {
167 let items = row.map((item, i) => {
168 // Add padding between columns unless the alignment is the opposite to the
169 // next column and pad to the column width.
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// Pad a string with spaces on either side
181function 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// Count visible characters in a string
191function stringWidth(string) {
192 return countBreaks(stripAnsi('' + string));
193}
194
195// If we are in a worker, make a proxy class which will
196// send the logger calls to the main process via IPC.
197// These are handled in WorkerFarm and directed to handleMessage above.
198if (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}