@@ -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