@@ -2,7 +2,13 @@ import type { Writable } from 'node:stream';
22import { getColumns } from '@clack/core' ;
33import color from 'picocolors' ;
44import { erase } from 'sisteransi' ;
5- import { type CommonOptions , S_BAR , S_STEP_SUBMIT , isCI as isCIFn } from './common.js' ;
5+ import {
6+ type CommonOptions ,
7+ S_BAR ,
8+ S_STEP_SUBMIT ,
9+ isCI as isCIFn ,
10+ isTTY as isTTYFn ,
11+ } from './common.js' ;
612import { log } from './log.js' ;
713
814export interface TaskLogOptions extends CommonOptions {
@@ -20,6 +26,16 @@ export interface TaskLogCompletionOptions {
2026 showLog ?: boolean ;
2127}
2228
29+ interface BufferEntry {
30+ header ?: string ;
31+ value : string ;
32+ full : string ;
33+ result ?: {
34+ status : 'success' | 'error' ;
35+ message : string ;
36+ } ;
37+ }
38+
2339/**
2440 * Renders a log which clears on success and remains on failure
2541 */
@@ -30,69 +46,159 @@ export const taskLog = (opts: TaskLogOptions) => {
3046 const spacing = opts . spacing ?? 1 ;
3147 const barSize = 3 ;
3248 const retainLog = opts . retainLog === true ;
33- const isCI = isCIFn ( ) ;
49+ const isTTY = ! isCIFn ( ) && isTTYFn ( output ) ;
3450
3551 output . write ( `${ secondarySymbol } \n` ) ;
3652 output . write ( `${ color . green ( S_STEP_SUBMIT ) } ${ opts . title } \n` ) ;
3753 for ( let i = 0 ; i < spacing ; i ++ ) {
3854 output . write ( `${ secondarySymbol } \n` ) ;
3955 }
4056
41- let buffer = '' ;
42- let fullBuffer = '' ;
57+ const buffers : BufferEntry [ ] = [
58+ {
59+ value : '' ,
60+ full : '' ,
61+ } ,
62+ ] ;
4363 let lastMessageWasRaw = false ;
4464
4565 const clear = ( clearTitle : boolean ) : void => {
46- if ( buffer . length === 0 ) {
66+ if ( buffers . length === 0 ) {
4767 return ;
4868 }
49- const bufferHeight = buffer . split ( '\n' ) . reduce ( ( count , line ) => {
50- if ( line === '' ) {
51- return count + 1 ;
69+
70+ let lines = 0 ;
71+
72+ if ( clearTitle ) {
73+ lines += spacing + 2 ;
74+ }
75+
76+ for ( const buffer of buffers ) {
77+ const { value, result } = buffer ;
78+ let text = result ?. message ?? value ;
79+
80+ if ( text . length === 0 ) {
81+ continue ;
5282 }
53- return count + Math . ceil ( ( line . length + barSize ) / columns ) ;
54- } , 0 ) ;
55- const lines = bufferHeight + 1 + ( clearTitle ? spacing + 2 : 0 ) ;
56- output . write ( erase . lines ( lines ) ) ;
83+
84+ if ( result === undefined && buffer . header !== undefined && buffer . header !== '' ) {
85+ text += `\n${ buffer . header } ` ;
86+ }
87+
88+ const bufferHeight = text . split ( '\n' ) . reduce ( ( count , line ) => {
89+ if ( line === '' ) {
90+ return count + 1 ;
91+ }
92+ return count + Math . ceil ( ( line . length + barSize ) / columns ) ;
93+ } , 0 ) ;
94+
95+ lines += bufferHeight ;
96+ }
97+
98+ if ( lines > 0 ) {
99+ lines += 1 ;
100+ output . write ( erase . lines ( lines ) ) ;
101+ }
57102 } ;
58- const printBuffer = ( buf : string , messageSpacing ?: number ) : void => {
59- log . message ( buf . split ( '\n' ) . map ( color . dim ) , {
103+ const printBuffer = ( buffer : BufferEntry , messageSpacing ?: number , full ?: boolean ) : void => {
104+ const messages = full ? `${ buffer . full } \n${ buffer . value } ` : buffer . value ;
105+ if ( buffer . header !== undefined && buffer . header !== '' ) {
106+ log . message ( buffer . header . split ( '\n' ) . map ( color . bold ) , {
107+ output,
108+ secondarySymbol,
109+ symbol : secondarySymbol ,
110+ spacing : 0 ,
111+ } ) ;
112+ }
113+ log . message ( messages . split ( '\n' ) . map ( color . dim ) , {
60114 output,
61115 secondarySymbol,
62116 symbol : secondarySymbol ,
63117 spacing : messageSpacing ?? spacing ,
64118 } ) ;
65119 } ;
66120 const renderBuffer = ( ) : void => {
67- if ( retainLog === true && fullBuffer . length > 0 ) {
68- printBuffer ( `${ fullBuffer } \n${ buffer } ` ) ;
69- } else {
70- printBuffer ( buffer ) ;
121+ for ( const buffer of buffers ) {
122+ const { header, value, full } = buffer ;
123+ if ( ( header === undefined || header . length === 0 ) && value . length === 0 ) {
124+ continue ;
125+ }
126+ printBuffer ( buffer , undefined , retainLog === true && full . length > 0 ) ;
71127 }
72128 } ;
73-
74- return {
75- message ( msg : string , mopts ?: TaskLogMessageOptions ) {
76- clear ( false ) ;
77- if ( ( mopts ?. raw !== true || ! lastMessageWasRaw ) && buffer !== '' ) {
78- buffer += '\n' ;
79- }
80- buffer += msg ;
81- lastMessageWasRaw = mopts ?. raw === true ;
82- if ( opts . limit !== undefined ) {
83- const lines = buffer . split ( '\n' ) ;
84- const linesToRemove = lines . length - opts . limit ;
85- if ( linesToRemove > 0 ) {
86- const removedLines = lines . splice ( 0 , linesToRemove ) ;
87- if ( retainLog ) {
88- fullBuffer += ( fullBuffer === '' ? '' : '\n' ) + removedLines . join ( '\n' ) ;
89- }
129+ const message = ( buffer : BufferEntry , msg : string , mopts ?: TaskLogMessageOptions ) => {
130+ clear ( false ) ;
131+ if ( ( mopts ?. raw !== true || ! lastMessageWasRaw ) && buffer . value !== '' ) {
132+ buffer . value += '\n' ;
133+ }
134+ buffer . value += msg ;
135+ lastMessageWasRaw = mopts ?. raw === true ;
136+ if ( opts . limit !== undefined ) {
137+ const lines = buffer . value . split ( '\n' ) ;
138+ const linesToRemove = lines . length - opts . limit ;
139+ if ( linesToRemove > 0 ) {
140+ const removedLines = lines . splice ( 0 , linesToRemove ) ;
141+ if ( retainLog ) {
142+ buffer . full += ( buffer . full === '' ? '' : '\n' ) + removedLines . join ( '\n' ) ;
90143 }
91- buffer = lines . join ( '\n' ) ;
92144 }
93- if ( ! isCI ) {
145+ buffer . value = lines . join ( '\n' ) ;
146+ }
147+ if ( isTTY ) {
148+ printBuffers ( ) ;
149+ }
150+ } ;
151+ const printBuffers = ( ) : void => {
152+ for ( const buffer of buffers ) {
153+ if ( buffer . result ) {
154+ if ( buffer . result . status === 'error' ) {
155+ log . error ( buffer . result . message , { output, secondarySymbol, spacing : 0 } ) ;
156+ } else {
157+ log . success ( buffer . result . message , { output, secondarySymbol, spacing : 0 } ) ;
158+ }
159+ } else if ( buffer . value !== '' ) {
94160 printBuffer ( buffer , 0 ) ;
95161 }
162+ }
163+ } ;
164+ const completeBuffer = ( buffer : BufferEntry , result : BufferEntry [ 'result' ] ) : void => {
165+ clear ( false ) ;
166+
167+ buffer . result = result ;
168+
169+ if ( isTTY ) {
170+ printBuffers ( ) ;
171+ }
172+ } ;
173+
174+ return {
175+ message ( msg : string , mopts ?: TaskLogMessageOptions ) {
176+ message ( buffers [ 0 ] , msg , mopts ) ;
177+ } ,
178+ group ( name : string ) {
179+ const buffer : BufferEntry = {
180+ header : name ,
181+ value : '' ,
182+ full : '' ,
183+ } ;
184+ buffers . push ( buffer ) ;
185+ return {
186+ message ( msg : string , mopts ?: TaskLogMessageOptions ) {
187+ message ( buffer , msg , mopts ) ;
188+ } ,
189+ error ( message : string ) {
190+ completeBuffer ( buffer , {
191+ status : 'error' ,
192+ message,
193+ } ) ;
194+ } ,
195+ success ( message : string ) {
196+ completeBuffer ( buffer , {
197+ status : 'success' ,
198+ message,
199+ } ) ;
200+ } ,
201+ } ;
96202 } ,
97203 error ( message : string , opts ?: TaskLogCompletionOptions ) : void {
98204 clear ( true ) ;
@@ -101,7 +207,9 @@ export const taskLog = (opts: TaskLogOptions) => {
101207 renderBuffer ( ) ;
102208 }
103209 // clear buffer since error is an end state
104- buffer = fullBuffer = '' ;
210+ buffers . splice ( 1 , buffers . length - 1 ) ;
211+ buffers [ 0 ] . value = '' ;
212+ buffers [ 0 ] . full = '' ;
105213 } ,
106214 success ( message : string , opts ?: TaskLogCompletionOptions ) : void {
107215 clear ( true ) ;
@@ -110,7 +218,9 @@ export const taskLog = (opts: TaskLogOptions) => {
110218 renderBuffer ( ) ;
111219 }
112220 // clear buffer since success is an end state
113- buffer = fullBuffer = '' ;
221+ buffers . splice ( 1 , buffers . length - 1 ) ;
222+ buffers [ 0 ] . value = '' ;
223+ buffers [ 0 ] . full = '' ;
114224 } ,
115225 } ;
116226} ;
0 commit comments