1+ /* eslint-disable no-undef, no-console, security/detect-object-injection */
2+ /**
3+ * Enhanced logging interface with levels, colors, and spinners
4+ */
5+ import pc from 'picocolors' ;
6+
7+ export type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'verbose' | 'debug' ;
8+
9+ const LOG_LEVEL_PRIORITY : Record < LogLevel , number > = {
10+ silent : 0 ,
11+ error : 1 ,
12+ warn : 2 ,
13+ info : 3 ,
14+ verbose : 4 ,
15+ debug : 5
16+ } ;
17+
18+ // Simple spinner implementation using console
19+ const SPINNER_FRAMES = [ '⠋' , '⠙' , '⠹' , '⠸' , '⠼' , '⠴' , '⠦' , '⠧' , '⠇' , '⠏' ] ;
20+
21+ interface SpinnerState {
22+ text : string ;
23+ interval : ReturnType < typeof setInterval > | null ;
24+ frameIndex : number ;
25+ }
26+
127/**
228 * Logging interface for the model generation library
329 */
430export interface LoggingInterface {
5- debug ( message ?: any , ...optionalParams : any [ ] ) : void ;
6- info ( message ?: any , ...optionalParams : any [ ] ) : void ;
7- warn ( message ?: any , ...optionalParams : any [ ] ) : void ;
8- error ( message ?: any , ...optionalParams : any [ ] ) : void ;
31+ debug ( message ?: unknown , ...optionalParams : unknown [ ] ) : void ;
32+ info ( message ?: unknown , ...optionalParams : unknown [ ] ) : void ;
33+ warn ( message ?: unknown , ...optionalParams : unknown [ ] ) : void ;
34+ error ( message ?: unknown , ...optionalParams : unknown [ ] ) : void ;
35+ }
36+
37+ /**
38+ * Extended logging interface with additional capabilities
39+ */
40+ export interface ExtendedLoggingInterface extends LoggingInterface {
41+ // Additional log level
42+ verbose ( message ?: unknown , ...optionalParams : unknown [ ] ) : void ;
43+
44+ // Progress helpers
45+ startSpinner ( text : string ) : void ;
46+ updateSpinner ( text : string ) : void ;
47+ succeedSpinner ( text ?: string ) : void ;
48+ failSpinner ( text ?: string ) : void ;
49+ stopSpinner ( ) : void ;
50+
51+ // Structured output
52+ json ( data : unknown ) : void ;
53+
54+ // Configuration
55+ setLevel ( level : LogLevel ) : void ;
56+ setJsonMode ( enabled : boolean ) : void ;
57+ setColors ( enabled : boolean ) : void ;
58+
59+ // State queries
60+ getLevel ( ) : LogLevel ;
61+ isJsonMode ( ) : boolean ;
962}
1063
1164/**
12- * Logger class for the model generation library
65+ * Logger class with enhanced capabilities
1366 *
14- * This class acts as a forefront for any external loggers which is why it also implements the interface itself.
67+ * Supports log levels, colors, spinners, and JSON output mode.
68+ * Acts as a forefront for any external loggers.
1569 */
16- export class LoggerClass implements LoggingInterface {
70+ export class LoggerClass implements ExtendedLoggingInterface {
1771 private logger ?: LoggingInterface = undefined ;
72+ private level : LogLevel = 'info' ;
73+ private jsonMode = false ;
74+ private colorsEnabled = true ;
75+ private spinner : SpinnerState | null = null ;
76+
77+ /**
78+ * Check if a message at the given level should be logged
79+ */
80+ private shouldLog ( messageLevel : LogLevel ) : boolean {
81+ return (
82+ LOG_LEVEL_PRIORITY [ messageLevel ] <= LOG_LEVEL_PRIORITY [ this . level ] &&
83+ ! this . jsonMode
84+ ) ;
85+ }
86+
87+ /**
88+ * Format a message with optional color
89+ */
90+ private formatMessage (
91+ message : unknown ,
92+ colorFn ?: ( s : string ) => string
93+ ) : string {
94+ const msg = String ( message ) ;
95+ if ( this . colorsEnabled && colorFn ) {
96+ return colorFn ( msg ) ;
97+ }
98+ return msg ;
99+ }
100+
101+ /**
102+ * Stop spinner before logging to prevent output overlap
103+ */
104+ private pauseSpinner ( ) : void {
105+ if ( this . spinner ?. interval ) {
106+ clearInterval ( this . spinner . interval ) ;
107+ // Clear the current line
108+ if ( process . stdout . isTTY ) {
109+ process . stdout . clearLine ( 0 ) ;
110+ process . stdout . cursorTo ( 0 ) ;
111+ }
112+ }
113+ }
114+
115+ /**
116+ * Resume spinner after logging
117+ */
118+ private resumeSpinner ( ) : void {
119+ if ( this . spinner && ! this . spinner . interval ) {
120+ this . renderSpinner ( ) ;
121+ }
122+ }
123+
124+ /**
125+ * Render the spinner
126+ */
127+ private renderSpinner ( ) : void {
128+ if ( ! this . spinner || ! process . stdout . isTTY ) {
129+ return ;
130+ }
131+
132+ this . spinner . interval = setInterval ( ( ) => {
133+ if ( ! this . spinner ) {
134+ return ;
135+ }
136+ const frame = this . colorsEnabled
137+ ? pc . cyan ( SPINNER_FRAMES [ this . spinner . frameIndex ] )
138+ : SPINNER_FRAMES [ this . spinner . frameIndex ] ;
139+ process . stdout . clearLine ( 0 ) ;
140+ process . stdout . cursorTo ( 0 ) ;
141+ process . stdout . write ( `${ frame } ${ this . spinner . text } ` ) ;
142+ this . spinner . frameIndex =
143+ ( this . spinner . frameIndex + 1 ) % SPINNER_FRAMES . length ;
144+ } , 80 ) ;
145+ }
146+
147+ debug ( message ?: unknown , ...optionalParams : unknown [ ] ) : void {
148+ if ( ! this . shouldLog ( 'debug' ) ) {
149+ return ;
150+ }
151+
152+ this . pauseSpinner ( ) ;
153+ const prefix = this . formatMessage ( '[DEBUG] ' , pc . gray ) ;
154+ const formattedMessage = this . formatMessage ( message , pc . gray ) ;
18155
19- debug ( message ?: any , ...optionalParams : any [ ] ) : void {
20156 if ( this . logger ) {
21- this . logger . debug ( message , ...optionalParams ) ;
157+ this . logger . debug ( prefix + formattedMessage , ...optionalParams ) ;
158+ } else {
159+ console . debug ( prefix + formattedMessage , ...optionalParams ) ;
22160 }
161+ this . resumeSpinner ( ) ;
23162 }
24163
25- info ( message ?: any , ...optionalParams : any [ ] ) : void {
164+ verbose ( message ?: unknown , ...optionalParams : unknown [ ] ) : void {
165+ if ( ! this . shouldLog ( 'verbose' ) ) {
166+ return ;
167+ }
168+
169+ this . pauseSpinner ( ) ;
170+ const formattedMessage = this . formatMessage ( message , pc . dim ) ;
171+
26172 if ( this . logger ) {
27- this . logger . info ( message , ...optionalParams ) ;
173+ this . logger . info ( formattedMessage , ...optionalParams ) ;
174+ } else {
175+ console . log ( formattedMessage , ...optionalParams ) ;
28176 }
177+ this . resumeSpinner ( ) ;
29178 }
30179
31- warn ( message ?: any , ...optionalParams : any [ ] ) : void {
180+ info ( message ?: unknown , ...optionalParams : unknown [ ] ) : void {
181+ if ( ! this . shouldLog ( 'info' ) ) {
182+ return ;
183+ }
184+
185+ this . pauseSpinner ( ) ;
186+ const msg = String ( message ) ;
187+
32188 if ( this . logger ) {
33- this . logger . warn ( message , ...optionalParams ) ;
189+ this . logger . info ( msg , ...optionalParams ) ;
190+ } else {
191+ // Use process.stdout.write for better capture by oclif test utilities
192+ const fullMsg =
193+ optionalParams . length > 0
194+ ? `${ msg } ${ optionalParams . join ( ' ' ) } \n`
195+ : `${ msg } \n` ;
196+ process . stdout . write ( fullMsg ) ;
34197 }
198+ this . resumeSpinner ( ) ;
35199 }
36200
37- error ( message ?: any , ...optionalParams : any [ ] ) : void {
201+ warn ( message ?: unknown , ...optionalParams : unknown [ ] ) : void {
202+ if ( ! this . shouldLog ( 'warn' ) ) {
203+ return ;
204+ }
205+
206+ this . pauseSpinner ( ) ;
207+ const formattedMessage = this . formatMessage ( message , pc . yellow ) ;
208+
38209 if ( this . logger ) {
39- this . logger . error ( message , ...optionalParams ) ;
210+ this . logger . warn ( formattedMessage , ...optionalParams ) ;
211+ } else {
212+ console . warn ( formattedMessage , ...optionalParams ) ;
213+ }
214+ this . resumeSpinner ( ) ;
215+ }
216+
217+ error ( message ?: unknown , ...optionalParams : unknown [ ] ) : void {
218+ if ( ! this . shouldLog ( 'error' ) ) {
219+ return ;
220+ }
221+
222+ this . pauseSpinner ( ) ;
223+ const formattedMessage = this . formatMessage ( message , pc . red ) ;
224+
225+ if ( this . logger ) {
226+ this . logger . error ( formattedMessage , ...optionalParams ) ;
227+ } else {
228+ console . error ( formattedMessage , ...optionalParams ) ;
229+ }
230+ this . resumeSpinner ( ) ;
231+ }
232+
233+ /**
234+ * Start a spinner with the given text
235+ */
236+ startSpinner ( text : string ) : void {
237+ if ( this . jsonMode ) {
238+ return ;
239+ }
240+ this . stopSpinner ( ) ;
241+
242+ this . spinner = {
243+ text,
244+ interval : null ,
245+ frameIndex : 0
246+ } ;
247+
248+ if ( process . stdout . isTTY ) {
249+ this . renderSpinner ( ) ;
250+ } else {
251+ // In non-TTY mode, just print the text
252+ console . log ( text ) ;
253+ }
254+ }
255+
256+ /**
257+ * Update the spinner text
258+ */
259+ updateSpinner ( text : string ) : void {
260+ if ( this . spinner ) {
261+ this . spinner . text = text ;
262+ }
263+ }
264+
265+ /**
266+ * Stop the spinner with a success message
267+ */
268+ succeedSpinner ( text ?: string ) : void {
269+ this . stopSpinner ( ) ;
270+ const displayText = text || this . spinner ?. text || '' ;
271+ if ( displayText && this . shouldLog ( 'info' ) ) {
272+ const symbol = this . colorsEnabled ? pc . green ( '✓' ) : '[OK]' ;
273+ console . log ( `${ symbol } ${ displayText } ` ) ;
274+ }
275+ }
276+
277+ /**
278+ * Stop the spinner with a failure message
279+ */
280+ failSpinner ( text ?: string ) : void {
281+ this . stopSpinner ( ) ;
282+ const displayText = text || this . spinner ?. text || '' ;
283+ if ( displayText && this . shouldLog ( 'error' ) ) {
284+ const symbol = this . colorsEnabled ? pc . red ( '✗' ) : '[FAIL]' ;
285+ console . log ( `${ symbol } ${ displayText } ` ) ;
286+ }
287+ }
288+
289+ /**
290+ * Stop the spinner without a message
291+ */
292+ stopSpinner ( ) : void {
293+ if ( this . spinner ) {
294+ if ( this . spinner . interval ) {
295+ clearInterval ( this . spinner . interval ) ;
296+ }
297+ if ( process . stdout . isTTY ) {
298+ process . stdout . clearLine ( 0 ) ;
299+ process . stdout . cursorTo ( 0 ) ;
300+ }
301+ this . spinner = null ;
40302 }
41303 }
42304
305+ /**
306+ * Output structured JSON data
307+ * Only outputs in JSON mode or when explicitly called
308+ */
309+ json ( data : unknown ) : void {
310+ this . stopSpinner ( ) ;
311+ console . log ( JSON . stringify ( data , null , 2 ) ) ;
312+ }
313+
314+ /**
315+ * Set the log level
316+ */
317+ setLevel ( level : LogLevel ) : void {
318+ this . level = level ;
319+ }
320+
321+ /**
322+ * Enable or disable JSON mode
323+ * In JSON mode, only json() output is shown
324+ */
325+ setJsonMode ( enabled : boolean ) : void {
326+ this . jsonMode = enabled ;
327+ if ( enabled ) {
328+ this . stopSpinner ( ) ;
329+ }
330+ }
331+
332+ /**
333+ * Enable or disable colored output
334+ */
335+ setColors ( enabled : boolean ) : void {
336+ this . colorsEnabled = enabled ;
337+ }
338+
339+ /**
340+ * Get the current log level
341+ */
342+ getLevel ( ) : LogLevel {
343+ return this . level ;
344+ }
345+
346+ /**
347+ * Check if JSON mode is enabled
348+ */
349+ isJsonMode ( ) : boolean {
350+ return this . jsonMode ;
351+ }
352+
43353 /**
44354 * Sets the logger to use for the model generation library
45355 *
@@ -48,6 +358,18 @@ export class LoggerClass implements LoggingInterface {
48358 setLogger ( logger ?: LoggingInterface ) : void {
49359 this . logger = logger ;
50360 }
361+
362+ /**
363+ * Reset the logger to default state.
364+ * Useful for testing or when re-initializing the logger.
365+ */
366+ reset ( ) : void {
367+ this . stopSpinner ( ) ;
368+ this . level = 'info' ;
369+ this . jsonMode = false ;
370+ this . colorsEnabled = true ;
371+ this . logger = undefined ;
372+ }
51373}
52374
53375export const Logger : LoggerClass = new LoggerClass ( ) ;
0 commit comments