Skip to content

Commit 11967c8

Browse files
committed
fix(ng-dev): improve spinner experience when executed in a CI environment (#2426)
Improve the spinner experience in a CI environment so that it does not show the same message repeatedly, but also continues to show output to prevent CI environments from treating the execution as inactive. PR Close #2426
1 parent d99222b commit 11967c8

File tree

2 files changed

+88
-32
lines changed

2 files changed

+88
-32
lines changed

ng-dev/perf/workflow/workflow.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import {ChildProcess} from '../../utils/child-process.js';
2-
import {green} from '../../utils/logging.js';
32
import {Spinner} from '../../utils/spinner.js';
43
import {Workflow} from './loader.js';
54

65
export async function measureWorkflow({name, workflow, prepare, cleanup}: Workflow) {
7-
const spinner = new Spinner('');
6+
const spinner = new Spinner();
87
try {
98
if (prepare) {
109
spinner.update('Preparing environment for workflow execution');
@@ -32,7 +31,7 @@ export async function measureWorkflow({name, workflow, prepare, cleanup}: Workfl
3231

3332
const results = performance.measure(name, 'start', 'end');
3433

35-
spinner.complete(` ${green('✓')} ${name}: ${results.duration.toFixed(2)}ms`);
34+
spinner.success(`${name}: ${results.duration.toFixed(2)}ms`);
3635

3736
return results.toJSON();
3837
} finally {

ng-dev/utils/spinner.ts

Lines changed: 86 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,74 @@
77
*/
88

99
import {cursorTo, clearLine} from 'readline';
10+
import {green, red} from './logging.js';
1011

12+
/** Whether execution is in a CI environment. */
13+
const IS_CI = process.env['CI'];
1114
/** ANSI escape code to hide cursor in terminal. */
1215
const hideCursor = '\x1b[?25l';
1316
/** ANSI escape code to show cursor in terminal. */
1417
const showCursor = '\x1b[?25h';
1518

1619
export class Spinner {
17-
/** Whether the spinner is currently running. */
18-
private isRunning = true;
20+
/** Whether the spinner is marked as completed. */
21+
private completed = false;
1922
/** The id of the interval being used to trigger frame printing. */
20-
private intervalId = setInterval(() => this.printFrame(), 125);
23+
private intervalId = setInterval(() => this.printFrame(), IS_CI ? 2500 : 125);
2124
/** The characters to iterate through to create the appearance of spinning in the spinner. */
2225
private spinnerCharacters = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
2326
/** The index of the spinner character used in the frame. */
2427
private currentSpinnerCharacterIndex = 0;
2528
/** The current text of the spinner. */
26-
private text: string = '';
29+
private _text: string = '';
30+
private set text(text: string | undefined) {
31+
this._text = text || this._text;
32+
this.printFrame(this.getNextSpinnerCharacter(), text);
33+
}
34+
private get text(): string {
35+
return this._text;
36+
}
37+
38+
constructor();
39+
constructor(text: string);
40+
constructor(text?: string) {
41+
this.hideCursor();
42+
this.text = text;
43+
}
44+
45+
/** Updates the spinner text with the provided text. */
46+
update(text: string) {
47+
this.text = text;
48+
}
2749

28-
constructor(text: string) {
29-
process.stdout.write(hideCursor);
30-
this.update(text);
50+
/** Completes the spinner marking it as successful with a `✓`. */
51+
success(text: string): void {
52+
this._complete(green('✓'), text);
53+
}
54+
55+
/** Completes the spinner marking it as failing with an `✘`. */
56+
failure(text: string): void {
57+
this._complete(red('✘'), text);
58+
}
59+
60+
/** Completes the spinner. */
61+
complete() {
62+
this._complete('', this.text);
63+
}
64+
65+
/**
66+
* Internal implementation for completing the spinner, marking it as completed, and printing the
67+
* final frame.
68+
*/
69+
private _complete(prefix: string, text: string) {
70+
if (this.completed) {
71+
return;
72+
}
73+
clearInterval(this.intervalId);
74+
this.printFrame(prefix, text);
75+
process.stdout.write('\n');
76+
this.showCursor();
77+
this.completed = true;
3178
}
3279

3380
/** Get the next spinner character. */
@@ -37,36 +84,46 @@ export class Spinner {
3784
return this.spinnerCharacters[this.currentSpinnerCharacterIndex];
3885
}
3986

40-
/** Print the current text for the spinner to the */
41-
private printFrame(prefix = this.getNextSpinnerCharacter(), text = this.text) {
87+
/**
88+
* Print the next frame either in CI mode or local terminal mode based on whether the script is run in a
89+
* CI environment.
90+
*/
91+
private printFrame(prefix = this.getNextSpinnerCharacter(), text?: string): void {
92+
if (IS_CI) {
93+
this.printNextCIFrame(text);
94+
} else {
95+
this.printNextLocalFrame(prefix, text);
96+
}
97+
}
98+
99+
/** Print the current text for the spinner to the terminal. */
100+
private printNextLocalFrame(prefix: string, text?: string) {
42101
cursorTo(process.stdout, 0);
43-
process.stdout.write(` ${prefix} ${text}`);
102+
process.stdout.write(` ${prefix} ${text || this.text}`);
44103
// Clear to the right of the cursor location in case the new frame is shorter than the previous.
45104
clearLine(process.stdout, 1);
46-
cursorTo(process.stdout, 0);
47105
}
48106

49-
/** Updates the spinner text with the provided text. */
50-
update(text: string) {
51-
this.text = text;
52-
this.printFrame(this.spinnerCharacters[this.currentSpinnerCharacterIndex]);
107+
/** Print the next expected piece for the spinner to stdout for CI usage. */
108+
private printNextCIFrame(text?: string) {
109+
if (text) {
110+
process.stdout.write(`\n${text}.`);
111+
return;
112+
}
113+
process.stdout.write('.');
53114
}
54115

55-
/** Completes the spinner. */
56-
complete(): void;
57-
complete(text: string): void;
58-
complete(text?: string) {
59-
if (!this.isRunning) {
60-
return;
116+
/** Hide the cursor in the terminal, only executed in local environments. */
117+
private hideCursor() {
118+
if (!IS_CI) {
119+
process.stdout.write(hideCursor);
61120
}
62-
clearInterval(this.intervalId);
63-
clearLine(process.stdout, 1);
64-
cursorTo(process.stdout, 0);
65-
if (text) {
66-
process.stdout.write(text);
67-
process.stdout.write('\n');
121+
}
122+
123+
/** Resume showing the cursor in the terminal, only executed in local environments. */
124+
private showCursor() {
125+
if (!IS_CI) {
126+
process.stdout.write(showCursor);
68127
}
69-
process.stdout.write(showCursor);
70-
this.isRunning = false;
71128
}
72129
}

0 commit comments

Comments
 (0)