77 */
88
99import { 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. */
1215const hideCursor = '\x1b[?25l' ;
1316/** ANSI escape code to show cursor in terminal. */
1417const showCursor = '\x1b[?25h' ;
1518
1619export 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