1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.StreamReport = exports.formatNameWithHyperlink = exports.formatName = void 0;
|
4 | const tslib_1 = require("tslib");
|
5 | const slice_ansi_1 = tslib_1.__importDefault(require("@arcanis/slice-ansi"));
|
6 | const MessageName_1 = require("./MessageName");
|
7 | const Report_1 = require("./Report");
|
8 | const PROGRESS_FRAMES = [`⠋`, `⠙`, `⠹`, `⠸`, `⠼`, `⠴`, `⠦`, `⠧`, `⠇`, `⠏`];
|
9 | const PROGRESS_INTERVAL = 80;
|
10 | const BASE_FORGETTABLE_NAMES = new Set([MessageName_1.MessageName.FETCH_NOT_CACHED, MessageName_1.MessageName.UNUSED_CACHE_ENTRY]);
|
11 | const BASE_FORGETTABLE_BUFFER_SIZE = 5;
|
12 | const GROUP = process.env.GITHUB_ACTIONS
|
13 | ? { start: (what) => `::group::${what}\n`, end: (what) => `::endgroup::\n` }
|
14 | : process.env.TRAVIS
|
15 | ? { start: (what) => `travis_fold:start:${what}\n`, end: (what) => `travis_fold:end:${what}\n` }
|
16 | : process.env.GITLAB_CI
|
17 | ? { start: (what) => `section_start:${Math.floor(Date.now() / 1000)}:${what.toLowerCase().replace(/\W+/g, `_`)}\r\x1b[0K${what}\n`, end: (what) => `section_end:${Math.floor(Date.now() / 1000)}:${what.toLowerCase().replace(/\W+/g, `_`)}\r\x1b[0K` }
|
18 | : null;
|
19 | const now = new Date();
|
20 | // We only want to support environments that will out-of-the-box accept the
|
21 | // characters we want to use. Others can enforce the style from the project
|
22 | // configuration.
|
23 | const supportsEmojis = [`iTerm.app`, `Apple_Terminal`].includes(process.env.TERM_PROGRAM) || !!process.env.WT_SESSION;
|
24 | const makeRecord = (obj) => obj;
|
25 | const PROGRESS_STYLES = makeRecord({
|
26 | patrick: {
|
27 | date: [17, 3],
|
28 | chars: [`🍀`, `🌱`],
|
29 | size: 40,
|
30 | },
|
31 | simba: {
|
32 | date: [19, 7],
|
33 | chars: [`🦁`, `🌴`],
|
34 | size: 40,
|
35 | },
|
36 | jack: {
|
37 | date: [31, 10],
|
38 | chars: [`🎃`, `🦇`],
|
39 | size: 40,
|
40 | },
|
41 | hogsfather: {
|
42 | date: [31, 12],
|
43 | chars: [`🎉`, `🎄`],
|
44 | size: 40,
|
45 | },
|
46 | default: {
|
47 | chars: [`=`, `-`],
|
48 | size: 80,
|
49 | },
|
50 | });
|
51 | const defaultStyle = (supportsEmojis && Object.keys(PROGRESS_STYLES).find(name => {
|
52 | const style = PROGRESS_STYLES[name];
|
53 | if (style.date && (style.date[0] !== now.getDate() || style.date[1] !== now.getMonth() + 1))
|
54 | return false;
|
55 | return true;
|
56 | })) || `default`;
|
57 | function formatName(name, { configuration, json }) {
|
58 | const num = name === null ? 0 : name;
|
59 | const label = `YN${num.toString(10).padStart(4, `0`)}`;
|
60 | if (!json && name === null) {
|
61 | return configuration.format(label, `grey`);
|
62 | }
|
63 | else {
|
64 | return label;
|
65 | }
|
66 | }
|
67 | exports.formatName = formatName;
|
68 | function formatNameWithHyperlink(name, { configuration, json }) {
|
69 | const code = formatName(name, { configuration, json });
|
70 |
|
71 | if (!configuration.get(`enableHyperlinks`))
|
72 | return code;
|
73 |
|
74 | if (name === null || name === MessageName_1.MessageName.UNNAMED)
|
75 | return code;
|
76 | const desc = MessageName_1.MessageName[name];
|
77 | const href = `https://yarnpkg.com/advanced/error-codes#${code}---${desc}`.toLowerCase();
|
78 |
|
79 |
|
80 |
|
81 | return `\u001b]8;;${href}\u0007${code}\u001b]8;;\u0007`;
|
82 | }
|
83 | exports.formatNameWithHyperlink = formatNameWithHyperlink;
|
84 | class StreamReport extends Report_1.Report {
|
85 | constructor({ configuration, stdout, json = false, includeFooter = true, includeLogs = !json, includeInfos = includeLogs, includeWarnings = includeLogs, forgettableBufferSize = BASE_FORGETTABLE_BUFFER_SIZE, forgettableNames = new Set(), }) {
|
86 | super();
|
87 | this.cacheHitCount = 0;
|
88 | this.cacheMissCount = 0;
|
89 | this.warningCount = 0;
|
90 | this.errorCount = 0;
|
91 | this.startTime = Date.now();
|
92 | this.indent = 0;
|
93 | this.progress = new Map();
|
94 | this.progressTime = 0;
|
95 | this.progressFrame = 0;
|
96 | this.progressTimeout = null;
|
97 | this.forgettableLines = [];
|
98 | this.configuration = configuration;
|
99 | this.forgettableBufferSize = forgettableBufferSize;
|
100 | this.forgettableNames = new Set([
|
101 | ...forgettableNames,
|
102 | ...BASE_FORGETTABLE_NAMES,
|
103 | ]);
|
104 | this.includeFooter = includeFooter;
|
105 | this.includeInfos = includeInfos;
|
106 | this.includeWarnings = includeWarnings;
|
107 | this.json = json;
|
108 | this.stdout = stdout;
|
109 | }
|
110 | static async start(opts, cb) {
|
111 | const report = new this(opts);
|
112 | const emitWarning = process.emitWarning;
|
113 | process.emitWarning = (message, name) => {
|
114 | if (typeof message !== `string`) {
|
115 | const error = message;
|
116 | message = error.message;
|
117 | name = name !== null && name !== void 0 ? name : error.name;
|
118 | }
|
119 | const fullMessage = typeof name !== `undefined`
|
120 | ? `${name}: ${message}`
|
121 | : message;
|
122 | report.reportWarning(MessageName_1.MessageName.UNNAMED, fullMessage);
|
123 | };
|
124 | try {
|
125 | await cb(report);
|
126 | }
|
127 | catch (error) {
|
128 | report.reportExceptionOnce(error);
|
129 | }
|
130 | finally {
|
131 | await report.finalize();
|
132 | process.emitWarning = emitWarning;
|
133 | }
|
134 | return report;
|
135 | }
|
136 | hasErrors() {
|
137 | return this.errorCount > 0;
|
138 | }
|
139 | exitCode() {
|
140 | return this.hasErrors() ? 1 : 0;
|
141 | }
|
142 | reportCacheHit(locator) {
|
143 | this.cacheHitCount += 1;
|
144 | }
|
145 | reportCacheMiss(locator, message) {
|
146 | this.cacheMissCount += 1;
|
147 | if (typeof message !== `undefined` && !this.configuration.get(`preferAggregateCacheInfo`)) {
|
148 | this.reportInfo(MessageName_1.MessageName.FETCH_NOT_CACHED, message);
|
149 | }
|
150 | }
|
151 | startTimerSync(what, cb) {
|
152 | this.reportInfo(null, `┌ ${what}`);
|
153 | const before = Date.now();
|
154 | this.indent += 1;
|
155 | try {
|
156 | return cb();
|
157 | }
|
158 | catch (error) {
|
159 | this.reportExceptionOnce(error);
|
160 | throw error;
|
161 | }
|
162 | finally {
|
163 | const after = Date.now();
|
164 | this.indent -= 1;
|
165 | if (this.configuration.get(`enableTimers`) && after - before > 200) {
|
166 | this.reportInfo(null, `└ Completed in ${this.formatTiming(after - before)}`);
|
167 | }
|
168 | else {
|
169 | this.reportInfo(null, `└ Completed`);
|
170 | }
|
171 | }
|
172 | }
|
173 | async startTimerPromise(what, cb) {
|
174 | this.reportInfo(null, `┌ ${what}`);
|
175 | if (GROUP !== null)
|
176 | this.stdout.write(GROUP.start(what));
|
177 | const before = Date.now();
|
178 | this.indent += 1;
|
179 | try {
|
180 | return await cb();
|
181 | }
|
182 | catch (error) {
|
183 | this.reportExceptionOnce(error);
|
184 | throw error;
|
185 | }
|
186 | finally {
|
187 | const after = Date.now();
|
188 | this.indent -= 1;
|
189 | if (GROUP !== null)
|
190 | this.stdout.write(GROUP.end(what));
|
191 | if (this.configuration.get(`enableTimers`) && after - before > 200) {
|
192 | this.reportInfo(null, `└ Completed in ${this.formatTiming(after - before)}`);
|
193 | }
|
194 | else {
|
195 | this.reportInfo(null, `└ Completed`);
|
196 | }
|
197 | }
|
198 | }
|
199 | async startCacheReport(cb) {
|
200 | const cacheInfo = this.configuration.get(`preferAggregateCacheInfo`)
|
201 | ? { cacheHitCount: this.cacheHitCount, cacheMissCount: this.cacheMissCount }
|
202 | : null;
|
203 | try {
|
204 | return await cb();
|
205 | }
|
206 | catch (error) {
|
207 | this.reportExceptionOnce(error);
|
208 | throw error;
|
209 | }
|
210 | finally {
|
211 | if (cacheInfo !== null) {
|
212 | this.reportCacheChanges(cacheInfo);
|
213 | }
|
214 | }
|
215 | }
|
216 | reportSeparator() {
|
217 | if (this.indent === 0) {
|
218 | this.writeLineWithForgettableReset(``);
|
219 | }
|
220 | else {
|
221 | this.reportInfo(null, ``);
|
222 | }
|
223 | }
|
224 | reportInfo(name, text) {
|
225 | if (!this.includeInfos)
|
226 | return;
|
227 | const message = `${this.configuration.format(`➤`, `blueBright`)} ${this.formatNameWithHyperlink(name)}: ${this.formatIndent()}${text}`;
|
228 | if (!this.json) {
|
229 | if (this.forgettableNames.has(name)) {
|
230 | this.forgettableLines.push(message);
|
231 | if (this.forgettableLines.length > this.forgettableBufferSize) {
|
232 | while (this.forgettableLines.length > this.forgettableBufferSize)
|
233 | this.forgettableLines.shift();
|
234 | this.writeLines(this.forgettableLines, { truncate: true });
|
235 | }
|
236 | else {
|
237 | this.writeLine(message, { truncate: true });
|
238 | }
|
239 | }
|
240 | else {
|
241 | this.writeLineWithForgettableReset(message);
|
242 | }
|
243 | }
|
244 | else {
|
245 | this.reportJson({ type: `info`, name, displayName: this.formatName(name), indent: this.formatIndent(), data: text });
|
246 | }
|
247 | }
|
248 | reportWarning(name, text) {
|
249 | this.warningCount += 1;
|
250 | if (!this.includeWarnings)
|
251 | return;
|
252 | if (!this.json) {
|
253 | this.writeLineWithForgettableReset(`${this.configuration.format(`➤`, `yellowBright`)} ${this.formatNameWithHyperlink(name)}: ${this.formatIndent()}${text}`);
|
254 | }
|
255 | else {
|
256 | this.reportJson({ type: `warning`, name, displayName: this.formatName(name), indent: this.formatIndent(), data: text });
|
257 | }
|
258 | }
|
259 | reportError(name, text) {
|
260 | this.errorCount += 1;
|
261 | if (!this.json) {
|
262 | this.writeLineWithForgettableReset(`${this.configuration.format(`➤`, `redBright`)} ${this.formatNameWithHyperlink(name)}: ${this.formatIndent()}${text}`, { truncate: false });
|
263 | }
|
264 | else {
|
265 | this.reportJson({ type: `error`, name, displayName: this.formatName(name), indent: this.formatIndent(), data: text });
|
266 | }
|
267 | }
|
268 | reportProgress(progressIt) {
|
269 | let stopped = false;
|
270 | const promise = Promise.resolve().then(async () => {
|
271 | const progressDefinition = {
|
272 | progress: 0,
|
273 | title: undefined,
|
274 | };
|
275 | this.progress.set(progressIt, progressDefinition);
|
276 | this.refreshProgress(-1);
|
277 | for await (const { progress, title } of progressIt) {
|
278 | if (stopped)
|
279 | continue;
|
280 | if (progressDefinition.progress === progress && progressDefinition.title === title)
|
281 | continue;
|
282 | progressDefinition.progress = progress;
|
283 | progressDefinition.title = title;
|
284 | this.refreshProgress();
|
285 | }
|
286 | stop();
|
287 | });
|
288 | const stop = () => {
|
289 | if (stopped)
|
290 | return;
|
291 | stopped = true;
|
292 | this.progress.delete(progressIt);
|
293 | this.refreshProgress(+1);
|
294 | };
|
295 | return { ...promise, stop };
|
296 | }
|
297 | reportJson(data) {
|
298 | if (this.json) {
|
299 | this.writeLineWithForgettableReset(`${JSON.stringify(data)}`);
|
300 | }
|
301 | }
|
302 | async finalize() {
|
303 | if (!this.includeFooter)
|
304 | return;
|
305 | let installStatus = ``;
|
306 | if (this.errorCount > 0)
|
307 | installStatus = `Failed with errors`;
|
308 | else if (this.warningCount > 0)
|
309 | installStatus = `Done with warnings`;
|
310 | else
|
311 | installStatus = `Done`;
|
312 | const timing = this.formatTiming(Date.now() - this.startTime);
|
313 | const message = this.configuration.get(`enableTimers`)
|
314 | ? `${installStatus} in ${timing}`
|
315 | : installStatus;
|
316 | if (this.errorCount > 0) {
|
317 | this.reportError(MessageName_1.MessageName.UNNAMED, message);
|
318 | }
|
319 | else if (this.warningCount > 0) {
|
320 | this.reportWarning(MessageName_1.MessageName.UNNAMED, message);
|
321 | }
|
322 | else {
|
323 | this.reportInfo(MessageName_1.MessageName.UNNAMED, message);
|
324 | }
|
325 | }
|
326 | writeLine(str, { truncate } = {}) {
|
327 | this.clearProgress({ clear: true });
|
328 | this.stdout.write(`${this.truncate(str, { truncate })}\n`);
|
329 | this.writeProgress();
|
330 | }
|
331 | writeLineWithForgettableReset(str, { truncate } = {}) {
|
332 | this.forgettableLines = [];
|
333 | this.writeLine(str, { truncate });
|
334 | }
|
335 | writeLines(lines, { truncate } = {}) {
|
336 | this.clearProgress({ delta: lines.length });
|
337 | for (const line of lines)
|
338 | this.stdout.write(`${this.truncate(line, { truncate })}\n`);
|
339 | this.writeProgress();
|
340 | }
|
341 | reportCacheChanges({ cacheHitCount, cacheMissCount }) {
|
342 | const cacheHitDelta = this.cacheHitCount - cacheHitCount;
|
343 | const cacheMissDelta = this.cacheMissCount - cacheMissCount;
|
344 | if (cacheHitDelta === 0 && cacheMissDelta === 0)
|
345 | return;
|
346 | let fetchStatus = ``;
|
347 | if (this.cacheHitCount > 1)
|
348 | fetchStatus += `${this.cacheHitCount} packages were already cached`;
|
349 | else if (this.cacheHitCount === 1)
|
350 | fetchStatus += ` - one package was already cached`;
|
351 | else
|
352 | fetchStatus += `No packages were cached`;
|
353 | if (this.cacheHitCount > 0) {
|
354 | if (this.cacheMissCount > 1) {
|
355 | fetchStatus += `, ${this.cacheMissCount} had to be fetched`;
|
356 | }
|
357 | else if (this.cacheMissCount === 1) {
|
358 | fetchStatus += `, one had to be fetched`;
|
359 | }
|
360 | }
|
361 | else {
|
362 | if (this.cacheMissCount > 1) {
|
363 | fetchStatus += ` - ${this.cacheMissCount} packages had to be fetched`;
|
364 | }
|
365 | else if (this.cacheMissCount === 1) {
|
366 | fetchStatus += ` - one package had to be fetched`;
|
367 | }
|
368 | }
|
369 | this.reportInfo(MessageName_1.MessageName.FETCH_NOT_CACHED, fetchStatus);
|
370 | }
|
371 | clearProgress({ delta = 0, clear = false }) {
|
372 | if (!this.configuration.get(`enableProgressBars`) || this.json)
|
373 | return;
|
374 | if (this.progress.size + delta > 0) {
|
375 | this.stdout.write(`\x1b[${this.progress.size + delta}A`);
|
376 | if (delta > 0 || clear) {
|
377 | this.stdout.write(`\x1b[0J`);
|
378 | }
|
379 | }
|
380 | }
|
381 | writeProgress() {
|
382 | if (!this.configuration.get(`enableProgressBars`) || this.json)
|
383 | return;
|
384 | if (this.progressTimeout !== null)
|
385 | clearTimeout(this.progressTimeout);
|
386 | this.progressTimeout = null;
|
387 | if (this.progress.size === 0)
|
388 | return;
|
389 | const now = Date.now();
|
390 | if (now - this.progressTime > PROGRESS_INTERVAL) {
|
391 | this.progressFrame = (this.progressFrame + 1) % PROGRESS_FRAMES.length;
|
392 | this.progressTime = now;
|
393 | }
|
394 | const spinner = PROGRESS_FRAMES[this.progressFrame];
|
395 | const styleName = this.configuration.get(`progressBarStyle`) || defaultStyle;
|
396 | if (!Object.prototype.hasOwnProperty.call(PROGRESS_STYLES, styleName))
|
397 | throw new Error(`Assertion failed: Invalid progress bar style`);
|
398 | const style = PROGRESS_STYLES[styleName];
|
399 | const PAD_LEFT = `➤ YN0000: ┌ `.length;
|
400 | const maxWidth = Math.max(0, Math.min(process.stdout.columns - PAD_LEFT, 80));
|
401 | const scaledSize = Math.floor(style.size * maxWidth / 80);
|
402 | for (const { progress } of this.progress.values()) {
|
403 | const okSize = scaledSize * progress;
|
404 | const ok = style.chars[0].repeat(okSize);
|
405 | const ko = style.chars[1].repeat(scaledSize - okSize);
|
406 | this.stdout.write(`${this.configuration.format(`➤`, `blueBright`)} ${this.formatName(null)}: ${spinner} ${ok}${ko}\n`);
|
407 | }
|
408 | this.progressTimeout = setTimeout(() => {
|
409 | this.refreshProgress();
|
410 | }, 1000 / 60);
|
411 | }
|
412 | refreshProgress(delta = 0) {
|
413 | this.clearProgress({ delta });
|
414 | this.writeProgress();
|
415 | }
|
416 | formatTiming(timing) {
|
417 | return timing < 60 * 1000
|
418 | ? `${Math.round(timing / 10) / 100}s`
|
419 | : `${Math.round(timing / 600) / 100}m`;
|
420 | }
|
421 | truncate(str, { truncate } = {}) {
|
422 | if (!this.configuration.get(`enableProgressBars`))
|
423 | truncate = false;
|
424 | if (typeof truncate === `undefined`)
|
425 | truncate = this.configuration.get(`preferTruncatedLines`);
|
426 |
|
427 |
|
428 | if (truncate)
|
429 | str = slice_ansi_1.default(str, 0, process.stdout.columns - 1);
|
430 | return str;
|
431 | }
|
432 | formatName(name) {
|
433 | return formatName(name, {
|
434 | configuration: this.configuration,
|
435 | json: this.json,
|
436 | });
|
437 | }
|
438 | formatNameWithHyperlink(name) {
|
439 | return formatNameWithHyperlink(name, {
|
440 | configuration: this.configuration,
|
441 | json: this.json,
|
442 | });
|
443 | }
|
444 | formatIndent() {
|
445 | return `│ `.repeat(this.indent);
|
446 | }
|
447 | }
|
448 | exports.StreamReport = StreamReport;
|