@@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
1111use tokio:: fs:: write;
1212use vite_error:: Error ;
1313use vite_path:: { AbsolutePath , AbsolutePathBuf } ;
14- use vite_shared:: { PrependOptions , prepend_to_path_env} ;
14+ use vite_shared:: { PrependOptions , output , prepend_to_path_env} ;
1515use vite_str:: Str ;
1616use vite_task:: {
1717 Command , CommandHandler , ExitStatus , HandledCommand , ScriptCommand , Session , SessionCallbacks ,
@@ -97,6 +97,27 @@ pub enum SynthesizableSubcommand {
9797 #[ clap( allow_hyphen_values = true , trailing_var_arg = true ) ]
9898 args : Vec < String > ,
9999 } ,
100+ /// Run format, lint, and type checks
101+ Check {
102+ /// Auto-fix format and lint issues
103+ #[ arg( long) ]
104+ fix : bool ,
105+ /// Skip format check
106+ #[ arg( long = "no-fmt" ) ]
107+ no_fmt : bool ,
108+ /// Skip lint check
109+ #[ arg( long = "no-lint" ) ]
110+ no_lint : bool ,
111+ /// Disable type-aware linting
112+ #[ arg( long = "no-type-aware" ) ]
113+ no_type_aware : bool ,
114+ /// Disable TypeScript type checking
115+ #[ arg( long = "no-type-check" ) ]
116+ no_type_check : bool ,
117+ /// File paths to check (passed through to fmt and lint)
118+ #[ arg( trailing_var_arg = true ) ]
119+ paths : Vec < String > ,
120+ } ,
100121}
101122
102123/// Top-level CLI argument parser for vite-plus.
@@ -494,6 +515,11 @@ impl SubcommandResolver {
494515 envs : merge_resolved_envs ( envs, resolved. envs ) ,
495516 } )
496517 }
518+ SynthesizableSubcommand :: Check { .. } => {
519+ anyhow:: bail!(
520+ "Check is a composite command and cannot be resolved to a single subcommand"
521+ ) ;
522+ }
497523 SynthesizableSubcommand :: Install { args } => {
498524 let package_manager =
499525 vite_install:: PackageManager :: builder ( cwd) . build_with_default ( ) . await ?;
@@ -589,6 +615,10 @@ impl CommandHandler for VitePlusCommandHandler {
589615 let cli_args =
590616 CLIArgs :: try_parse_from ( iter:: once ( "vp" ) . chain ( command. args . iter ( ) . map ( Str :: as_str) ) ) ?;
591617 match cli_args {
618+ CLIArgs :: Synthesizable ( SynthesizableSubcommand :: Check { .. } ) => {
619+ // Check is a composite command — run as a subprocess in task scripts
620+ Ok ( HandledCommand :: Verbatim )
621+ }
592622 CLIArgs :: Synthesizable ( subcmd) => {
593623 let resolved = self . resolver . resolve ( subcmd, & command. envs , & command. cwd ) . await ?;
594624 Ok ( HandledCommand :: Synthesized ( resolved. into_synthetic_plan_request ( ) ) )
@@ -644,31 +674,16 @@ impl UserConfigLoader for VitePlusConfigLoader {
644674 }
645675}
646676
647- /// Execute a synthesizable subcommand directly (not through vite-task Session) .
648- /// No caching, no task graph, no dependency resolution.
649- async fn execute_direct_subcommand (
677+ /// Resolve a single subcommand and execute it, returning its exit status .
678+ async fn resolve_and_execute (
679+ resolver : & mut SubcommandResolver ,
650680 subcommand : SynthesizableSubcommand ,
681+ envs : & Arc < FxHashMap < Arc < OsStr > , Arc < OsStr > > > ,
651682 cwd : & AbsolutePathBuf ,
652- options : Option < CliOptions > ,
683+ cwd_arc : & Arc < AbsolutePath > ,
653684) -> Result < ExitStatus , Error > {
654- let ( workspace_root, _) = vite_workspace:: find_workspace_root ( cwd) ?;
655- let workspace_path: Arc < AbsolutePath > = workspace_root. path . into ( ) ;
656-
657- let mut resolver = if let Some ( options) = options {
658- SubcommandResolver :: new ( Arc :: clone ( & workspace_path) ) . with_cli_options ( options)
659- } else {
660- SubcommandResolver :: new ( Arc :: clone ( & workspace_path) )
661- } ;
662-
663- let envs: Arc < FxHashMap < Arc < OsStr > , Arc < OsStr > > > = Arc :: new (
664- std:: env:: vars_os ( )
665- . map ( |( k, v) | ( Arc :: from ( k. as_os_str ( ) ) , Arc :: from ( v. as_os_str ( ) ) ) )
666- . collect ( ) ,
667- ) ;
668- let cwd_arc: Arc < AbsolutePath > = cwd. clone ( ) . into ( ) ;
669-
670685 let resolved =
671- resolver. resolve ( subcommand, & envs, & cwd_arc) . await . map_err ( |e| Error :: Anyhow ( e) ) ?;
686+ resolver. resolve ( subcommand, envs, cwd_arc) . await . map_err ( |e| Error :: Anyhow ( e) ) ?;
672687
673688 // Resolve the program path using `which` to handle Windows .cmd/.bat files (PATHEXT)
674689 let program_path = {
@@ -695,11 +710,132 @@ async fn execute_direct_subcommand(
695710 let mut child = cmd. spawn ( ) . map_err ( |e| Error :: Anyhow ( e. into ( ) ) ) ?;
696711
697712 let status = child. wait ( ) . await ;
713+ let status = status. map_err ( |e| Error :: Anyhow ( e. into ( ) ) ) ?;
714+ Ok ( ExitStatus ( status. code ( ) . unwrap_or ( 1 ) as u8 ) )
715+ }
716+
717+ /// Execute a synthesizable subcommand directly (not through vite-task Session).
718+ /// No caching, no task graph, no dependency resolution.
719+ async fn execute_direct_subcommand (
720+ subcommand : SynthesizableSubcommand ,
721+ cwd : & AbsolutePathBuf ,
722+ options : Option < CliOptions > ,
723+ ) -> Result < ExitStatus , Error > {
724+ let ( workspace_root, _) = vite_workspace:: find_workspace_root ( cwd) ?;
725+ let workspace_path: Arc < AbsolutePath > = workspace_root. path . into ( ) ;
726+
727+ let mut resolver = if let Some ( options) = options {
728+ SubcommandResolver :: new ( Arc :: clone ( & workspace_path) ) . with_cli_options ( options)
729+ } else {
730+ SubcommandResolver :: new ( Arc :: clone ( & workspace_path) )
731+ } ;
732+
733+ let envs: Arc < FxHashMap < Arc < OsStr > , Arc < OsStr > > > = Arc :: new (
734+ std:: env:: vars_os ( )
735+ . map ( |( k, v) | ( Arc :: from ( k. as_os_str ( ) ) , Arc :: from ( v. as_os_str ( ) ) ) )
736+ . collect ( ) ,
737+ ) ;
738+ let cwd_arc: Arc < AbsolutePath > = cwd. clone ( ) . into ( ) ;
739+
740+ let status = match subcommand {
741+ SynthesizableSubcommand :: Check {
742+ fix,
743+ no_fmt,
744+ no_lint,
745+ no_type_aware,
746+ no_type_check,
747+ paths,
748+ } => {
749+ let mut status = ExitStatus :: SUCCESS ;
750+ let has_paths = !paths. is_empty ( ) ;
751+
752+ if !no_fmt {
753+ let mut args = if fix { vec ! [ ] } else { vec ! [ "--check" . to_string( ) ] } ;
754+ if has_paths {
755+ args. push ( "--no-error-on-unmatched-pattern" . to_string ( ) ) ;
756+ args. extend ( paths. iter ( ) . cloned ( ) ) ;
757+ }
758+ if args. is_empty ( ) {
759+ output:: info ( "vp fmt" ) ;
760+ } else {
761+ let cmd = vite_str:: format!( "vp fmt {}" , args. join( " " ) ) ;
762+ output:: info ( & cmd) ;
763+ }
764+ status = resolve_and_execute (
765+ & mut resolver,
766+ SynthesizableSubcommand :: Fmt { args } ,
767+ & envs,
768+ cwd,
769+ & cwd_arc,
770+ )
771+ . await ?;
772+ if status != ExitStatus :: SUCCESS {
773+ resolver. cleanup_temp_files ( ) . await ;
774+ return Ok ( status) ;
775+ }
776+ }
777+
778+ if !no_lint {
779+ let mut args = Vec :: new ( ) ;
780+ if fix {
781+ args. push ( "--fix" . to_string ( ) ) ;
782+ }
783+ if !no_type_aware {
784+ args. push ( "--type-aware" . to_string ( ) ) ;
785+ // --type-check requires --type-aware as prerequisite
786+ if !no_type_check {
787+ args. push ( "--type-check" . to_string ( ) ) ;
788+ }
789+ }
790+ if has_paths {
791+ args. extend ( paths. iter ( ) . cloned ( ) ) ;
792+ }
793+ if args. is_empty ( ) {
794+ output:: info ( "vp lint" ) ;
795+ } else {
796+ let cmd = vite_str:: format!( "vp lint {}" , args. join( " " ) ) ;
797+ output:: info ( & cmd) ;
798+ }
799+ status = resolve_and_execute (
800+ & mut resolver,
801+ SynthesizableSubcommand :: Lint { args } ,
802+ & envs,
803+ cwd,
804+ & cwd_arc,
805+ )
806+ . await ?;
807+ if status != ExitStatus :: SUCCESS {
808+ resolver. cleanup_temp_files ( ) . await ;
809+ return Ok ( status) ;
810+ }
811+ }
812+
813+ // Re-run fmt after lint --fix, since lint fixes can break formatting
814+ // (e.g. the curly rule adding braces to if-statements)
815+ if fix && !no_fmt && !no_lint {
816+ let mut args = Vec :: new ( ) ;
817+ if has_paths {
818+ args. push ( "--no-error-on-unmatched-pattern" . to_string ( ) ) ;
819+ args. extend ( paths. into_iter ( ) ) ;
820+ }
821+ status = resolve_and_execute (
822+ & mut resolver,
823+ SynthesizableSubcommand :: Fmt { args } ,
824+ & envs,
825+ cwd,
826+ & cwd_arc,
827+ )
828+ . await ?;
829+ }
830+
831+ status
832+ }
833+ other => resolve_and_execute ( & mut resolver, other, & envs, cwd, & cwd_arc) . await ?,
834+ } ;
698835
699836 resolver. cleanup_temp_files ( ) . await ;
700837
701- let status = status. map_err ( |e| Error :: Anyhow ( e. into ( ) ) ) ?;
702- Ok ( ExitStatus ( status. code ( ) . unwrap_or ( 1 ) as u8 ) )
838+ Ok ( status)
703839}
704840
705841/// Execute a vite-task command (run, cache) through Session.
@@ -817,6 +953,7 @@ fn print_help() {
817953 {bold}test{reset} Run tests
818954 {bold}lint{reset} Lint code
819955 {bold}fmt{reset} Format code
956+ {bold}check{reset} Run format, lint, and type checks
820957 {bold}pack{reset} Build library
821958 {bold}run{reset} Run tasks
822959 {bold}exec{reset} Execute a command from local node_modules/.bin
0 commit comments