Skip to content

Commit 9dc3176

Browse files
github-actions[bot]CopilotCopilotjamesadevine
authored
feat(ir): add typed helper for PublishPipelineArtifact@1 (#1065)
Adds `publish_pipeline_artifact_step()` to `src/compile/ir/tasks.rs`. The helper accepts the required `targetPath` as a positional parameter and exposes all optional inputs (artifact name, publishLocation, fileSharePath, parallel copy flags, custom properties) via the existing `.with_input()` builder on `TaskStep`. Four unit tests cover: - Default construction (task ID, display name, required input only) - Optional artifact name - Optional publishLocation - File share path variant Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
1 parent de0c8d5 commit 9dc3176

1 file changed

Lines changed: 89 additions & 1 deletion

File tree

src/compile/ir/tasks.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,37 @@ pub fn powershell_inline_step(script: impl Into<String>) -> TaskStep {
291291
.with_input("script", script)
292292
}
293293

294+
/// Returns a [`TaskStep`] for `PublishPipelineArtifact@1`.
295+
///
296+
/// Publishes (uploads) a file or directory as a named artifact for the
297+
/// current pipeline run. The artifact is stored in Azure Pipelines and
298+
/// can be downloaded by subsequent jobs or pipelines via
299+
/// `DownloadPipelineArtifact@2`.
300+
///
301+
/// - `target_path` — path of the file or directory to publish. Can be
302+
/// absolute or relative to the default working directory. Supports
303+
/// ADO macro variables (e.g. `$(Build.ArtifactStagingDirectory)`),
304+
/// but wildcards are **not** supported.
305+
///
306+
/// Optional inputs (applied with `.with_input(…)` on the returned
307+
/// value):
308+
///
309+
/// | Input key | Alias | Type | Default | Description |
310+
/// |---|---|---|---|---|
311+
/// | `artifact` | `artifactName` | string | *(unique job-scoped ID)* | Name of the published artifact (e.g. `"drop"`). May not contain `\`, `/`, `"`, `:`, `<`, `>`, `\|`, `*`, or `?`. |
312+
/// | `publishLocation` | `artifactType` | string | `"pipeline"` | Where to store the artifact: `"pipeline"` (Azure Pipelines) or `"filepath"` (a UNC file share). |
313+
/// | `fileSharePath` | — | string | — | Required when `publishLocation = filepath`. UNC path of the file share. |
314+
/// | `parallel` | — | bool string | `"false"` | Enable multi-threaded copy when `publishLocation = filepath`. |
315+
/// | `parallelCount` | — | string | `"8"` | Thread count for parallel copy (1–128). Applies when `parallel = true`. |
316+
/// | `properties` | — | string | — | JSON string of custom properties to associate with the artifact (keys must start with `user-`). |
317+
///
318+
/// ADO task reference:
319+
/// <https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/publish-pipeline-artifact-v1>
320+
pub fn publish_pipeline_artifact_step(target_path: impl Into<String>) -> TaskStep {
321+
TaskStep::new("PublishPipelineArtifact@1", "Publish Pipeline Artifact")
322+
.with_input("targetPath", target_path)
323+
}
324+
294325
/// Returns a [`TaskStep`] for `DownloadPipelineArtifact@2`.
295326
///
296327
/// Downloads artifacts produced by a pipeline run into `target_path`.
@@ -305,7 +336,7 @@ pub fn powershell_inline_step(script: impl Into<String>) -> TaskStep {
305336
/// Optional inputs (applied with `.with_input(…)` on the returned value):
306337
///
307338
/// | Input key | Type | Default | Description |
308-
/// |---|---|---|---|
339+
/// |---|---|---|
309340
/// | `artifact` | string | — | Name of the artifact to download. Omit to download all artifacts. |
310341
/// | `patterns` | string | `"**"` | Newline-separated glob patterns that filter which files inside the artifact are downloaded. |
311342
/// | `source` | string | `"current"` | `"current"` (this run) or `"specific"` (another run). |
@@ -878,6 +909,21 @@ mod tests {
878909
);
879910
}
880911

912+
// ── PublishPipelineArtifact@1 ─────────────────────────────────────────
913+
914+
#[test]
915+
fn publish_pipeline_artifact_step_sets_task_and_target_path() {
916+
let t = publish_pipeline_artifact_step("$(Build.ArtifactStagingDirectory)");
917+
assert_eq!(t.task, "PublishPipelineArtifact@1");
918+
assert_eq!(t.display_name, "Publish Pipeline Artifact");
919+
assert_eq!(
920+
t.inputs.get("targetPath").map(|s| s.as_str()),
921+
Some("$(Build.ArtifactStagingDirectory)")
922+
);
923+
// only the required input is set by default
924+
assert_eq!(t.inputs.len(), 1);
925+
}
926+
881927
// ── DownloadPipelineArtifact@2 ───────────────────────────────────────
882928

883929
#[test]
@@ -893,6 +939,18 @@ mod tests {
893939
assert_eq!(t.inputs.len(), 1);
894940
}
895941

942+
#[test]
943+
fn publish_pipeline_artifact_step_accepts_artifact_name() {
944+
let t = publish_pipeline_artifact_step("$(Build.ArtifactStagingDirectory)/output")
945+
.with_input("artifact", "drop");
946+
assert_eq!(t.task, "PublishPipelineArtifact@1");
947+
assert_eq!(
948+
t.inputs.get("artifact").map(|s| s.as_str()),
949+
Some("drop")
950+
);
951+
assert_eq!(t.inputs.len(), 2);
952+
}
953+
896954
#[test]
897955
fn download_pipeline_artifact_step_filters_by_artifact_name() {
898956
let t = download_pipeline_artifact_step("$(Agent.TempDirectory)/out")
@@ -909,6 +967,36 @@ mod tests {
909967
assert_eq!(t.inputs.len(), 2);
910968
}
911969

970+
#[test]
971+
fn publish_pipeline_artifact_step_accepts_publish_location() {
972+
let t = publish_pipeline_artifact_step("$(Build.ArtifactStagingDirectory)")
973+
.with_input("artifact", "binaries")
974+
.with_input("publishLocation", "pipeline");
975+
assert_eq!(t.task, "PublishPipelineArtifact@1");
976+
assert_eq!(
977+
t.inputs.get("publishLocation").map(|s| s.as_str()),
978+
Some("pipeline")
979+
);
980+
assert_eq!(t.inputs.len(), 3);
981+
}
982+
983+
#[test]
984+
fn publish_pipeline_artifact_step_accepts_file_share_path() {
985+
let t = publish_pipeline_artifact_step("$(Build.ArtifactStagingDirectory)")
986+
.with_input("publishLocation", "filepath")
987+
.with_input("fileSharePath", "\\\\myserver\\share\\$(Build.DefinitionName)");
988+
assert_eq!(t.task, "PublishPipelineArtifact@1");
989+
assert_eq!(
990+
t.inputs.get("publishLocation").map(|s| s.as_str()),
991+
Some("filepath")
992+
);
993+
assert_eq!(
994+
t.inputs.get("fileSharePath").map(|s| s.as_str()),
995+
Some("\\\\myserver\\share\\$(Build.DefinitionName)")
996+
);
997+
assert_eq!(t.inputs.len(), 3);
998+
}
999+
9121000
#[test]
9131001
fn download_pipeline_artifact_step_specific_source_with_branch() {
9141002
let t = download_pipeline_artifact_step("$(Agent.TempDirectory)/prev")

0 commit comments

Comments
 (0)