@@ -38,6 +38,9 @@ internal class ThemeCommand : CosmosCommand
3838 [ CosmosOption ( "force" , "f" ) ]
3939 public bool Force { get ; init ; }
4040
41+ [ CosmosOption ( "strict" ) ]
42+ public bool Strict { get ; init ; }
43+
4144 public override Task < CommandState > ExecuteAsync ( ShellInterpreter shell , CommandState commandState , string commandText , CancellationToken token )
4245 {
4346 var action = ( this . Action ?? "current" ) . Trim ( ) . ToLowerInvariant ( ) ;
@@ -314,13 +317,25 @@ private CommandState RunValidate(CommandState commandState)
314317 var requested = string . IsNullOrWhiteSpace ( this . Name ) ? this . Path : this . Name ;
315318 if ( string . IsNullOrWhiteSpace ( requested ) )
316319 {
317- var message = MessageService . GetString ( "command-theme-validate-missing-path" ) ;
318- AnsiConsole . MarkupLine ( message ) ;
319- return new ErrorCommandState ( new CommandException ( "theme" , message ) ) ;
320+ return this . RunValidateDirectory ( commandState , ThemeFile . DefaultUserThemesDirectory ( ) ) ;
321+ }
322+
323+ if ( Directory . Exists ( requested ) )
324+ {
325+ return this . RunValidateDirectory ( commandState , requested ) ;
320326 }
321327
322328 var path = ResolveThemePath ( requested ) ;
329+ if ( Directory . Exists ( path ) )
330+ {
331+ return this . RunValidateDirectory ( commandState , path ) ;
332+ }
333+
334+ return this . RunValidateFile ( commandState , path ) ;
335+ }
323336
337+ private CommandState RunValidateFile ( CommandState commandState , string path )
338+ {
324339 try
325340 {
326341 var result = ThemeRegistry . Instance . ValidateFile ( path ) ;
@@ -335,6 +350,18 @@ private CommandState RunValidate(CommandState commandState)
335350 AnsiConsole . MarkupLine ( Theme . FormatWarning ( warning ) ) ;
336351 }
337352
353+ if ( this . Strict && result . Warnings . Count > 0 )
354+ {
355+ var strictMessage = MessageService . GetArgsString (
356+ "command-theme-validate-strict-failed" ,
357+ "name" ,
358+ result . Name ,
359+ "count" ,
360+ result . Warnings . Count ) ;
361+ AnsiConsole . MarkupLine ( Theme . FormatError ( strictMessage ) ) ;
362+ return new ErrorCommandState ( new CommandException ( "theme" , strictMessage ) ) ;
363+ }
364+
338365 commandState . IsPrinted = true ;
339366 commandState . Result = new ShellJson ( JsonSerializer . SerializeToElement ( new
340367 {
@@ -359,6 +386,99 @@ private CommandState RunValidate(CommandState commandState)
359386 }
360387 }
361388
389+ private CommandState RunValidateDirectory ( CommandState commandState , string directory )
390+ {
391+ var files = ThemeFile . EnumerateThemeFiles ( directory ) ;
392+ if ( files . Length == 0 )
393+ {
394+ var emptyMessage = MessageService . GetArgsString ( "command-theme-validate-no-files" , "directory" , directory ) ;
395+ AnsiConsole . MarkupLine ( Theme . FormatMuted ( emptyMessage ) ) ;
396+ commandState . IsPrinted = true ;
397+ commandState . Result = new ShellJson ( JsonSerializer . SerializeToElement ( new
398+ {
399+ directory ,
400+ files = Array . Empty < object > ( ) ,
401+ valid = 0 ,
402+ invalid = 0 ,
403+ } ) ) ;
404+ return commandState ;
405+ }
406+
407+ var fileResults = new List < Dictionary < string , object ? > > ( ) ;
408+ var validCount = 0 ;
409+ var invalidCount = 0 ;
410+
411+ foreach ( var file in files )
412+ {
413+ var entry = new Dictionary < string , object ? >
414+ {
415+ [ "path" ] = file ,
416+ } ;
417+
418+ try
419+ {
420+ var result = ThemeRegistry . Instance . ValidateFile ( file ) ;
421+ var failedStrict = this . Strict && result . Warnings . Count > 0 ;
422+
423+ entry [ "name" ] = result . Name ;
424+ entry [ "valid" ] = ! failedStrict ;
425+ entry [ "warnings" ] = result . Warnings ;
426+
427+ if ( failedStrict )
428+ {
429+ invalidCount ++ ;
430+ AnsiConsole . MarkupLine ( $ " { Theme . FormatError ( "\u2717 " ) } { Markup . Escape ( result . Name ) } { Theme . FormatMuted ( "(" + System . IO . Path . GetFileName ( file ) + ")" ) } ") ;
431+ }
432+ else
433+ {
434+ validCount ++ ;
435+ AnsiConsole . MarkupLine ( $ " { Theme . FormatHelpAccent ( "\u2713 " ) } { Markup . Escape ( result . Name ) } { Theme . FormatMuted ( "(" + System . IO . Path . GetFileName ( file ) + ")" ) } ") ;
436+ }
437+
438+ foreach ( var warning in result . Warnings )
439+ {
440+ AnsiConsole . MarkupLine ( " " + Theme . FormatWarning ( warning ) ) ;
441+ }
442+ }
443+ catch ( Exception ex ) when ( ex is ThemeLoadException || ex is FileNotFoundException || ex is DirectoryNotFoundException )
444+ {
445+ invalidCount ++ ;
446+ entry [ "valid" ] = false ;
447+ entry [ "error" ] = ex . Message ;
448+ AnsiConsole . MarkupLine ( $ " { Theme . FormatError ( "\u2717 " ) } { Markup . Escape ( System . IO . Path . GetFileName ( file ) ) } ") ;
449+ AnsiConsole . MarkupLine ( " " + Theme . FormatError ( ex . Message ) ) ;
450+ }
451+
452+ fileResults . Add ( entry ) ;
453+ }
454+
455+ var summary = MessageService . GetArgsString (
456+ "command-theme-validate-summary" ,
457+ "valid" ,
458+ validCount ,
459+ "total" ,
460+ files . Length ,
461+ "directory" ,
462+ directory ) ;
463+ AnsiConsole . MarkupLine ( summary ) ;
464+
465+ commandState . IsPrinted = true ;
466+ commandState . Result = new ShellJson ( JsonSerializer . SerializeToElement ( new
467+ {
468+ directory ,
469+ files = fileResults ,
470+ valid = validCount ,
471+ invalid = invalidCount ,
472+ } ) ) ;
473+
474+ if ( invalidCount > 0 )
475+ {
476+ return new ErrorCommandState ( new CommandException ( "theme" , summary ) ) ;
477+ }
478+
479+ return commandState ;
480+ }
481+
362482 private CommandState RunReload ( CommandState commandState )
363483 {
364484 var registry = ThemeRegistry . Instance ;
0 commit comments