Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .ado/build-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ extends:
condition: and(succeeded(), eq('${{ parameters.buildEnvironment }}', 'Continuous'))

templateContext:
sdl:
codeql:
enabled: false
outputs:
- output: pipelineArtifact
displayName: 'Publish version variables'
Expand Down
348 changes: 348 additions & 0 deletions .ado/release-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
#
# The Release pipeline entry point.
# It publishes npm packages to npmjs.com, NuGet packages to the public
# ms/react-native and ms/react-native-public ADO feeds and to nuget.org,
# and PDB symbols to the Microsoft Symbol Server.
#
# This file replaces release.yml and references the renamed "CI" pipeline
# (formerly "Publish"). Once all branches use this file, release.yml can
# be deleted.
#
# The pipeline completion trigger is defined below in the pipeline resource.
# Do NOT add a build completion trigger in the ADO UI — UI triggers override
# YAML triggers and cause the pipeline to always run against the default branch
# with incorrect metadata (wrong commit message and branch).
#

name: RNW Release $(Date:yyyyMMdd).$(Rev:r)

trigger: none
pr: none

resources:
pipelines:
- pipeline: 'CI'
project: 'ReactNative'
source: 'CI'
trigger:
branches:
include:
- main
- '0.74-stable'
- '0.81-stable'
- '0.82-stable'
- '0.83-stable'
- '0.84-stable'
repositories:
- repository: 1ESPipelineTemplates
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release

extends:
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
parameters:
pool:
name: Azure-Pipelines-1ESPT-ExDShared
image: windows-latest
os: windows
stages:
#
# Gate stage — runs unconditionally for every trigger.
# It determines whether the Release stage should proceed and sets a
# descriptive build number so the pipeline history is easy to scan.
#
- stage: Gate
displayName: Evaluate release
jobs:
- job: Evaluate
displayName: Check if release should proceed
steps:
- checkout: none

- pwsh: |
Write-Host "== Build Variables =="
Write-Host "Build.Reason: $env:BUILD_REASON"
Write-Host "Build.SourceBranch: $env:BUILD_SOURCEBRANCH"
Write-Host "Build.SourceVersion: $env:BUILD_SOURCEVERSION"
Write-Host "Build.SourceVersionMessage: $env:BUILD_SOURCEVERSIONMESSAGE"
Write-Host "Build.BuildNumber: $env:BUILD_BUILDNUMBER"
Write-Host "Build.BuildId: $env:BUILD_BUILDID"
Write-Host "Build.DefinitionName: $env:BUILD_DEFINITIONNAME"
Write-Host "Build.Repository.Name: $env:BUILD_REPOSITORY_NAME"
Write-Host "System.TeamProject: $env:SYSTEM_TEAMPROJECT"
Write-Host ""
Write-Host "== Pipeline Resource: CI =="
Write-Host "CI.runName: $env:CI_RUNNAME"
Write-Host "CI.runID: $env:CI_RUNID"
Write-Host "CI.sourceBranch: $env:CI_SOURCEBRANCH"
Write-Host "CI.sourceCommit: $env:CI_SOURCECOMMIT"
Write-Host "CI.pipelineID: $env:CI_PIPELINEID"
Write-Host "CI.requestedFor: $env:CI_REQUESTEDFOR"
Write-Host "CI.requestedForID: $env:CI_REQUESTEDFORID"
displayName: Log all pipeline variables
env:
BUILD_REASON: $(Build.Reason)
BUILD_SOURCEBRANCH: $(Build.SourceBranch)
BUILD_SOURCEVERSION: $(Build.SourceVersion)
BUILD_SOURCEVERSIONMESSAGE: $(Build.SourceVersionMessage)
BUILD_BUILDNUMBER: $(Build.BuildNumber)
BUILD_BUILDID: $(Build.BuildId)
BUILD_DEFINITIONNAME: $(Build.DefinitionName)
BUILD_REPOSITORY_NAME: $(Build.Repository.Name)
SYSTEM_TEAMPROJECT: $(System.TeamProject)
CI_RUNNAME: $(resources.pipeline.CI.runName)
CI_RUNID: $(resources.pipeline.CI.runID)
CI_SOURCEBRANCH: $(resources.pipeline.CI.sourceBranch)
CI_SOURCECOMMIT: $(resources.pipeline.CI.sourceCommit)
CI_PIPELINEID: $(resources.pipeline.CI.pipelineID)
CI_REQUESTEDFOR: $(resources.pipeline.CI.requestedFor)
CI_REQUESTEDFORID: $(resources.pipeline.CI.requestedForID)

- pwsh: |
$buildReason = $env:BUILD_REASON
# Use only the first line of the commit message
$sourceMessage = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
$ciRunName = $env:CI_RUN_NAME
$sourceBranch = $env:SOURCE_BRANCH -replace '^refs/heads/', ''

# Extract the datestamp (e.g. "20260319.4") from the original build number
# which has the format "RNW Release 20260319.4"
$originalBuildNumber = $env:BUILD_BUILDNUMBER
$dateStamp = if ($originalBuildNumber -match '(\d{8}\.\d+)$') { $Matches[1] } else { "" }

$shouldRelease = $false
$buildNumber = ""

if ($buildReason -eq "Manual") {
$shouldRelease = $true
if ($ciRunName) {
$buildNumber = "$ciRunName ($sourceBranch) - $dateStamp"
} else {
$buildNumber = "Release ($sourceBranch) - $dateStamp"
}
}
elseif ($sourceMessage.StartsWith("RELEASE:")) {
$shouldRelease = $true
$buildNumber = "$ciRunName ($sourceBranch) - $dateStamp"
}
else {
$shouldRelease = $false
# Truncate commit message for readability
$shortMsg = $sourceMessage
if ($shortMsg.Length -gt 60) {
$shortMsg = $shortMsg.Substring(0, 57) + "..."
}
$buildNumber = "Skipped - $shortMsg ($sourceBranch) - $dateStamp"
}

# Sanitize: ADO build numbers cannot contain " / : < > \ | ? @ *
# and cannot end with '.'
$buildNumber = $buildNumber -replace '["/:<>\\|?@*]', '_'
$buildNumber = $buildNumber.TrimEnd('.')

Write-Host "shouldRelease: $shouldRelease"
Write-Host "buildNumber: $buildNumber"

Write-Host "##vso[build.updatebuildnumber]$buildNumber"
Write-Host "##vso[task.setvariable variable=shouldRelease;isOutput=true]$shouldRelease"
name: gate
displayName: Determine release eligibility and set build number
env:
BUILD_REASON: $(Build.Reason)
BUILD_BUILDNUMBER: $(Build.BuildNumber)
SOURCE_MESSAGE: $(Build.SourceVersionMessage)
CI_RUN_NAME: $(resources.pipeline.CI.runName)
SOURCE_BRANCH: $(resources.pipeline.CI.sourceBranch)

- script: echo Proceeding with release
displayName: RELEASING - proceeding to publish
condition: eq(variables['gate.shouldRelease'], 'True')

- script: echo Skipping release
displayName: SKIPPED - not a RELEASE commit
condition: eq(variables['gate.shouldRelease'], 'False')

- stage: Release
displayName: Publish artifacts
dependsOn: Gate
condition: eq(dependencies.Gate.outputs['Evaluate.gate.shouldRelease'], 'True')
jobs:
- job: PushNpm
displayName: npmjs.com - Publish npm packages
variables:
- group: RNW Secrets
timeoutInMinutes: 30
templateContext:
type: releaseJob
isProduction: true
inputs:
- input: pipelineArtifact
pipeline: 'CI'
artifactName: 'NpmPackedTarballs'
targetPath: '$(Pipeline.Workspace)/published-packages'
- input: pipelineArtifact
pipeline: 'CI'
artifactName: 'VersionEnvVars'
targetPath: '$(Pipeline.Workspace)/VersionEnvVars'
steps:
- task: CmdLine@2
displayName: Apply version variables
inputs:
script: node $(Pipeline.Workspace)/VersionEnvVars/versionEnvVars.js
- script: dir /s "$(Pipeline.Workspace)\published-packages"
displayName: Show npm packages before cleanup
- script: node "$(Pipeline.Workspace)\VersionEnvVars\npmPack.js" --no-pack --check-npm --no-color "$(Pipeline.Workspace)\published-packages"
displayName: Remove already published packages
- script: dir /s "$(Pipeline.Workspace)\published-packages"
displayName: Show npm packages after cleanup
- pwsh: |
$tgzFiles = Get-ChildItem -Path "$(Pipeline.Workspace)\published-packages" -Filter "*.tgz" -Recurse
$tgzCount = $tgzFiles.Count
Write-Host "Found $tgzCount .tgz files"
Write-Host "##vso[task.setvariable variable=HasPackagesToPublish]$($tgzCount -gt 0)"
displayName: Check if there are packages to publish
- task: 'EsrpRelease@11'
displayName: 'ESRP Release to npmjs.com'
condition: and(succeeded(), ne(variables['NpmDistTag'], ''), eq(variables['HasPackagesToPublish'], 'true'))
inputs:
connectedservicename: 'ESRP-CodeSigning-OGX-JSHost-RNW'
usemanagedidentity: false
keyvaultname: 'OGX-JSHost-KV'
authcertname: 'OGX-JSHost-Auth4'
signcertname: 'OGX-JSHost-Sign3'
clientid: '0a35e01f-eadf-420a-a2bf-def002ba898d'
domaintenantid: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2'
contenttype: npm
folderlocation: '$(Pipeline.Workspace)\published-packages'
productstate: '$(NpmDistTag)'
owners: 'vmorozov@microsoft.com'
approvers: 'khosany@microsoft.com'

- job: PushPrivateAdo
displayName: ADO - nuget - react-native
timeoutInMinutes: 30
templateContext:
type: releaseJob
isProduction: true
inputs:
- input: pipelineArtifact
pipeline: 'CI'
artifactName: 'ReactWindows-final-nuget'
targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget'
steps:
- template: .ado/templates/publish-nuget-to-ado-feed.yml@self
parameters:
endpointId: 'a7e33797-4804-4a1d-911d-5bd325e50a85'
nugetFeedUrl: 'https://pkgs.dev.azure.com/ms/_packaging/react-native/nuget/v3/index.json'
packageParentPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget'
packagesToPush: '$(Pipeline.Workspace)/ReactWindows-final-nuget/*.nupkg'
publishFeedCredentials: 'ms/react-native ADO Feed'
feedDisplayName: 'ms/react-native'

- job: PushPublicAdo
displayName: ADO - nuget - react-native-public
timeoutInMinutes: 30
templateContext:
type: releaseJob
isProduction: true
inputs:
- input: pipelineArtifact
pipeline: 'CI'
artifactName: 'ReactWindows-final-nuget'
targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget'
steps:
- template: .ado/templates/publish-nuget-to-ado-feed.yml@self
parameters:
endpointId: '9a2456d0-c163-405b-be24-c03fd74b155a'
nugetFeedUrl: 'https://pkgs.dev.azure.com/ms/react-native/_packaging/react-native-public/nuget/v3/index.json'
packageParentPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget'
packagesToPush: '$(Pipeline.Workspace)/ReactWindows-final-nuget/*.nupkg'
publishFeedCredentials: 'ms/react-native-public ADO Feed'
feedDisplayName: 'ms/react-native-public'

- job: PushNuGetOrg
displayName: nuget.org - Push nuget packages
variables:
- group: RNW Secrets
timeoutInMinutes: 30
templateContext:
type: releaseJob
isProduction: true
inputs:
- input: pipelineArtifact
pipeline: 'CI'
artifactName: 'ReactWindows-final-nuget'
targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget'
steps:
- task: NuGetToolInstaller@1
displayName: 'Use NuGet'
- pwsh: nuget.exe SetApiKey "$env:NUGET_API_KEY"
displayName: NuGet SetApiKey (nuget.org)
workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget
env:
NUGET_API_KEY: $(nugetorg-apiKey-push)
- script: dir /S "$(Pipeline.Workspace)\ReactWindows-final-nuget"
displayName: Show directory contents
- script: nuget.exe push .\Microsoft.ReactNative.*.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -NoSymbol -NonInteractive -Verbosity Detailed
displayName: NuGet push (nuget.org)
workingDirectory: $(Pipeline.Workspace)/ReactWindows-final-nuget

- job: PublishSymbols
displayName: Publish PDB Symbols to Symbol Server
timeoutInMinutes: 30
templateContext:
type: releaseJob
isProduction: true
inputs:
- input: pipelineArtifact
pipeline: 'CI'
artifactName: 'ReactWindows-final-nuget'
targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget'
steps:
- pwsh: |
# Extract PDB files from all NuGet packages (.nupkg are ZIP archives)
$nugetDir = "$(Pipeline.Workspace)/ReactWindows-final-nuget"
$symbolsDir = "$(Pipeline.Workspace)/symbols"
New-Item -ItemType Directory -Path $symbolsDir -Force | Out-Null

$nupkgs = Get-ChildItem "$nugetDir/*.nupkg"
Write-Host "Found $($nupkgs.Count) NuGet packages"

foreach ($nupkg in $nupkgs) {
Write-Host "Extracting PDBs from: $($nupkg.Name)"
$extractDir = "$symbolsDir/$($nupkg.BaseName)"
# Rename to .zip for Expand-Archive compatibility
$zipPath = "$nugetDir/$($nupkg.BaseName).zip"
Copy-Item $nupkg.FullName $zipPath
Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force
Remove-Item $zipPath
}

# Show extracted PDBs
$pdbs = Get-ChildItem "$symbolsDir" -Recurse -Filter "*.pdb"
Write-Host "`nFound $($pdbs.Count) PDB files:"
foreach ($pdb in $pdbs) {
Write-Host " $($pdb.FullName) ($([math]::Round($pdb.Length / 1MB, 2)) MB)"
}

if ($pdbs.Count -eq 0) {
Write-Host "##vso[task.logissue type=warning]No PDB files found in NuGet packages"
}
displayName: Extract PDBs from NuGet packages

- task: PublishSymbols@2
displayName: 'Publish Symbols to Microsoft Symbol Server'
continueOnError: true
inputs:
UseNetCoreClientTool: true
ConnectedServiceName: Office-React-Native-Windows-Bot
SymbolsFolder: '$(Pipeline.Workspace)/symbols'
SearchPattern: '**/*.pdb'
SymbolServerType: 'TeamServices'
IndexSources: false # SourceLink is already embedded in PDBs at compile time
SymbolsProduct: 'ReactNativeWindows'
SymbolsVersion: '$(Build.BuildNumber)'
SymbolsArtifactName: 'ReactNativeWindows-Symbols-$(Build.BuildId)'
DetailedLog: true
TreatNotIndexedAsWarning: false
Loading