diff --git a/Cargo.lock b/Cargo.lock index 7e973a1..ddf4d22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,7 +199,7 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jake" -version = "0.10.0" +version = "0.10.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 136ccfd..ff7f753 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jake" -version = "0.10.0" +version = "0.10.1" edition = "2024" license-file = "LICENSE" readme = "README.md" diff --git a/README.md b/README.md index 6479d3d..d25fd25 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,20 @@ echo 'hello back' echo 'bye' ``` +**Suppress command echo** + +By default, `jake` prints each command before executing it (to `stderr`). Use `--no-print-command` to suppress this output: + +```bash +jake say-hello --no-print-command +``` + +```text +hello +``` + +This only affects non-dry runs; `--dry-run` always prints commands. + ## In GitHub CI/CD You can use the [setup-jake](https://github.com/AstraBert/setup-jake) action to set up jake and use it in your GitHub Actions workflows. diff --git a/package.json b/package.json index 318e9e0..a318dfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cle-does-things/jake", - "version": "0.10.0", + "version": "0.10.1", "description": "A Make-like task executor for Unix operating systems", "main": "start.js", "directories": { diff --git a/pre-install.js b/pre-install.js index 8cfcda7..26055e7 100644 --- a/pre-install.js +++ b/pre-install.js @@ -31,8 +31,8 @@ const features = process.env.npm_config_features ? `--features ${process.env.npm_config_features.replace(",", " ")}` : ""; -console.log(`Installing and compiling jake 0.10.0 ${features} ...`); -exec(`cargo install jake --vers 0.10.0 ${features}`, (error, stdout, stderr) => { +console.log(`Installing and compiling jake 0.10.1 ${features} ...`); +exec(`cargo install jake --vers 0.10.1 ${features}`, (error, stdout, stderr) => { console.log(stdout); if (error || stderr) { console.log(error || stderr); diff --git a/reference/contents/features.md b/reference/contents/features.md index 79e62f8..0acf449 100644 --- a/reference/contents/features.md +++ b/reference/contents/features.md @@ -27,6 +27,7 @@ additional options from the command line. directly in `jakefile.toml` using the `script` key - **Custom working directory**: with `--cwd`, you can point `jake` to any directory to use as the working directory for command execution, enabling workspace-wide task running - **Dry-run**: `--dry-run` prints the commands that would be run without executing them +- **Suppress command echo**: `--no-print-command` hides the command before execution output ### Comparison @@ -51,6 +52,7 @@ The table below compares `jake` against [`just`](https://github.com/casey/just) | Execute scripts in `package.json` | ✅ | ❌ | ❌ | | Custom working directory (`--cwd`) | ✅ | ❌ | ❌ | | Dry-run (print commands only) | ✅ | ✅ | ✅ | +| Suppress command echo (`--no-print-command`) | ✅ | ❌ | ❌ | ⚠️ `make` supports passing variables from the command line but not named options in the same ergonomic way. diff --git a/reference/contents/reference.md b/reference/contents/reference.md index c6d09aa..418531d 100644 --- a/reference/contents/reference.md +++ b/reference/contents/reference.md @@ -190,6 +190,20 @@ echo 'hello back' echo 'bye' ``` +**Suppress command echo** + +By default, `jake` prints each command before executing it (to `stderr`). Use `--no-print-command` to suppress this output: + +```bash +jake say-hello --no-print-command +``` + +```text +hello +``` + +This only affects non-dry runs; `--dry-run` always prints commands. + **Pass additional options to a task** You can forward extra flags to the underlying command using `--options`: diff --git a/src/load.rs b/src/load.rs index 03130c6..4759e5e 100644 --- a/src/load.rs +++ b/src/load.rs @@ -196,6 +196,7 @@ pub fn execute_command( flags: &str, executor: &dyn Executor, load_env: bool, + print_command: bool, ) -> Result<()> { if task.is_empty() { return Ok(()); @@ -215,9 +216,15 @@ pub fn execute_command( // the actual command to execute is the last one in the execution order let cmd = &execution_order[execution_order.len() - 1]; if cmd_options.is_empty() { + if print_command { + eprintln!("\x1b[38;5;247m{}\x1b[0m\n", cmd) + } executor.execute(cmd, load_env, cwd)?; } else { let full_command = cmd.to_owned() + " " + cmd_options.join(" ").as_str(); + if print_command { + eprintln!("\x1b[38;5;247m{}\x1b[0m\n", full_command) + } executor.execute(&full_command, load_env, cwd)?; } Ok(()) @@ -228,16 +235,31 @@ pub fn execute_default_command( flags: &str, executor: &dyn Executor, load_env: bool, + print_command: bool, ) -> Result<()> { let (available_tasks, _) = parse_jakefile(jakefile_path)?; if available_tasks.contains_key("default") { - execute_command(jakefile_path, "default", flags, executor, load_env)?; + execute_command( + jakefile_path, + "default", + flags, + executor, + load_env, + print_command, + )?; } else { let first_key = available_tasks.keys().next(); match first_key { None => return Err(anyhow!("could not find any task within jakefile")), Some(task) => { - execute_command(jakefile_path, task, flags, executor, load_env)?; + execute_command( + jakefile_path, + task, + flags, + executor, + load_env, + print_command, + )?; } } } @@ -331,6 +353,7 @@ mod tests { "-la /hello/something", &mock_executor, false, + false, ); assert!(result.is_ok()); let mock_content = @@ -342,13 +365,19 @@ mod tests { "", &mock_executor, false, + false, ); assert!(result_1.is_ok()); let mock_content_1 = std::fs::read_to_string("test.mock").expect("Should be able to read test.mock"); assert_eq!(mock_content_1.trim(), "ls"); - let result_2 = - execute_default_command(Some("testfiles/jakefile.toml"), "", &mock_executor, false); + let result_2 = execute_default_command( + Some("testfiles/jakefile.toml"), + "", + &mock_executor, + false, + false, + ); assert!(result_2.is_ok()); let mock_content_2 = std::fs::read_to_string("test.mock").expect("Should be able to read test.mock"); @@ -358,6 +387,7 @@ mod tests { "", &mock_executor, false, + false, ); assert!(result_3.is_ok()); let mock_content_3 = @@ -369,6 +399,7 @@ mod tests { "", &mock_executor, false, + false, ); assert!(result_4.is_ok()); let mock_content_4 = @@ -386,6 +417,7 @@ mod tests { "", &mock_executor, false, + false, ); assert!(result.is_ok()); let mock_content = @@ -397,6 +429,7 @@ mod tests { "", &mock_executor, false, + false, ); assert!(result_1.is_ok()); let mock_content_1 = @@ -408,6 +441,7 @@ mod tests { "", &mock_executor, false, + false, ); assert!(result_2.is_ok()); let mock_content_2 = @@ -425,6 +459,7 @@ mod tests { "", &executor, false, + false, ); assert!(result.is_err_and(|e| e.to_string() == "Script could not be parsed correctly".to_string())) } @@ -433,7 +468,14 @@ mod tests { #[serial] fn test_script_execution_unsupported_lang() { let executor = CommandExecutor::new(); - let result = execute_command(Some("testfiles/scripts.toml"), "rust", "", &executor, false); + let result = execute_command( + Some("testfiles/scripts.toml"), + "rust", + "", + &executor, + false, + false, + ); assert!(result.is_err_and( |e| e.to_string() == "rs is not a supported language for jake scripts".to_string() )) @@ -449,6 +491,7 @@ mod tests { "", &executor, false, + false, ); assert!(result.is_ok()); } @@ -463,6 +506,7 @@ mod tests { "", &executor, false, + false, ); assert_eq!( result.is_err_and(|e| { @@ -484,6 +528,7 @@ mod tests { "", &executor, false, + false, ); assert_eq!( result.is_err_and(|e| e.to_string() @@ -502,6 +547,7 @@ mod tests { "", &executor, false, + false, ); assert_eq!(result.is_err_and( |e| e.to_string() == "`command` or `script` key not available for the requested task: ensure that there are no typos and the TOML syntax is correct before running again".to_string() @@ -518,6 +564,7 @@ mod tests { "", &executor, false, + false, ); assert_eq!( result.is_err_and(|e| e.to_string() @@ -536,6 +583,7 @@ mod tests { "", &executor, false, + false, ); assert!(result.is_ok()); } @@ -550,6 +598,7 @@ mod tests { "", &executor, false, + false, ); assert!(result.is_ok()); } @@ -558,8 +607,13 @@ mod tests { #[serial] fn test_default_command_with_default() { let executor = CommandExecutor::new(); - let result = - execute_default_command(Some("testfiles/withdefault.toml"), "", &executor, false); + let result = execute_default_command( + Some("testfiles/withdefault.toml"), + "", + &executor, + false, + false, + ); assert!(result.is_ok()); } @@ -567,7 +621,8 @@ mod tests { #[serial] fn test_default_command_first_key() { let executor = CommandExecutor::new(); - let result = execute_default_command(Some("testfiles/jakefile.toml"), "", &executor, false); + let result = + execute_default_command(Some("testfiles/jakefile.toml"), "", &executor, false, false); assert!(result.is_ok()); } @@ -581,6 +636,7 @@ mod tests { "", &executor, false, + false, ); assert_eq!( result.is_err_and(|e| e.to_string() @@ -599,6 +655,7 @@ mod tests { "", &executor, false, + false, ); assert_eq!( result.is_err_and(|e| e.to_string() @@ -618,6 +675,7 @@ mod tests { "", &executor, false, + false, ); assert_eq!( result.is_err_and(|e| e.to_string() @@ -636,6 +694,7 @@ mod tests { "", &executor, false, + false, ); assert_eq!( result.is_err_and( @@ -655,6 +714,7 @@ mod tests { "", &executor, true, + false, ); assert!(result.is_ok()); } @@ -717,7 +777,7 @@ mod tests { fn test_dry_run_executor_command() { let path = Some("testfiles/jakefile.toml"); let executor = DryRunExecutor::new(); - let result = execute_command(path, "say-hello", "", &executor, false); + let result = execute_command(path, "say-hello", "", &executor, false, false); assert!(result.is_ok()); } @@ -726,7 +786,7 @@ mod tests { fn test_dry_run_executor_default_command() { let path = Some("testfiles/withdefault.toml"); let executor = DryRunExecutor::new(); - let result = execute_default_command(path, "", &executor, false); + let result = execute_default_command(path, "", &executor, false, false); assert!(result.is_ok()); } @@ -735,7 +795,7 @@ mod tests { fn test_fails_on_non_zero_exit_code() { let path = Some("testfiles/jakefile.toml"); let executor = CommandExecutor::new(); - let result = execute_command(path, "fails", "", &executor, false); + let result = execute_command(path, "fails", "", &executor, false, false); assert!(result.is_err_and(|e| { e.to_string() .starts_with("Command exited with a non-zero exit code:") diff --git a/src/main.rs b/src/main.rs index 47c5be5..35f6632 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ mod package_json; /// Make-like task executor for Unix-based operating systems #[derive(Parser, Debug)] -#[command(version = "0.10.0")] +#[command(version = "0.10.1")] #[command(name = "jake")] #[command(about, long_about = None)] struct Args { @@ -54,6 +54,12 @@ struct Args { /// for the command execution #[arg(long, default_value = None)] cwd: Option, + + /// Do not print the command + /// that you are about to execute + /// (only applicable for non-dry runs) + #[arg(long, default_value_t = false)] + no_print_command: bool, } fn main() -> anyhow::Result<()> { @@ -73,10 +79,10 @@ fn main() -> anyhow::Result<()> { println!("Available tasks:\n- {}\n", task_list); return Ok(()); } - let executor: Box = if args.dry_run { - Box::new(DryRunExecutor::new()) + let (executor, print_command): (Box, bool) = if args.dry_run { + (Box::new(DryRunExecutor::new()), false) } else { - Box::new(CommandExecutor::new()) + (Box::new(CommandExecutor::new()), !args.no_print_command) }; if args.js { if let Some(script_name) = args.task { @@ -86,7 +92,13 @@ fn main() -> anyhow::Result<()> { .to_string_lossy() .to_string() }); - execute_script(package_json_path, script_name, args.env, executor.as_ref())?; + execute_script( + package_json_path, + script_name, + args.env, + executor.as_ref(), + print_command, + )?; } else { return Err(anyhow!( "No script name provided, please provide one or, if you wish to execute the default command from jakefile.toml, do not pass the `--js` flag." @@ -108,6 +120,7 @@ fn main() -> anyhow::Result<()> { &args.options, executor.as_ref(), args.env, + print_command, )? } None => { @@ -122,6 +135,7 @@ fn main() -> anyhow::Result<()> { &args.options, executor.as_ref(), args.env, + print_command, )? } } diff --git a/src/package_json.rs b/src/package_json.rs index 05d3f84..930c2de 100644 --- a/src/package_json.rs +++ b/src/package_json.rs @@ -129,6 +129,7 @@ pub fn execute_script( script_name: String, load_env: bool, executor: &dyn Executor, + print_command: bool, ) -> Result<()> { let owned_path; let (path, cwd) = match package_json_path { @@ -154,7 +155,9 @@ pub fn execute_script( let scripts = load_scripts(map)?; let command = get_script_command(scripts, script_name)?; let resolved_command = resolve_command_executables(&command); - println!("\x1b[38;5;247m{}\x1b[0m\n", command); + if print_command { + eprintln!("\x1b[38;5;247m{}\x1b[0m\n", command); + } executor.execute( &resolved_command, load_env, @@ -323,7 +326,7 @@ mod tests { fn test_mock_command_execution() { let executor = MockCommandExecutor::new(); let path = Some("testfiles/test-package.json".to_string()); - let result = execute_script(path, "test".to_string(), false, &executor); + let result = execute_script(path, "test".to_string(), false, &executor, false); assert!(result.is_ok()); let content = fs::read_to_string("package.mock").expect("Should be able to read file"); assert_eq!(content.trim(), "true"); @@ -334,7 +337,7 @@ mod tests { fn test_command_execution() { let executor = CommandExecutor::new(); let path = Some("testfiles/test-package.json".to_string()); - let result = execute_script(path, "test".to_string(), false, &executor); + let result = execute_script(path, "test".to_string(), false, &executor, false); assert!(result.is_ok()); } @@ -343,7 +346,7 @@ mod tests { fn test_dry_run_command_execution() { let executor = DryRunExecutor::new(); let path = Some("testfiles/test-package.json".to_string()); - let result = execute_script(path, "test".to_string(), false, &executor); + let result = execute_script(path, "test".to_string(), false, &executor, false); assert!(result.is_ok()); }