diff --git a/src/compile/ir/tasks.rs b/src/compile/ir/tasks.rs index 31b0fda2..38b2483c 100644 --- a/src/compile/ir/tasks.rs +++ b/src/compile/ir/tasks.rs @@ -291,6 +291,37 @@ pub fn powershell_inline_step(script: impl Into) -> TaskStep { .with_input("script", script) } +/// Returns a [`TaskStep`] for `PublishPipelineArtifact@1`. +/// +/// Publishes (uploads) a file or directory as a named artifact for the +/// current pipeline run. The artifact is stored in Azure Pipelines and +/// can be downloaded by subsequent jobs or pipelines via +/// `DownloadPipelineArtifact@2`. +/// +/// - `target_path` — path of the file or directory to publish. Can be +/// absolute or relative to the default working directory. Supports +/// ADO macro variables (e.g. `$(Build.ArtifactStagingDirectory)`), +/// but wildcards are **not** supported. +/// +/// Optional inputs (applied with `.with_input(…)` on the returned +/// value): +/// +/// | Input key | Alias | Type | Default | Description | +/// |---|---|---|---|---| +/// | `artifact` | `artifactName` | string | *(unique job-scoped ID)* | Name of the published artifact (e.g. `"drop"`). May not contain `\`, `/`, `"`, `:`, `<`, `>`, `\|`, `*`, or `?`. | +/// | `publishLocation` | `artifactType` | string | `"pipeline"` | Where to store the artifact: `"pipeline"` (Azure Pipelines) or `"filepath"` (a UNC file share). | +/// | `fileSharePath` | — | string | — | Required when `publishLocation = filepath`. UNC path of the file share. | +/// | `parallel` | — | bool string | `"false"` | Enable multi-threaded copy when `publishLocation = filepath`. | +/// | `parallelCount` | — | string | `"8"` | Thread count for parallel copy (1–128). Applies when `parallel = true`. | +/// | `properties` | — | string | — | JSON string of custom properties to associate with the artifact (keys must start with `user-`). | +/// +/// ADO task reference: +/// +pub fn publish_pipeline_artifact_step(target_path: impl Into) -> TaskStep { + TaskStep::new("PublishPipelineArtifact@1", "Publish Pipeline Artifact") + .with_input("targetPath", target_path) +} + /// Returns a [`TaskStep`] for `DownloadPipelineArtifact@2`. /// /// Downloads artifacts produced by a pipeline run into `target_path`. @@ -305,7 +336,7 @@ pub fn powershell_inline_step(script: impl Into) -> TaskStep { /// Optional inputs (applied with `.with_input(…)` on the returned value): /// /// | Input key | Type | Default | Description | -/// |---|---|---|---| +/// |---|---|---| /// | `artifact` | string | — | Name of the artifact to download. Omit to download all artifacts. | /// | `patterns` | string | `"**"` | Newline-separated glob patterns that filter which files inside the artifact are downloaded. | /// | `source` | string | `"current"` | `"current"` (this run) or `"specific"` (another run). | @@ -878,6 +909,21 @@ mod tests { ); } + // ── PublishPipelineArtifact@1 ───────────────────────────────────────── + + #[test] + fn publish_pipeline_artifact_step_sets_task_and_target_path() { + let t = publish_pipeline_artifact_step("$(Build.ArtifactStagingDirectory)"); + assert_eq!(t.task, "PublishPipelineArtifact@1"); + assert_eq!(t.display_name, "Publish Pipeline Artifact"); + assert_eq!( + t.inputs.get("targetPath").map(|s| s.as_str()), + Some("$(Build.ArtifactStagingDirectory)") + ); + // only the required input is set by default + assert_eq!(t.inputs.len(), 1); + } + // ── DownloadPipelineArtifact@2 ─────────────────────────────────────── #[test] @@ -893,6 +939,18 @@ mod tests { assert_eq!(t.inputs.len(), 1); } + #[test] + fn publish_pipeline_artifact_step_accepts_artifact_name() { + let t = publish_pipeline_artifact_step("$(Build.ArtifactStagingDirectory)/output") + .with_input("artifact", "drop"); + assert_eq!(t.task, "PublishPipelineArtifact@1"); + assert_eq!( + t.inputs.get("artifact").map(|s| s.as_str()), + Some("drop") + ); + assert_eq!(t.inputs.len(), 2); + } + #[test] fn download_pipeline_artifact_step_filters_by_artifact_name() { let t = download_pipeline_artifact_step("$(Agent.TempDirectory)/out") @@ -909,6 +967,36 @@ mod tests { assert_eq!(t.inputs.len(), 2); } + #[test] + fn publish_pipeline_artifact_step_accepts_publish_location() { + let t = publish_pipeline_artifact_step("$(Build.ArtifactStagingDirectory)") + .with_input("artifact", "binaries") + .with_input("publishLocation", "pipeline"); + assert_eq!(t.task, "PublishPipelineArtifact@1"); + assert_eq!( + t.inputs.get("publishLocation").map(|s| s.as_str()), + Some("pipeline") + ); + assert_eq!(t.inputs.len(), 3); + } + + #[test] + fn publish_pipeline_artifact_step_accepts_file_share_path() { + let t = publish_pipeline_artifact_step("$(Build.ArtifactStagingDirectory)") + .with_input("publishLocation", "filepath") + .with_input("fileSharePath", "\\\\myserver\\share\\$(Build.DefinitionName)"); + assert_eq!(t.task, "PublishPipelineArtifact@1"); + assert_eq!( + t.inputs.get("publishLocation").map(|s| s.as_str()), + Some("filepath") + ); + assert_eq!( + t.inputs.get("fileSharePath").map(|s| s.as_str()), + Some("\\\\myserver\\share\\$(Build.DefinitionName)") + ); + assert_eq!(t.inputs.len(), 3); + } + #[test] fn download_pipeline_artifact_step_specific_source_with_branch() { let t = download_pipeline_artifact_step("$(Agent.TempDirectory)/prev")