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
1 change: 1 addition & 0 deletions .ado/build-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ extends:
- template: .ado/templates/set-version-vars.yml@self
parameters:
buildEnvironment: Continuous
isReleaseBuild: $(isReleaseBuild)

# 8. Include npmPack.js for Release pipeline (CI only)
- script: copy ".ado\scripts\npmPack.js" "$(Build.StagingDirectory)\versionEnvVars\npmPack.js"
Expand Down
18 changes: 16 additions & 2 deletions .ado/jobs/desktop-single.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,29 @@ steps:
displayName: Install IIS

- pwsh: |
Invoke-WebRequest `
function Invoke-WebRequestWithRetry($Uri, $OutFile, $MaxRetries = 3) {
for ($i = 1; $i -le $MaxRetries; $i++) {
try {
Write-Host "Downloading $OutFile (attempt $i of $MaxRetries)"
Invoke-WebRequest -Uri $Uri -OutFile $OutFile
return
} catch {
Write-Host "Attempt $i failed: $_"
if ($i -eq $MaxRetries) { throw }
Start-Sleep -Seconds (5 * $i)
}
}
}

Invoke-WebRequestWithRetry `
-Uri 'https://download.visualstudio.microsoft.com/download/pr/20598243-c38f-4538-b2aa-af33bc232f80/ea9b2ca232f59a6fdc84b7a31da88464/dotnet-hosting-8.0.3-win.exe' `
-OutFile dotnet-hosting-8.0.3-win.exe

Write-Host 'Installing .NET hosting bundle'
Start-Process -Wait -FilePath .\dotnet-hosting-8.0.3-win.exe -ArgumentList '/INSTALL', '/QUIET', '/NORESTART'
Write-Host 'Installed .NET hosting bundle'

Invoke-WebRequest `
Invoke-WebRequestWithRetry `
-Uri 'https://download.visualstudio.microsoft.com/download/pr/f2ec926e-0d98-4a8b-8c70-722ccc2ca0e5/b59941b0c60f16421679baafdb7e9338/dotnet-sdk-7.0.407-win-x64.exe' `
-OutFile dotnet-sdk-7.0.407-win-x64.exe

Expand Down
27 changes: 26 additions & 1 deletion .ado/jobs/linting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,29 @@ jobs:
variables: [template: ../variables/windows.yml]
pool: ${{ parameters.AgentPool.Medium }}
steps:
- template: ../templates/checkout-shallow.yml
- ${{ if eq(parameters.buildEnvironment, 'Continuous') }}:
- template: ../templates/checkout-shallow.yml
parameters:
persistCredentials: true

# Extract the GitHub OAuth token that ADO uses to clone the repo.
# Any authenticated token raises the GitHub API rate limit from 60 to
# 5,000 req/hr, which prevents validate-overrides from being throttled.
- pwsh: |
$headerLine = git config --get-regexp "http.*\.extraheader" 2>$null | Select-Object -First 1
if (-not $headerLine) {
Write-Host "##vso[task.logissue type=warning]No HTTP extraheader found — validate-overrides will run without GitHub auth"
exit 0
}
$encoded = ($headerLine.Split(' ')[-1]).Trim()
$decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded))
$token = $decoded.Split(':')[-1]
Write-Host "Extracted GitHub OAuth token (length=$($token.Length))"
Write-Host "##vso[task.setvariable variable=GitHubOAuthToken;issecret=true]$token"
displayName: Extract GitHub OAuth token

- ${{ else }}:
- template: ../templates/checkout-shallow.yml

- template: ../templates/prepare-js-env.yml

Expand All @@ -28,6 +50,9 @@ jobs:

- script: yarn validate-overrides
displayName: yarn validate-overrides
${{ if eq(parameters.buildEnvironment, 'Continuous') }}:
env:
PLATFORM_OVERRIDE_GITHUB_TOKEN: $(GitHubOAuthToken)

- script: npx unbroken -q --local-only --allow-local-line-sections
displayName: check local links in .md files
Expand Down
60 changes: 47 additions & 13 deletions .ado/release-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ name: RNW Release $(Date:yyyyMMdd).$(Rev:r)
trigger: none
pr: none

parameters:
- name: forceRelease
displayName: 'Force release (override CI flag — emergency use only)'
type: boolean
default: false

resources:
pipelines:
- pipeline: 'CI'
Expand Down Expand Up @@ -57,6 +63,12 @@ extends:
jobs:
- job: Evaluate
displayName: Check if release should proceed
templateContext:
inputs:
- input: pipelineArtifact
pipeline: 'CI'
artifactName: 'VersionEnvVars'
targetPath: '$(Pipeline.Workspace)/VersionEnvVars'
steps:
- checkout: none

Expand Down Expand Up @@ -99,10 +111,15 @@ extends:
CI_REQUESTEDFOR: $(resources.pipeline.CI.requestedFor)
CI_REQUESTEDFORID: $(resources.pipeline.CI.requestedForID)

# Apply version variables from the CI artifact (sets IsReleaseBuild, npmVersion, etc.)
- task: CmdLine@2
displayName: Apply CI version variables
inputs:
script: node $(Pipeline.Workspace)/VersionEnvVars/versionEnvVars.js

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

Expand All @@ -111,25 +128,42 @@ extends:
$originalBuildNumber = $env:BUILD_BUILDNUMBER
$dateStamp = if ($originalBuildNumber -match '(\d{8}\.\d+)$') { $Matches[1] } else { "" }

# Fallback: if IsReleaseBuild is not in the CI artifact (old branches),
# fall back to commit message detection for backward compatibility.
if (-not $isReleaseBuild) {
$sourceMessage = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
$isReleaseBuild = if ($sourceMessage.StartsWith("RELEASE:")) { 'True' } else { 'False' }
Write-Host "##[warning]IsReleaseBuild not found in CI artifact, fell back to commit message detection: $isReleaseBuild"
}

Write-Host "isReleaseBuild (from CI): $isReleaseBuild"
Write-Host "forceRelease (param): $forceRelease"

$shouldRelease = $false
$buildNumber = ""

if ($buildReason -eq "Manual") {
if ($forceRelease -eq 'True') {
# Emergency override — allow publishing regardless of CI flag
$shouldRelease = $true
if ($ciRunName) {
$buildNumber = "$ciRunName ($sourceBranch) - $dateStamp"
$buildNumber = "$ciRunName [FORCED] ($sourceBranch) - $dateStamp"
} else {
$buildNumber = "Release ($sourceBranch) - $dateStamp"
$buildNumber = "Release [FORCED] ($sourceBranch) - $dateStamp"
}
Write-Host "##[warning]Release forced by parameter override"
}
elseif ($sourceMessage.StartsWith("RELEASE:")) {
elseif ($isReleaseBuild -eq 'True') {
$shouldRelease = $true
$buildNumber = "$ciRunName ($sourceBranch) - $dateStamp"
if ($ciRunName) {
$buildNumber = "$ciRunName ($sourceBranch) - $dateStamp"
} else {
$buildNumber = "Release ($sourceBranch) - $dateStamp"
}
}
else {
$shouldRelease = $false
# Truncate commit message for readability
$shortMsg = $sourceMessage
$shortMsg = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
if ($shortMsg.Length -gt 60) {
$shortMsg = $shortMsg.Substring(0, 57) + "..."
}
Expand All @@ -147,20 +181,20 @@ extends:
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
displayName: Determine release eligibility from CI artifact
env:
BUILD_REASON: $(Build.Reason)
BUILD_BUILDNUMBER: $(Build.BuildNumber)
SOURCE_MESSAGE: $(Build.SourceVersionMessage)
IS_RELEASE_BUILD: $(IsReleaseBuild)
CI_RUN_NAME: $(resources.pipeline.CI.runName)
SOURCE_BRANCH: $(resources.pipeline.CI.sourceBranch)
SOURCE_MESSAGE: $(Build.SourceVersionMessage)

- 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
displayName: SKIPPED - not a release build
condition: eq(variables['gate.shouldRelease'], 'False')

- stage: Release
Expand Down
60 changes: 47 additions & 13 deletions .ado/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ name: RNW NuGet Release $(Date:yyyyMMdd).$(Rev:r)
trigger: none
pr: none

parameters:
- name: forceRelease
displayName: 'Force release (override CI flag — emergency use only)'
type: boolean
default: false

resources:
pipelines:
- pipeline: 'Publish'
Expand Down Expand Up @@ -52,6 +58,12 @@ extends:
jobs:
- job: Evaluate
displayName: Check if release should proceed
templateContext:
inputs:
- input: pipelineArtifact
pipeline: 'Publish'
artifactName: 'VersionEnvVars'
targetPath: '$(Pipeline.Workspace)/VersionEnvVars'
steps:
- checkout: none

Expand Down Expand Up @@ -94,10 +106,15 @@ extends:
PUBLISH_REQUESTEDFOR: $(resources.pipeline.Publish.requestedFor)
PUBLISH_REQUESTEDFORID: $(resources.pipeline.Publish.requestedForID)

# Apply version variables from the CI artifact (sets IsReleaseBuild, npmVersion, etc.)
- task: CmdLine@2
displayName: Apply CI version variables
inputs:
script: node $(Pipeline.Workspace)/VersionEnvVars/versionEnvVars.js

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

Expand All @@ -106,25 +123,42 @@ extends:
$originalBuildNumber = $env:BUILD_BUILDNUMBER
$dateStamp = if ($originalBuildNumber -match '(\d{8}\.\d+)$') { $Matches[1] } else { "" }

# Fallback: if IsReleaseBuild is not in the CI artifact (old branches),
# fall back to commit message detection for backward compatibility.
if (-not $isReleaseBuild) {
$sourceMessage = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
$isReleaseBuild = if ($sourceMessage.StartsWith("RELEASE:")) { 'True' } else { 'False' }
Write-Host "##[warning]IsReleaseBuild not found in CI artifact, fell back to commit message detection: $isReleaseBuild"
}

Write-Host "isReleaseBuild (from CI): $isReleaseBuild"
Write-Host "forceRelease (param): $forceRelease"

$shouldRelease = $false
$buildNumber = ""

if ($buildReason -eq "Manual") {
if ($forceRelease -eq 'True') {
# Emergency override — allow publishing regardless of CI flag
$shouldRelease = $true
if ($publishRunName) {
$buildNumber = "$publishRunName ($sourceBranch) - $dateStamp"
$buildNumber = "$publishRunName [FORCED] ($sourceBranch) - $dateStamp"
} else {
$buildNumber = "Release ($sourceBranch) - $dateStamp"
$buildNumber = "Release [FORCED] ($sourceBranch) - $dateStamp"
}
Write-Host "##[warning]Release forced by parameter override"
}
elseif ($sourceMessage.StartsWith("RELEASE:")) {
elseif ($isReleaseBuild -eq 'True') {
$shouldRelease = $true
$buildNumber = "$publishRunName ($sourceBranch) - $dateStamp"
if ($publishRunName) {
$buildNumber = "$publishRunName ($sourceBranch) - $dateStamp"
} else {
$buildNumber = "Release ($sourceBranch) - $dateStamp"
}
}
else {
$shouldRelease = $false
# Truncate commit message for readability
$shortMsg = $sourceMessage
$shortMsg = ($env:SOURCE_MESSAGE -split "`n")[0].Trim()
if ($shortMsg.Length -gt 60) {
$shortMsg = $shortMsg.Substring(0, 57) + "..."
}
Expand All @@ -142,20 +176,20 @@ extends:
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
displayName: Determine release eligibility from CI artifact
env:
BUILD_REASON: $(Build.Reason)
BUILD_BUILDNUMBER: $(Build.BuildNumber)
SOURCE_MESSAGE: $(Build.SourceVersionMessage)
IS_RELEASE_BUILD: $(IsReleaseBuild)
PUBLISH_RUN_NAME: $(resources.pipeline.Publish.runName)
SOURCE_BRANCH: $(resources.pipeline.Publish.sourceBranch)
SOURCE_MESSAGE: $(Build.SourceVersionMessage)

- 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
displayName: SKIPPED - not a release build
condition: eq(variables['gate.shouldRelease'], 'False')

- stage: Release
Expand Down
19 changes: 18 additions & 1 deletion .ado/scripts/setVersionEnvVars.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ if (buildId == undefined) {
throw new Error("Missing argument for the buildid")
}

// Optional: isReleaseBuild flag from CI setup detection step.
// When present, it is included in the VersionEnvVars artifact so the Release
// pipeline can read the authoritative release/developer classification without
// re-deriving it from commit messages.
const isReleaseBuild = process.argv[4] === 'True' ? 'True' : 'False';
if (process.argv[4] == null) {
console.log("##[warning]isReleaseBuild argument not provided, defaulting to False");
}

let adoBuildVersion=pkgJson.version;
if (isPr) {
adoBuildVersion += `.pr${buildId}`;
Expand All @@ -38,6 +47,7 @@ const versionEnvVars = {
reactDevDependency: pkgJson.devDependencies['react'],
reactNativeDevDependency: pkgJson.devDependencies['react-native'],
npmDistTag: pkgJson?.beachball?.defaultNpmTag?.trim(),
IsReleaseBuild: isReleaseBuild,
}

if (!versionEnvVars.npmDistTag) {
Expand All @@ -46,7 +56,12 @@ if (!versionEnvVars.npmDistTag) {

// Set the build number so the build in the publish pipeline and the release pipeline are named with the convenient version
// See: https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#updatebuildnumber-override-the-automatically-generated-build-number
console.log(`##vso[build.updatebuildnumber]RNW_${adoBuildVersion}`)
// CI builds include [Release] or [Dev] tag for at-a-glance identification in pipeline history.
let buildNumber = `RNW_${adoBuildVersion}`;
if (!isPr) {
buildNumber += isReleaseBuild === 'True' ? ' [Release]' : ' [Dev]';
}
console.log(`##vso[build.updatebuildnumber]${buildNumber}`)

// Set env variable to allow VS to build dll with correct version information
console.log(`##vso[task.setvariable variable=RNW_PKG_VERSION_STR]${versionEnvVars.RNW_PKG_VERSION_STR}`);
Expand All @@ -60,6 +75,7 @@ console.log(`##vso[task.setvariable variable=publishCommitId]${versionEnvVars.pu
console.log(`##vso[task.setvariable variable=reactDevDependency]${versionEnvVars.reactDevDependency}`);
console.log(`##vso[task.setvariable variable=reactNativeDevDependency]${versionEnvVars.reactNativeDevDependency}`);
console.log(`##vso[task.setvariable variable=NpmDistTag]${versionEnvVars.npmDistTag}`);
console.log(`##vso[task.setvariable variable=IsReleaseBuild]${versionEnvVars.IsReleaseBuild}`);

const runnerTemp = process.env.RUNNER_TEMP;
if (!runnerTemp) {
Expand All @@ -80,4 +96,5 @@ console.log("##vso[task.setvariable variable=publishCommitId]${versionEnvVars.pu
console.log("##vso[task.setvariable variable=reactDevDependency]${versionEnvVars.reactDevDependency}");
console.log("##vso[task.setvariable variable=reactNativeDevDependency]${versionEnvVars.reactNativeDevDependency}");
console.log("##vso[task.setvariable variable=NpmDistTag]${versionEnvVars.npmDistTag}");
console.log("##vso[task.setvariable variable=IsReleaseBuild]${versionEnvVars.IsReleaseBuild}");
`);
6 changes: 6 additions & 0 deletions .ado/templates/checkout-shallow.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# Checkout the state of the repo at the time of the commit being tested,
# without full history.
parameters:
- name: persistCredentials
type: boolean
default: false

steps:
- checkout: self
fetchDepth: 1
clean: false
submodules: false
lfs: false
persistCredentials: ${{ parameters.persistCredentials }}
5 changes: 4 additions & 1 deletion .ado/templates/set-version-vars.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ parameters:
values:
- PullRequest
- Continuous
- name: isReleaseBuild
type: string
default: 'False'

steps:
- script: node ./.ado/scripts/setVersionEnvVars.js ${{ parameters.buildEnvironment }} $(Build.BuildId)
- script: node ./.ado/scripts/setVersionEnvVars.js ${{ parameters.buildEnvironment }} $(Build.BuildId) ${{ parameters.isReleaseBuild }}
displayName: Set version variables
name: setVersionEnvVars
env:
Expand Down
Loading
Loading