@@ -380,21 +380,42 @@ impl Devenv {
380
380
Ok ( shell_cmd)
381
381
}
382
382
383
- /// Exec into the shell.
384
- /// This method does not return after calling `exec`.
385
- pub async fn shell ( self ) -> Result < ( ) > {
386
- let shell_cmd = self . prepare_shell ( & None , & [ ] ) . await ?;
383
+ /// Launch an interactive shell (uses exec, never returns).
384
+ pub async fn shell ( & self ) -> Result < ( ) > {
385
+ self . exec_in_shell ( None , & [ ] ) . await
386
+ }
387
+
388
+ /// Execute a command by replacing the current process using exec.
389
+ ///
390
+ /// This method accepts `Option<String>` for the command to support both:
391
+ /// - Interactive shell: `exec_in_shell(None, &[])`
392
+ /// - Command execution: `exec_in_shell(Some(cmd), args)`
393
+ ///
394
+ /// **Important**: This function never returns `Ok(())` on success because `exec()`
395
+ /// replaces the current process. The `Result<()>` return type only represents
396
+ /// potential errors during setup or if `exec()` fails to start the new process.
397
+ /// On successful exec, this function never returns.
398
+ pub async fn exec_in_shell ( & self , cmd : Option < String > , args : & [ String ] ) -> Result < ( ) > {
399
+ let shell_cmd = self . prepare_shell ( & cmd, args) . await ?;
387
400
info ! ( devenv. is_user_message = true , "Entering shell" ) ;
388
401
let err = shell_cmd. into_std ( ) . exec ( ) ;
389
- bail ! ( "Failed to execute shell: {}" , err) ;
402
+
403
+ let cmd_context = match & cmd {
404
+ Some ( c) => format ! ( "command '{}'" , c) ,
405
+ None => "interactive shell" . to_string ( ) ,
406
+ } ;
407
+ bail ! ( "Failed to exec into shell with {}: {}" , cmd_context, err) ;
390
408
}
391
409
392
- pub async fn exec_in_shell ( & self , cmd : String , args : & [ String ] ) -> Result < Output > {
410
+ /// Run a command and return the output.
411
+ ///
412
+ /// This method accepts `String` (not `Option<String>`) because it's specifically
413
+ /// designed for running commands and capturing their output. Unlike `exec_in_shell`,
414
+ /// this method always requires a command and uses `spawn` + `wait_with_output`
415
+ /// to return control to the caller with the command's output.
416
+ pub async fn run_in_shell ( & self , cmd : String , args : & [ String ] ) -> Result < Output > {
393
417
let mut shell_cmd = self . prepare_shell ( & Some ( cmd) , args) . await ?;
394
- let span = info_span ! (
395
- "executing_in_shell" ,
396
- devenv. user_message = "Executing in shell"
397
- ) ;
418
+ let span = info_span ! ( "running_in_shell" , devenv. user_message = "Running in shell" ) ;
398
419
// Note that tokio's `output()` always configures stdout/stderr as pipes.
399
420
// Use `spawn` + `wait_with_output` instead.
400
421
let proc = shell_cmd
0 commit comments