Skip to content

Commit c752f81

Browse files
feat(ir): add typed helpers for PowerShell@2 (#1051)
Add two factory functions to src/compile/ir/tasks.rs: - powershell_file_step(file_path): runs a .ps1 file via PowerShell@2 with targetType=filePath. Optional inputs: arguments, errorActionPreference, failOnStderr, ignoreLASTEXITCODE, pwsh, workingDirectory. - powershell_inline_step(script): runs an inline PowerShell block via PowerShell@2 with targetType=inline. Same optional inputs minus arguments. Both functions follow the same positional-required / optional-via- with_input pattern used by all other helpers in this module. Six unit tests cover required inputs, optional inputs, error/exit flags, multiline scripts, and both modes. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 106886d commit c752f81

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

src/compile/ir/tasks.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,53 @@ pub fn nuget_command_step(command: impl Into<String>) -> TaskStep {
244244
TaskStep::new("NuGetCommand@2", format!("NuGet {cmd}")).with_input("command", cmd)
245245
}
246246

247+
/// Returns a [`TaskStep`] for `PowerShell@2` in file-path mode.
248+
///
249+
/// Runs the PowerShell script at `file_path` on Linux, macOS, or Windows.
250+
/// `file_path` must be a fully qualified path or relative to
251+
/// `$(System.DefaultWorkingDirectory)`.
252+
///
253+
/// Optional inputs (applied via `.with_input(…)` on the returned value):
254+
///
255+
/// | Input key | Type | Default | Description |
256+
/// |---|---|---|---|
257+
/// | `arguments` | string | — | Arguments passed to the script. |
258+
/// | `errorActionPreference` | string | `"stop"` | Non-terminating error behaviour: `"stop"`, `"continue"`, `"silentlyContinue"`. |
259+
/// | `failOnStderr` | bool string | `"false"` | Fail the step if anything is written to stderr. |
260+
/// | `ignoreLASTEXITCODE` | bool string | `"false"` | Do not fail when `$LASTEXITCODE` is non-zero. |
261+
/// | `pwsh` | bool string | `"false"` | Use PowerShell Core (`pwsh`) instead of Windows PowerShell. |
262+
/// | `workingDirectory` | string | — | Working directory for the script. |
263+
///
264+
/// ADO task reference:
265+
/// <https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/powershell-v2>
266+
pub fn powershell_file_step(file_path: impl Into<String>) -> TaskStep {
267+
TaskStep::new("PowerShell@2", "PowerShell Script")
268+
.with_input("targetType", "filePath")
269+
.with_input("filePath", file_path)
270+
}
271+
272+
/// Returns a [`TaskStep`] for `PowerShell@2` in inline mode.
273+
///
274+
/// Runs `script` as an inline PowerShell block on Linux, macOS, or Windows.
275+
///
276+
/// Optional inputs (applied via `.with_input(…)` on the returned value):
277+
///
278+
/// | Input key | Type | Default | Description |
279+
/// |---|---|---|---|
280+
/// | `errorActionPreference` | string | `"stop"` | Non-terminating error behaviour: `"stop"`, `"continue"`, `"silentlyContinue"`. |
281+
/// | `failOnStderr` | bool string | `"false"` | Fail the step if anything is written to stderr. |
282+
/// | `ignoreLASTEXITCODE` | bool string | `"false"` | Do not fail when `$LASTEXITCODE` is non-zero. |
283+
/// | `pwsh` | bool string | `"false"` | Use PowerShell Core (`pwsh`) instead of Windows PowerShell. |
284+
/// | `workingDirectory` | string | — | Working directory for the script. |
285+
///
286+
/// ADO task reference:
287+
/// <https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/powershell-v2>
288+
pub fn powershell_inline_step(script: impl Into<String>) -> TaskStep {
289+
TaskStep::new("PowerShell@2", "PowerShell Script")
290+
.with_input("targetType", "inline")
291+
.with_input("script", script)
292+
}
293+
247294
#[cfg(test)]
248295
mod tests {
249296
use super::*;
@@ -682,4 +729,116 @@ mod tests {
682729
assert_eq!(t.inputs.get("command").map(|s| s.as_str()), Some(*cmd));
683730
}
684731
}
732+
733+
// ── PowerShell@2 ─────────────────────────────────────────────────────
734+
735+
#[test]
736+
fn powershell_file_step_sets_task_and_required_inputs() {
737+
let t = powershell_file_step("scripts/deploy.ps1");
738+
assert_eq!(t.task, "PowerShell@2");
739+
assert_eq!(t.display_name, "PowerShell Script");
740+
assert_eq!(
741+
t.inputs.get("targetType").map(|s| s.as_str()),
742+
Some("filePath")
743+
);
744+
assert_eq!(
745+
t.inputs.get("filePath").map(|s| s.as_str()),
746+
Some("scripts/deploy.ps1")
747+
);
748+
// only the required inputs are set by default
749+
assert_eq!(t.inputs.len(), 2);
750+
}
751+
752+
#[test]
753+
fn powershell_file_step_accepts_optional_arguments() {
754+
let t = powershell_file_step("$(System.DefaultWorkingDirectory)/scripts/build.ps1")
755+
.with_input("arguments", "-Configuration Release -OutputDir $(Build.ArtifactStagingDirectory)")
756+
.with_input("workingDirectory", "$(Build.SourcesDirectory)");
757+
assert_eq!(t.task, "PowerShell@2");
758+
assert_eq!(
759+
t.inputs.get("filePath").map(|s| s.as_str()),
760+
Some("$(System.DefaultWorkingDirectory)/scripts/build.ps1")
761+
);
762+
assert_eq!(
763+
t.inputs.get("arguments").map(|s| s.as_str()),
764+
Some("-Configuration Release -OutputDir $(Build.ArtifactStagingDirectory)")
765+
);
766+
assert_eq!(
767+
t.inputs.get("workingDirectory").map(|s| s.as_str()),
768+
Some("$(Build.SourcesDirectory)")
769+
);
770+
assert_eq!(t.inputs.len(), 4);
771+
}
772+
773+
#[test]
774+
fn powershell_file_step_accepts_error_and_exit_flags() {
775+
let t = powershell_file_step("scripts/test.ps1")
776+
.with_input("errorActionPreference", "continue")
777+
.with_input("failOnStderr", "true")
778+
.with_input("ignoreLASTEXITCODE", "true")
779+
.with_input("pwsh", "true");
780+
assert_eq!(t.task, "PowerShell@2");
781+
assert_eq!(
782+
t.inputs.get("errorActionPreference").map(|s| s.as_str()),
783+
Some("continue")
784+
);
785+
assert_eq!(
786+
t.inputs.get("failOnStderr").map(|s| s.as_str()),
787+
Some("true")
788+
);
789+
assert_eq!(
790+
t.inputs.get("ignoreLASTEXITCODE").map(|s| s.as_str()),
791+
Some("true")
792+
);
793+
assert_eq!(t.inputs.get("pwsh").map(|s| s.as_str()), Some("true"));
794+
assert_eq!(t.inputs.len(), 6);
795+
}
796+
797+
#[test]
798+
fn powershell_inline_step_sets_task_and_required_inputs() {
799+
let script = "Write-Host 'Hello, World!'";
800+
let t = powershell_inline_step(script);
801+
assert_eq!(t.task, "PowerShell@2");
802+
assert_eq!(t.display_name, "PowerShell Script");
803+
assert_eq!(
804+
t.inputs.get("targetType").map(|s| s.as_str()),
805+
Some("inline")
806+
);
807+
assert_eq!(
808+
t.inputs.get("script").map(|s| s.as_str()),
809+
Some("Write-Host 'Hello, World!'")
810+
);
811+
// only the required inputs are set by default
812+
assert_eq!(t.inputs.len(), 2);
813+
}
814+
815+
#[test]
816+
fn powershell_inline_step_accepts_optional_flags() {
817+
let t = powershell_inline_step("Get-Date")
818+
.with_input("pwsh", "true")
819+
.with_input("errorActionPreference", "silentlyContinue")
820+
.with_input("workingDirectory", "$(Build.SourcesDirectory)");
821+
assert_eq!(t.task, "PowerShell@2");
822+
assert_eq!(t.inputs.get("pwsh").map(|s| s.as_str()), Some("true"));
823+
assert_eq!(
824+
t.inputs.get("errorActionPreference").map(|s| s.as_str()),
825+
Some("silentlyContinue")
826+
);
827+
assert_eq!(
828+
t.inputs.get("workingDirectory").map(|s| s.as_str()),
829+
Some("$(Build.SourcesDirectory)")
830+
);
831+
assert_eq!(t.inputs.len(), 5);
832+
}
833+
834+
#[test]
835+
fn powershell_inline_step_multiline_script() {
836+
let script = "$version = Get-Content VERSION\nWrite-Host \"Building version $version\"";
837+
let t = powershell_inline_step(script);
838+
assert_eq!(t.task, "PowerShell@2");
839+
assert_eq!(
840+
t.inputs.get("script").map(|s| s.as_str()),
841+
Some("$version = Get-Content VERSION\nWrite-Host \"Building version $version\"")
842+
);
843+
}
685844
}

0 commit comments

Comments
 (0)