From daa42395c1cf39c52f727bee1a4ffd53c584d009 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 01:35:55 +0000 Subject: [PATCH] feat(ir): add typed helper for CmdLine@2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `cmd_line_step()` to `src/compile/ir/tasks.rs` — a typed factory for the `CmdLine@2` ADO task that runs an inline script on Linux/macOS (Bash) or Windows (cmd.exe). The helper takes the required `script` input as a positional parameter; optional inputs (`workingDirectory`, `failOnStderr`) are applied via `.with_input()` on the returned `TaskStep`. Four unit tests cover the basic shape, working-directory override, fail-on-stderr flag, and multiline script bodies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/compile/ir/tasks.rs | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/compile/ir/tasks.rs b/src/compile/ir/tasks.rs index 389be749..c0e75582 100644 --- a/src/compile/ir/tasks.rs +++ b/src/compile/ir/tasks.rs @@ -381,6 +381,28 @@ pub fn delete_files_step(contents: impl Into) -> TaskStep { .with_input("Contents", contents) } +/// Returns a [`TaskStep`] for `CmdLine@2`. +/// +/// Runs an inline command-line script. On Linux and macOS the script +/// is executed with Bash; on Windows it runs in `cmd.exe`. This makes +/// `CmdLine@2` the cross-platform sibling of the `bash:` step shorthand. +/// +/// - `script` — the inline script text to execute (required). Maps to +/// the `script` ADO task input. +/// +/// Optional inputs (applied via `.with_input(…)` on the returned value): +/// +/// | Input key | Type | Default | Description | +/// |---|---|---|---| +/// | `workingDirectory` | string | — | Working directory in which to run the script. | +/// | `failOnStderr` | bool string | `"false"` | Fail the step if the script writes anything to stderr. | +/// +/// ADO task reference: +/// +pub fn cmd_line_step(script: impl Into) -> TaskStep { + TaskStep::new("CmdLine@2", "Command Line Script").with_input("script", script) +} + #[cfg(test)] mod tests { use super::*; @@ -993,6 +1015,53 @@ mod tests { ); } + // ── CmdLine@2 ──────────────────────────────────────────────────────── + + #[test] + fn cmd_line_step_sets_task_and_required_input() { + let t = cmd_line_step("echo hello"); + assert_eq!(t.task, "CmdLine@2"); + assert_eq!(t.display_name, "Command Line Script"); + assert_eq!( + t.inputs.get("script").map(|s| s.as_str()), + Some("echo hello") + ); + // only the required input is set by default + assert_eq!(t.inputs.len(), 1); + } + + #[test] + fn cmd_line_step_accepts_working_directory() { + let t = cmd_line_step("dir /b") + .with_input("workingDirectory", "$(Build.SourcesDirectory)"); + assert_eq!(t.task, "CmdLine@2"); + assert_eq!( + t.inputs.get("workingDirectory").map(|s| s.as_str()), + Some("$(Build.SourcesDirectory)") + ); + assert_eq!(t.inputs.len(), 2); + } + + #[test] + fn cmd_line_step_accepts_fail_on_stderr() { + let t = cmd_line_step("my-tool --verbose").with_input("failOnStderr", "true"); + assert_eq!(t.task, "CmdLine@2"); + assert_eq!( + t.inputs.get("failOnStderr").map(|s| s.as_str()), + Some("true") + ); + assert_eq!(t.inputs.len(), 2); + } + + #[test] + fn cmd_line_step_accepts_multiline_script() { + let script = "echo step 1\necho step 2\necho step 3"; + let t = cmd_line_step(script); + assert_eq!(t.task, "CmdLine@2"); + assert_eq!(t.inputs.get("script").map(|s| s.as_str()), Some(script)); + assert_eq!(t.inputs.len(), 1); + } + // ── PublishPipelineArtifact@1 ───────────────────────────────────────── #[test]