@@ -59,6 +59,18 @@ export interface CommandHelpData {
5959 globalOptions : HelpOption [ ] ;
6060}
6161
62+ /** Passed to `renderCommandHeader` to describe the running command. */
63+ export interface CommandMeta {
64+ name : string ;
65+ description : string ;
66+ }
67+
68+ /** One section emitted by `renderInfoOutput`, e.g. "System" or "Binaries". */
69+ export interface InfoSection {
70+ title : string ;
71+ rows : Row [ ] ;
72+ }
73+
6274// ─── Layout constants ─────────────────────────────────────────────
6375export const MAX_WIDTH = 80 ;
6476export const INDENT = 2 ;
@@ -127,6 +139,29 @@ export function renderRows(
127139 }
128140}
129141
142+ export function renderCommandHeader ( meta : CommandMeta , opts : RenderOptions ) : void {
143+ const { colors, log } = opts ;
144+ const termWidth = Math . min ( opts . columns || MAX_WIDTH , MAX_WIDTH ) ;
145+
146+ log ( "" ) ;
147+ log ( `${ indent ( INDENT ) } ${ colors . bold ( colors . cyan ( "⬡" ) ) } ${ colors . bold ( `webpack ${ meta . name } ` ) } ` ) ;
148+ log ( divider ( termWidth , colors ) ) ;
149+
150+ if ( meta . description ) {
151+ const descWidth = termWidth - INDENT * 2 ;
152+ for ( const line of wrapValue ( meta . description , descWidth ) ) {
153+ log ( `${ indent ( INDENT ) } ${ line } ` ) ;
154+ }
155+ log ( "" ) ;
156+ }
157+ }
158+
159+ export function renderCommandFooter ( opts : RenderOptions ) : void {
160+ const termWidth = Math . min ( opts . columns , MAX_WIDTH ) ;
161+ opts . log ( divider ( termWidth , opts . colors ) ) ;
162+ opts . log ( "" ) ;
163+ }
164+
130165function _renderHelpOptions (
131166 options : HelpOption [ ] ,
132167 colors : Colors ,
@@ -236,6 +271,108 @@ export function renderAliasHelp(data: AliasHelpData, opts: RenderOptions): void
236271 renderOptionHelp ( data . optionHelp , opts ) ;
237272}
238273
274+ export function renderError ( message : string , opts : RenderOptions ) : void {
275+ const { colors, log } = opts ;
276+ log ( `${ indent ( INDENT ) } ${ colors . red ( "✖" ) } ${ colors . bold ( message ) } ` ) ;
277+ }
278+
279+ export function renderSuccess ( message : string , opts : RenderOptions ) : void {
280+ const { colors, log } = opts ;
281+ log ( `${ indent ( INDENT ) } ${ colors . green ( "✔" ) } ${ colors . bold ( message ) } ` ) ;
282+ }
283+
284+ export function renderWarning ( message : string , opts : RenderOptions ) : void {
285+ const { colors, log } = opts ;
286+ log ( `${ indent ( INDENT ) } ${ colors . yellow ( "⚠" ) } ${ message } ` ) ;
287+ }
288+
289+ export function renderInfo ( message : string , opts : RenderOptions ) : void {
290+ const { colors, log } = opts ;
291+ log ( `${ indent ( INDENT ) } ${ colors . cyan ( "ℹ" ) } ${ message } ` ) ;
292+ }
293+
294+ export function parseEnvinfoSections ( raw : string ) : InfoSection [ ] {
295+ const sections : InfoSection [ ] = [ ] ;
296+ let current : InfoSection | null = null ;
297+
298+ for ( const line of raw . split ( "\n" ) ) {
299+ const sectionMatch = line . match ( / ^ { 2 } ( [ ^ : ] + ) : \s * $ / ) ;
300+ if ( sectionMatch ) {
301+ if ( current ) sections . push ( current ) ;
302+ current = { title : sectionMatch [ 1 ] . trim ( ) , rows : [ ] } ;
303+ continue ;
304+ }
305+
306+ const rowMatch = line . match ( / ^ { 4 } ( [ ^ : ] + ) : \s + ( .+ ) $ / ) ;
307+ if ( rowMatch && current ) {
308+ current . rows . push ( { label : rowMatch [ 1 ] . trim ( ) , value : rowMatch [ 2 ] . trim ( ) } ) ;
309+ continue ;
310+ }
311+
312+ const emptyRowMatch = line . match ( / ^ { 4 } ( [ ^ : ] + ) : \s * $ / ) ;
313+ if ( emptyRowMatch && current ) {
314+ current . rows . push ( { label : emptyRowMatch [ 1 ] . trim ( ) , value : "N/A" , color : ( str ) => str } ) ;
315+ }
316+ }
317+
318+ if ( current ) sections . push ( current ) ;
319+ return sections . filter ( ( section ) => section . rows . length > 0 ) ;
320+ }
321+
322+ export function renderInfoOutput ( rawEnvinfo : string , opts : RenderOptions ) : void {
323+ const { colors, log } = opts ;
324+ const termWidth = Math . min ( opts . columns , MAX_WIDTH ) ;
325+ const div = divider ( termWidth , colors ) ;
326+ const sections = parseEnvinfoSections ( rawEnvinfo ) ;
327+
328+ log ( "" ) ;
329+
330+ for ( const section of sections ) {
331+ log (
332+ `${ indent ( INDENT ) } ${ colors . bold ( colors . cyan ( "⬡" ) ) } ${ colors . bold ( colors . cyan ( section . title ) ) } ` ,
333+ ) ;
334+ log ( div ) ;
335+ renderRows ( section . rows , colors , log , termWidth ) ;
336+ log ( div ) ;
337+ log ( "" ) ;
338+ }
339+ }
340+
341+ export function renderVersionOutput ( rawEnvinfo : string , opts : RenderOptions ) : void {
342+ const { colors, log } = opts ;
343+ const termWidth = Math . min ( opts . columns , MAX_WIDTH ) ;
344+ const div = divider ( termWidth , colors ) ;
345+ const sections = parseEnvinfoSections ( rawEnvinfo ) ;
346+
347+ for ( const section of sections ) {
348+ log ( "" ) ;
349+ log (
350+ `${ indent ( INDENT ) } ${ colors . bold ( colors . cyan ( "⬡" ) ) } ${ colors . bold ( colors . cyan ( section . title ) ) } ` ,
351+ ) ;
352+ log ( div ) ;
353+
354+ const labelWidth = Math . max ( ...section . rows . map ( ( row ) => row . label . length ) ) ;
355+
356+ for ( const { label, value } of section . rows ) {
357+ const arrowIdx = value . indexOf ( "=>" ) ;
358+
359+ if ( arrowIdx !== - 1 ) {
360+ const requested = value . slice ( 0 , arrowIdx ) . trim ( ) ;
361+ const resolved = value . slice ( arrowIdx + 2 ) . trim ( ) ;
362+ log (
363+ `${ indent ( INDENT ) } ${ colors . bold ( label . padEnd ( labelWidth ) ) } ${ indent ( COL_GAP ) } ` +
364+ `${ colors . cyan ( requested . padEnd ( 12 ) ) } ${ colors . cyan ( "→" ) } ${ colors . green ( colors . bold ( resolved ) ) } ` ,
365+ ) ;
366+ } else {
367+ log (
368+ `${ indent ( INDENT ) } ${ colors . bold ( label . padEnd ( labelWidth ) ) } ${ indent ( COL_GAP ) } ${ colors . green ( value ) } ` ,
369+ ) ;
370+ }
371+ }
372+ log ( div ) ;
373+ }
374+ }
375+
239376export function renderFooter ( opts : RenderOptions , footer : FooterOptions = { } ) : void {
240377 const { colors, log } = opts ;
241378
0 commit comments