@@ -11,6 +11,7 @@ use std::{
1111use bincode:: { Decode , Encode } ;
1212use fspy:: { AccessMode , Spy } ;
1313use futures_util:: future:: try_join3;
14+ use itertools:: Itertools ;
1415use serde:: { Deserialize , Serialize } ;
1516use sha2:: { Digest , Sha256 } ;
1617use supports_color:: { Stream , on} ;
@@ -348,6 +349,36 @@ pub async fn execute_task(
348349 cmd
349350 }
350351 TaskCommand :: Parsed ( task_parsed_command) => {
352+ // handle shell built-ins
353+ match task_parsed_command. program . as_str ( ) {
354+ "echo" => {
355+ let mut prints_new_line = true ;
356+ let mut args = task_parsed_command. args . as_slice ( ) ;
357+ if let Some ( first_arg) = args. first ( )
358+ && first_arg == "-n"
359+ {
360+ prints_new_line = false ;
361+ args = & args[ 1 ..] ;
362+ }
363+ let mut output = args. iter ( ) . map ( |arg| arg. as_str ( ) ) . join ( " " ) ;
364+ if prints_new_line {
365+ output. push ( '\n' ) ;
366+ }
367+ print ! ( "{output}" ) ;
368+ return Ok ( ExecutedTask {
369+ std_outputs : vec ! [ StdOutput {
370+ kind: OutputKind :: StdOut ,
371+ content: Vec :: <u8 >:: from( output) . into( ) ,
372+ } ]
373+ . into ( ) ,
374+ exit_status : ExitStatus :: default ( ) ,
375+ path_reads : Default :: default ( ) ,
376+ path_writes : Default :: default ( ) ,
377+ duration : Duration :: ZERO ,
378+ } ) ;
379+ }
380+ _ => { }
381+ }
351382 if resolved_command. fingerprint . command . need_skip_cache ( ) {
352383 let mut child = tokio:: process:: Command :: new ( & task_parsed_command. program )
353384 . args ( & task_parsed_command. args )
0 commit comments