@@ -64,16 +64,48 @@ enum Step {
6464#[ derive( serde:: Deserialize , Debug ) ]
6565#[ serde( deny_unknown_fields) ]
6666struct StepConfig {
67- command : Str ,
67+ /// Shell command string (run via `sh -c`).
68+ #[ serde( default ) ]
69+ command : Option < Str > ,
70+ /// Argument vector — spawned directly without a shell wrapper.
71+ #[ serde( default ) ]
72+ argv : Option < Vec < Str > > ,
6873 #[ serde( default ) ]
6974 interactions : Vec < Interaction > ,
7075}
7176
77+ /// How to spawn a step: either via shell or directly.
78+ enum StepSpawn < ' a > {
79+ /// Run through `sh -c "<command>"`.
80+ Shell ( & ' a str ) ,
81+ /// Spawn directly with the given argv (first element is the program).
82+ Direct ( & ' a [ Str ] ) ,
83+ }
84+
7285impl Step {
73- fn command ( & self ) -> & str {
86+ fn spawn_mode ( & self ) -> StepSpawn < ' _ > {
7487 match self {
75- Self :: Command ( command) => command. as_str ( ) ,
76- Self :: Detailed ( config) => config. command . as_str ( ) ,
88+ Self :: Command ( command) => StepSpawn :: Shell ( command. as_str ( ) ) ,
89+ Self :: Detailed ( config) => config. argv . as_deref ( ) . map_or_else (
90+ || {
91+ StepSpawn :: Shell (
92+ config
93+ . command
94+ . as_ref ( )
95+ . expect ( "step must have either 'command' or 'argv'" )
96+ . as_str ( ) ,
97+ )
98+ } ,
99+ StepSpawn :: Direct ,
100+ ) ,
101+ }
102+ }
103+
104+ #[ expect( clippy:: disallowed_types, reason = "String required by join" ) ]
105+ fn display_command ( & self ) -> String {
106+ match self . spawn_mode ( ) {
107+ StepSpawn :: Shell ( cmd) => cmd. to_string ( ) ,
108+ StepSpawn :: Direct ( argv) => argv. iter ( ) . map ( Str :: as_str) . collect :: < Vec < _ > > ( ) . join ( " " ) ,
77109 }
78110 }
79111
@@ -276,10 +308,27 @@ fn run_case_inner(tmpdir: &AbsolutePath, fixture_path: &std::path::Path, fixture
276308
277309 let mut e2e_outputs = String :: new ( ) ;
278310 for step in & e2e. steps {
279- let step_command = step. command ( ) ;
280- let mut cmd = CommandBuilder :: new ( & shell_exe) ;
281- cmd. arg ( "-c" ) ;
282- cmd. arg ( step_command) ;
311+ let step_display = step. display_command ( ) ;
312+ let mut cmd = match step. spawn_mode ( ) {
313+ StepSpawn :: Shell ( command) => {
314+ let mut cmd = CommandBuilder :: new ( & shell_exe) ;
315+ cmd. arg ( "-c" ) ;
316+ cmd. arg ( command) ;
317+ cmd
318+ }
319+ StepSpawn :: Direct ( argv) => {
320+ // Resolve the program from CARGO_BIN_EXE_<name> if available,
321+ // since CommandBuilder doesn't do PATH lookup on all platforms.
322+ let program = argv[ 0 ] . as_str ( ) ;
323+ let exe_env = vite_str:: format!( "CARGO_BIN_EXE_{program}" ) ;
324+ let resolved = env:: var_os ( exe_env. as_str ( ) ) . unwrap_or_else ( || program. into ( ) ) ;
325+ let mut cmd = CommandBuilder :: new ( resolved) ;
326+ for arg in & argv[ 1 ..] {
327+ cmd. arg ( arg. as_str ( ) ) ;
328+ }
329+ cmd
330+ }
331+ } ;
283332 cmd. env_clear ( ) ;
284333 cmd. env ( "PATH" , & e2e_env_path) ;
285334 cmd. env ( "NO_COLOR" , "1" ) ;
@@ -390,7 +439,7 @@ fn run_case_inner(tmpdir: &AbsolutePath, fixture_path: &std::path::Path, fixture
390439 }
391440
392441 e2e_outputs. push_str ( "> " ) ;
393- e2e_outputs. push_str ( step_command ) ;
442+ e2e_outputs. push_str ( & step_display ) ;
394443 e2e_outputs. push ( '\n' ) ;
395444
396445 e2e_outputs. push_str ( & redact_e2e_output ( output, e2e_stage_path_str) ) ;
@@ -412,9 +461,6 @@ fn run_case_inner(tmpdir: &AbsolutePath, fixture_path: &std::path::Path, fixture
412461}
413462
414463fn main ( ) {
415- // SAFETY: Called before any threads are spawned; insta reads this lazily on first assertion.
416- unsafe { std:: env:: set_var ( "INSTA_REQUIRE_FULL_MATCH" , "1" ) } ;
417-
418464 let filter = std:: env:: args ( ) . nth ( 1 ) ;
419465
420466 let tmp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
@@ -430,7 +476,7 @@ fn main() {
430476
431477 // Copy .node-version to the tmp dir so version manager shims can resolve the correct
432478 // Node.js binary when running task commands.
433- let repo_root = manifest_dir. join ( "../.." ) . canonicalize ( ) . unwrap ( ) ;
479+ let repo_root = manifest_dir. parent ( ) . unwrap ( ) . parent ( ) . unwrap ( ) ;
434480 std:: fs:: copy ( repo_root. join ( ".node-version" ) , tmp_dir. path ( ) . join ( ".node-version" ) )
435481 . unwrap ( ) ;
436482
0 commit comments