@@ -102,223 +102,218 @@ function stripVTControlCharacters(string) {
102102}
103103
104104class YoctoSpinner {
105- #color
106- #currentFrame = - 1
107- #exitHandlerBound
108105 #frames
109- #indention = ''
110106 #interval
107+ #currentFrame = - 1
108+ #timer
109+ #text
110+ #stream
111+ #color
112+ #lines = 0
113+ #exitHandlerBound
111114 #isInteractive
112115 #lastSpinnerFrameTime = 0
113- #lines = 0
114- #signal
115- #stream
116- #text
117- #timer
116+ #isSpinning = false
118117
119118 constructor ( options = { } ) {
120119 const opts = { __proto__ : null , ...options }
121120 const spinner = opts . spinner ?? getDefaultSpinner ( )
122121 const stream = opts . stream ?? getProcess ( ) . stderr
123- this . #color = opts . color ?? 'cyan'
124- this . #exitHandlerBound = this . #exitHandler. bind ( this )
125122 this . #frames = spinner . frames
126- this . #interval = spinner . interval ?? getDefaultSpinner ( ) . interval
123+ this . #interval = spinner . interval
124+ this . #text = options . text ?? ''
125+ this . #stream = stream ?? process . stderr
126+ this . #color = options . color ?? 'cyan'
127127 this . #isInteractive = ! ! stream . isTTY && isProcessInteractive ( )
128- this . #signal = opts . signal
129- this . #stream = stream
130- this . #text = opts . text ?? ''
128+ this . #exitHandlerBound = this . #exitHandler. bind ( this )
131129 }
132130
133- #clearTimer ( ) {
134- clearInterval ( this . #timer )
135- this . #timer = undefined
136- }
131+ start ( text ) {
132+ if ( text ) {
133+ this . #text = text
134+ }
137135
138- #exitHandler( ) {
139136 if ( this . isSpinning ) {
140- this . stop ( )
137+ return this
141138 }
142- }
143139
144- #hideCursor( ) {
140+ this . #isSpinning = true
141+ this . #hideCursor( )
142+ this . #render( )
143+ this . #subscribeToProcessEvents( )
144+
145+ // Only start the timer in interactive mode
145146 if ( this . #isInteractive) {
146- this . #write( '\u001B[?25l' )
147+ this . #timer = setInterval ( ( ) => {
148+ this . #render( )
149+ } , this . #interval)
147150 }
148- }
149151
150- #lineCount( text ) {
151- const width = this . #stream. columns ?? defaultTtyColumns
152- const lines = stripVTControlCharacters ( text ) . split ( '\n' )
153- let lineCount = 0
154- for ( const line of lines ) {
155- lineCount += Math . max ( 1 , Math . ceil ( line . length / width ) )
156- }
157- return lineCount
152+ return this
158153 }
159154
160- #render( ) {
161- // Ensure we only update the spinner frame at the wanted interval,
162- // even if the frame method is called more often.
163- const now = Date . now ( )
164- if (
165- this . #currentFrame === - 1 ||
166- now - this . #lastSpinnerFrameTime >= this . #interval
167- ) {
168- this . #currentFrame = ( this . #currentFrame + 1 ) % this . #frames. length
169- this . #lastSpinnerFrameTime = now
170- }
171- const colors = getYoctocolors ( )
172- const applyColor = colors [ this . #color] ?? colors . cyan
173- const frame = this . #frames[ this . #currentFrame]
174- let string = `${ frame ? applyColor ( frame ) : '' } ${ this . #text ? ` ${ this . #text} ` : '' } `
175- if ( string ) {
176- if ( this . #indention. length ) {
177- string = `${ this . #indention} ${ string } `
178- }
179- if ( ! this . #isInteractive) {
180- string += '\n'
181- }
155+ stop ( finalText ) {
156+ if ( ! this . isSpinning ) {
157+ return this
182158 }
183- this . clear ( )
184- this . #write( string )
185- if ( this . #isInteractive) {
186- this . #lines = this . #lineCount( string )
159+
160+ this . #isSpinning = false
161+ if ( this . #timer) {
162+ clearInterval ( this . #timer)
163+ this . #timer = undefined
187164 }
188- }
189165
190- #setTimer( ) {
191- const timeout = setInterval ( ( ) => {
192- this . #render( )
193- } , this . #interval)
194- // Guard unref usage in case yocto-spinner is somehow built to run in a browser.
195- // https://nodejs.org/api/timers.html#timeoutunref
196- timeout ?. unref ?. ( )
197- this . #timer = timeout
198- }
166+ this . #showCursor( )
167+ this . clear ( )
168+ this . #unsubscribeFromProcessEvents( )
199169
200- #showCursor( ) {
201- if ( this . #isInteractive) {
202- this . #write( '\u001B[?25h' )
170+ if ( finalText ) {
171+ this . #stream. write ( `${ finalText } \n` )
203172 }
204- }
205173
206- #subscribeToExitEvents( ) {
207- this . #signal?. addEventListener ( 'abort' , this . #exitHandlerBound)
208- process . once ( 'SIGINT' , this . #exitHandlerBound)
209- process . once ( 'SIGTERM' , this . #exitHandlerBound)
174+ return this
210175 }
211176
212177 #symbolStop( symbolType , text ) {
213178 const symbols = getLogSymbols ( )
214179 return this . stop ( `${ symbols [ symbolType ] } ${ text ?? this . #text} ` )
215180 }
216181
217- #unsubscribeFromExitEvents( ) {
218- this . #signal?. removeEventListener ( 'abort' , this . #exitHandlerBound)
219- process . off ( 'SIGINT' , this . #exitHandlerBound)
220- process . off ( 'SIGTERM' , this . #exitHandlerBound)
182+ success ( text ) {
183+ return this . #symbolStop( 'success' , text )
221184 }
222185
223- #write ( text ) {
224- this . #stream . write ( text )
186+ error ( text ) {
187+ return this . #symbolStop ( 'error' , text )
225188 }
226189
227- get color ( ) {
228- return this . #color
190+ warning ( text ) {
191+ return this . #symbolStop ( 'warning' , text )
229192 }
230193
231- set color ( value ) {
232- this . #color = value
233- this . #render( )
194+ info ( text ) {
195+ return this . #symbolStop( 'info' , text )
234196 }
235197
236198 get isSpinning ( ) {
237- return this . #timer !== undefined
199+ return this . #isSpinning
238200 }
239201
240202 get text ( ) {
241203 return this . #text
242204 }
243205
244206 set text ( value ) {
245- const text = value ?? ''
246- this . #text = typeof text === 'string' ? text : String ( text )
207+ this . #text = value ?? ''
208+ this . #render( )
209+ }
210+
211+ get color ( ) {
212+ return this . #color
213+ }
214+
215+ set color ( value ) {
216+ this . #color = value
247217 this . #render( )
248218 }
249219
250220 clear ( ) {
251221 if ( ! this . #isInteractive) {
252222 return this
253223 }
224+
254225 this . #stream. cursorTo ( 0 )
255- for ( let index = 0 ; index < this . #lines; index += 1 ) {
226+
227+ for ( let index = 0 ; index < this . #lines; index ++ ) {
256228 if ( index > 0 ) {
257229 this . #stream. moveCursor ( 0 , - 1 )
258230 }
231+
259232 this . #stream. clearLine ( 1 )
260233 }
234+
261235 this . #lines = 0
262- return this
263- }
264236
265- dedent ( spaces = 2 ) {
266- this . #indention = this . #indention. slice ( 0 , - spaces )
267237 return this
268238 }
269239
270- error ( text ) {
271- return this . #symbolStop( 'error' , text )
272- }
240+ #render( ) {
241+ // Ensure we only update the spinner frame at the wanted interval,
242+ // even if the frame method is called more often.
243+ const now = Date . now ( )
244+ if (
245+ this . #currentFrame === - 1 ||
246+ now - this . #lastSpinnerFrameTime >= this . #interval
247+ ) {
248+ this . #currentFrame = ++ this . #currentFrame % this . #frames. length
249+ this . #lastSpinnerFrameTime = now
250+ }
273251
274- indent ( spaces = 2 ) {
275- this . #indention += ' ' . repeat ( spaces )
276- return this
277- }
252+ const colors = getYoctocolors ( )
253+ const applyColor = colors [ this . #color ] ?? colors . cyan
254+ const frame = this . #frames [ this . #currentFrame ]
255+ let string = ` ${ applyColor ( frame ) } ${ this . #text } `
278256
279- info ( text ) {
280- return this . #symbolStop( 'info' , text )
257+ if ( ! this . #isInteractive) {
258+ string += '\n'
259+ }
260+
261+ this . clear ( )
262+ this . #write( string )
263+
264+ if ( this . #isInteractive) {
265+ this . #lines = this . #lineCount( string )
266+ }
281267 }
282268
283- resetIndent ( ) {
284- this . #indention = ''
285- return this
269+ #write( text ) {
270+ this . #stream. write ( text )
286271 }
287272
288- start ( text ) {
289- if ( text ) {
290- this . #text = text
291- }
292- if ( this . isSpinning ) {
293- return this
273+ #lineCount( text ) {
274+ const width = this . #stream. columns ?? defaultTtyColumns
275+ const lines = stripVTControlCharacters ( text ) . split ( '\n' )
276+
277+ let lineCount = 0
278+ for ( const line of lines ) {
279+ lineCount += Math . max ( 1 , Math . ceil ( line . length / width ) )
294280 }
295- this . #hideCursor( )
296- this . #render( )
297- this . #setTimer( )
298- this . #subscribeToExitEvents( )
299- return this
281+
282+ return lineCount
300283 }
301284
302- stop ( finalText ) {
303- if ( ! this . isSpinning ) {
304- return this
285+ #hideCursor ( ) {
286+ if ( this . #isInteractive ) {
287+ this . #write ( '\u001B[?25l' )
305288 }
306- this . #showCursor( )
307- this . clear ( )
308- this . #clearTimer( )
309- this . #unsubscribeFromExitEvents( )
310- if ( finalText ) {
311- this . #write( `${ this . #indention} ${ finalText } \n` )
289+ }
290+
291+ #showCursor( ) {
292+ if ( this . #isInteractive) {
293+ this . #write( '\u001B[?25h' )
312294 }
313- return this
314295 }
315296
316- success ( text ) {
317- return this . #symbolStop( 'success' , text )
297+ #subscribeToProcessEvents( ) {
298+ process . once ( 'SIGINT' , this . #exitHandlerBound)
299+ process . once ( 'SIGTERM' , this . #exitHandlerBound)
318300 }
319301
320- warning ( text ) {
321- return this . #symbolStop( 'warning' , text )
302+ #unsubscribeFromProcessEvents( ) {
303+ process . off ( 'SIGINT' , this . #exitHandlerBound)
304+ process . off ( 'SIGTERM' , this . #exitHandlerBound)
305+ }
306+
307+ #exitHandler( signal ) {
308+ if ( this . isSpinning ) {
309+ this . stop ( )
310+ }
311+
312+ // SIGINT: 128 + 2
313+ // SIGTERM: 128 + 15
314+ const exitCode = signal === 'SIGINT' ? 130 : signal === 'SIGTERM' ? 143 : 1
315+ // eslint-disable-next-line n/no-process-exit
316+ process . exit ( exitCode )
322317 }
323318}
324319
0 commit comments