Skip to content

Commit 280b6eb

Browse files
authored
Fix: Progress bar continue to update after footer (#4084)
**Summary** Fixes #4023. There was nothing preventing the console reporter from having more than one progress bars, or keep updating and rendering its progress bar after it was finished or the reporter was "done". This patch stores the active progress bar, and stops it before `footer` is printed out. Also makes sure the progress bar itself ignores any updates once it stops. **Test plan** One, weak, existing test with updated snapshot. Manual steps: - Run `yarn install express` - Observe that your console is properly cleared after `yarn` finishes and the last thing you see is the "Done in X.YZs." message and not a corrupted progress bar.
1 parent 5a191dd commit 280b6eb

File tree

4 files changed

+46
-9
lines changed

4 files changed

+46
-9
lines changed

__tests__/reporters/__snapshots__/console-reporter.js.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ exports[`ProgressBar 1`] = `"░░ 0/2"`;
136136
137137
exports[`ProgressBar 2`] = `"░░ 0/2█░ 1/2"`;
138138
139-
exports[`ProgressBar 3`] = `"[2K[1G[1G░░ 0/2[1G█░ 1/2[2K[1G[1G██ 2/2"`;
139+
exports[`ProgressBar 3`] = `"[2K[1G[1G░░ 0/2[1G█░ 1/2[1G██ 2/2"`;
140140
141141
exports[`Spinner 1`] = `"⠁ "`;
142142

src/cli/index.js

-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ export function main({
170170
const run = (): Promise<void> => {
171171
invariant(command, 'missing command');
172172
return command.run(config, reporter, commander, commander.args).then(exitCode => {
173-
reporter.close();
174173
if (outputWrapper) {
175174
reporter.footer(false);
176175
}

src/reporters/console/console-reporter.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export default class ConsoleReporter extends BaseReporter {
4242
}
4343

4444
_lastCategorySize: number;
45+
_progressBar: ?Progress;
4546

4647
_prependEmoji(msg: string, emoji: ?string): string {
4748
if (this.emoji && emoji && this.isTTY) {
@@ -63,6 +64,11 @@ export default class ConsoleReporter extends BaseReporter {
6364
this.inspect(obj);
6465
}
6566

67+
close() {
68+
this.stopProgress();
69+
super.close();
70+
}
71+
6672
table(head: Array<string>, body: Array<Row>) {
6773
//
6874
head = head.map((field: string): string => this.format.underline(field));
@@ -136,6 +142,8 @@ export default class ConsoleReporter extends BaseReporter {
136142
}
137143

138144
footer(showPeakMemory?: boolean) {
145+
this.stopProgress();
146+
139147
const totalTime = (this.getTotalTime() / 1000).toFixed(2);
140148
let msg = `Done in ${totalTime}s.`;
141149
if (showPeakMemory) {
@@ -387,7 +395,14 @@ export default class ConsoleReporter extends BaseReporter {
387395
};
388396
}
389397

390-
const bar = new Progress(count, this.stderr);
398+
// Clear any potentiall old progress bars
399+
this.stopProgress();
400+
401+
const bar = (this._progressBar = new Progress(count, this.stderr, (progress: Progress) => {
402+
if (progress === this._progressBar) {
403+
this._progressBar = null;
404+
}
405+
}));
391406

392407
bar.render();
393408

@@ -396,6 +411,12 @@ export default class ConsoleReporter extends BaseReporter {
396411
};
397412
}
398413

414+
stopProgress() {
415+
if (this._progressBar) {
416+
this._progressBar.stop();
417+
}
418+
}
419+
399420
async prompt<T>(message: string, choices: Array<*>, options?: PromptOptions = {}): Promise<Array<T>> {
400421
if (!process.stdout.isTTY) {
401422
return Promise.reject(new Error("Can't answer a question unless a user TTY"));

src/reporters/console/progress-bar.js

+23-6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import type {Stdout} from '../types.js';
44
import {clearLine, toStartOfLine} from './util.js';
55

66
export default class ProgressBar {
7-
constructor(total: number, stdout: Stdout = process.stderr) {
7+
constructor(total: number, stdout: Stdout = process.stderr, callback: ?(progressBar: ProgressBar) => void) {
88
this.stdout = stdout;
99
this.total = total;
1010
this.chars = ProgressBar.bars[0];
1111
this.delay = 60;
1212
this.curr = 0;
13+
this._callback = callback;
1314
clearLine(stdout);
1415
}
1516

@@ -20,28 +21,44 @@ export default class ProgressBar {
2021
chars: [string, string];
2122
delay: number;
2223
id: ?number;
24+
_callback: ?(progressBar: ProgressBar) => void;
2325

2426
static bars = [['█', '░']];
2527

2628
tick() {
29+
if (this.curr >= this.total) {
30+
return;
31+
}
32+
2733
this.curr++;
2834

2935
// schedule render
3036
if (!this.id) {
3137
this.id = setTimeout((): void => this.render(), this.delay);
3238
}
39+
}
3340

34-
// progress complete
35-
if (this.curr >= this.total) {
41+
cancelTick() {
42+
if (this.id) {
3643
clearTimeout(this.id);
37-
clearLine(this.stdout);
44+
this.id = null;
45+
}
46+
}
47+
48+
stop() {
49+
// "stop" by setting current to end so `tick` becomes noop
50+
this.curr = this.total;
51+
52+
this.cancelTick();
53+
clearLine(this.stdout);
54+
if (this._callback) {
55+
this._callback(this);
3856
}
3957
}
4058

4159
render() {
4260
// clear throttle
43-
clearTimeout(this.id);
44-
this.id = null;
61+
this.cancelTick();
4562

4663
let ratio = this.curr / this.total;
4764
ratio = Math.min(Math.max(ratio, 0), 1);

0 commit comments

Comments
 (0)