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