Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions eng/pipelines/common/templates/jobs/publish-packages-job.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#################################################################################
# Licensed to the .NET Foundation under one or more agreements. #
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
parameters:
# Approval aliases for manual validation before publishing packages
- name: approvalAliases
type: string
default: '[ADO.Net]\\SqlClient Admins'

# Where to publish the packages: 'Internal' or 'Public' feed
- name: publishDestination
type: string

# Boolean value to indicate whether to perform a dry run or actual publish
- name: dryRun
type: boolean

# Boolean value to indicate if the build is a preview release build
- name: isPreview
type: boolean

# Internal feed source URL for publishing packages to Azure DevOps Feed
- name: internalFeedSource
type: string

# Public NuGet source URL for publishing packages to public feed
- name: publicNuGetSource
type: string

# Boolean value to indicate whether to publish symbols
- name: publishSymbols
type: boolean

# Name of the folder containing the packages to be published
- name: packageFolderName
type: string

# NuGet package version to be published
- name: nugetPackageVersion
type: string

# Product name associated with the packages
- name: product
type: string

jobs:
- job: AwaitApproval
displayName: "Await Release Approval"
pool: server
steps:
- task: ManualValidation@0
displayName: "Manual Approval"
timeoutInMinutes: 4320 # 3 days
inputs:
notifyUsers: ${{ parameters.approvalAliases }}
instructions: |
Release Checklist:
* Destination: ${{ parameters.publishDestination }}
* Preview build: ${{ parameters.isPreview }}
* Dry run: ${{ parameters.dryRun }}
* Symbols: ${{ parameters.publishSymbols }}
* NuGet package version: ${{ parameters.nugetPackageVersion }}
* Product: ${{ parameters.product }}
Approve to continue or Reject to abort.

- job: PublishPackages
displayName: "Publish Packages"
variables:
- name: targetDownloadPath
value: "$(Pipeline.Workspace)/release/packages"
dependsOn: AwaitApproval
condition: succeeded()
pool:
vmImage: "ubuntu-latest"
steps:
- task: DownloadPipelineArtifact@2
displayName: "Download Signed Packages"
inputs:
buildType: current
artifactName: ${{ parameters.packageFolderName }}
targetPath: ${{ variables.targetDownloadPath }}
- script: |
echo "NuGet Package Version: ${{ parameters.nugetPackageVersion }}"
echo "Downloaded signed packages to: ${{ variables.targetDownloadPath }}"
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as line 83 - the variable targetDownloadPath should be referenced using runtime syntax $(targetDownloadPath) instead of template syntax ${{ variables.targetDownloadPath }}.

Copilot uses AI. Check for mistakes.
displayName: "Echo NuGet Package Version"
# Push to Public NuGet Feed if publishDestination is 'Public'
- ${{ if eq(parameters.publishDestination, 'Public') }}:
- template: ../steps/publish-public-nuget-step.yml
parameters:
dryRun: ${{ parameters.dryRun }}
publicNuGetSource: ${{ parameters.publicNuGetSource }}
packagesGlob: ${{ variables.targetDownloadPath }}/*.nupkg
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as line 83 - the variable targetDownloadPath should be referenced using runtime syntax $(targetDownloadPath) instead of template syntax ${{ variables.targetDownloadPath }}.

Copilot uses AI. Check for mistakes.
# Else Push to Internal Feed
- ${{ else }}:
- template: ../steps/publish-internal-feed-step.yml
parameters:
dryRun: ${{ parameters.dryRun }}
internalFeedSource: ${{ parameters.internalFeedSource }}
packagesGlob: ${{ variables.targetDownloadPath }}/*.nupkg
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as line 83 - the variable targetDownloadPath should be referenced using runtime syntax $(targetDownloadPath) instead of template syntax ${{ variables.targetDownloadPath }}.

Copilot uses AI. Check for mistakes.
# Publish Symbols if publishSymbols is true and is not a dry run
- ${{ if and(parameters.publishSymbols, not(parameters.dryRun)) }}:
- template: ../steps/publish-symbols-step.yml
parameters:
publishSymbols: ${{ parameters.publishSymbols }}
symbolsArtifactName: ${{ parameters.product }}_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.nugetPackageVersion }}_$(System.TimelineId)
product: ${{ parameters.product }}
70 changes: 70 additions & 0 deletions eng/pipelines/common/templates/stages/release-stage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#################################################################################
# Licensed to the .NET Foundation under one or more agreements. #
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
parameters:
# Boolean value to indicate whether to run the release stage
- name: runRelease
type: boolean
default: false

# Where to publish the packages: 'Internal' or 'Public' feed
- name: publishDestination
type: string

# Boolean value to indicate whether to perform a dry run or actual publish
- name: dryRun
type: boolean
default: true

# Approval aliases for manual validation before publishing packages
- name: approvalAliases
type: string

# Internal feed source URL for publishing packages to Azure DevOps Feed
- name: internalFeedSource
type: string

# Public NuGet source URL for publishing packages to public feed
- name: publicNuGetSource
type: string

# Boolean value to indicate whether to publish symbols
- name: publishSymbols
type: boolean
default: false

# Boolean value to indicate if the build is a preview release build
- name: isPreview
type: boolean

# Product name associated with the packages
- name: product
type: string

# NuGet package version to be published
- name: nugetPackageVersion
type: string

# Name of the folder containing the packages to be published
- name: packageFolderName
type: string

stages:
- stage: Release ${{ parameters.product }}
displayName: "Release (Manual)"
condition: and(succeeded(), eq('${{ variables.Build.Reason }}', 'Manual'), eq(${{ parameters.runRelease }}, true))
jobs:
- template: ../jobs/publish-packages-job.yml
parameters:
approvalAliases: ${{ parameters.approvalAliases }}
publishDestination: ${{ parameters.publishDestination }}
dryRun: ${{ parameters.dryRun }}
isPreview: ${{ parameters.isPreview }}
internalFeedSource: ${{ parameters.internalFeedSource }}
publicNuGetSource: ${{ parameters.publicNuGetSource }}
publishSymbols: ${{ parameters.publishSymbols }}
packageFolderName: ${{ parameters.packageFolderName }}
nugetPackageVersion: ${{ parameters.nugetPackageVersion }}
product: ${{ parameters.product }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#################################################################################
# Licensed to the .NET Foundation under one or more agreements. #
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################

# Template to publish NuGet packages to an internal Azure DevOps feed

parameters:
# Boolean value to indicate whether to perform a dry run or actual publish
- name: dryRun
type: boolean
default: true

# Internal feed source URL for publishing packages to Azure DevOps Feed
- name: internalFeedSource
type: string

# Glob pattern to identify packages to be published
- name: packagesGlob
type: string

steps:
- task: PowerShell@2
inputs:
displayName: Publish to Internal Feed
pwsh: true
filePath: /tools/scripts/publishPackagesToFeed.ps1
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The filePath references '/tools/scripts/publishPackagesToFeed.ps1', but the actual script file created in this PR is named 'publishPackagesToAzDOFeed.ps1'. This path mismatch will cause the task to fail. Update the path to match the actual script filename.

Suggested change
filePath: /tools/scripts/publishPackagesToFeed.ps1
filePath: /tools/scripts/publishPackagesToAzDOFeed.ps1

Copilot uses AI. Check for mistakes.
arguments: >
-dryRun ${{ parameters.dryRun }}
-internalFeedSource '${{ parameters.internalFeedSource }}'
-packagesGlob '${{ parameters.packagesGlob }}'
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#################################################################################
# Licensed to the .NET Foundation under one or more agreements. #
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################

# Template to publish NuGet packages to a public NuGet feed

parameters:
# Boolean value to indicate whether to perform a dry run or actual publish
- name: dryRun
type: boolean
default: true

# Public NuGet source URL for publishing packages to public feed
- name: publicNuGetSource
type: string

# Service connection name for authenticating to NuGet.org
- name: nugetServiceConnection
type: string
default: "ADO Nuget Org Connection"

# Glob pattern to identify packages to be published
- name: packagesGlob
type: string

steps:
- task: NuGetToolInstaller@1
displayName: "Install Latest Nuget"
inputs:
checkLatest: true
- script: |
echo "[DRY RUN] Listing packages targeted for push to: ${{ parameters.publicNuGetSource }}"
echo "Using glob pattern: ${{ parameters.packagesGlob }}"
# Derive directory and filename pattern from the glob for a precise find (handles nested patterns minimally)
glob='${{ parameters.packagesGlob }}'
dir="${glob%/*}"
name="${glob##*/}"
echo "Resolved directory: $dir"
echo "Filename pattern: $name"
if [ -d "$dir" ]; then
echo "Matched files:" || true
# Print all matched files to identify what would be pushed
find "$dir" -type f -name "$name" -print || true
else
echo "Directory does not exist yet: $dir"
fi
displayName: "Dry Run - List Packages"
condition: and(succeeded(), eq(${{ parameters.dryRun }}, true))

- task: NuGetCommand@2
displayName: "Push to Nuget.org"
condition: and(succeeded(), eq(${{ parameters.dryRun }}, false))
inputs:
command: push
packagesToPush: "${{ parameters.packagesGlob }}"
nuGetFeedType: external
publishFeedCredentials: "${{ parameters.nugetServiceConnection }}"
Loading
Loading