Skip to content

Commit b6bccbf

Browse files
CopilotJoshLove-msftweshaggardchristothes
authored
Add step in publish pipeline to create PR to azure-sdk-for-net (#7426)
This PR adds automation to create a pull request in the Azure SDK for .NET repository that updates the dependency on http-client-csharp when a new version is published. ## Implementation Details 1. Created a new script in `internal-build-utils` package: - Added `create-azure-sdk-for-net-pr.ts` to handle PR creation - Added `http-client.ts` for GitHub API requests - Updated the CLI to include a new command for creating PRs 2. Modified the `http-client-csharp` publish pipeline: - Added a new stage that runs after successful publish - Calls the PR creation command with proper parameters - Only runs on the main branch (not on PRs) 3. Added documentation: - Created a `CONTRIBUTING.md` for http-client-csharp - Documented the automated PR creation process and manual fallback ## How It Works When a new version of http-client-csharp is published from the main branch: 1. The script clones the azure-sdk-for-net repository 2. Creates a new branch 3. Updates the Microsoft.TypeSpec.Generator.ClientModel package reference in Directory.Packages.props 4. Creates a PR with appropriate title and description including a link back to the original TypeSpec PR This automation helps ensure that the Azure SDK for .NET always uses the latest version of the TypeSpec-generated client components, improving consistency across repositories. Fixes #7110. --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Co-authored-by: weshaggard <9010698+weshaggard@users.noreply.github.com> Co-authored-by: christothes <1279263+christothes@users.noreply.github.com>
1 parent cce9837 commit b6bccbf

File tree

2 files changed

+274
-0
lines changed

2 files changed

+274
-0
lines changed

packages/http-client-csharp/eng/pipeline/publish.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,45 @@ extends:
4747
inputs:
4848
useGlobalJson: true
4949
workingDirectory: $(Build.SourcesDirectory)/packages/http-client-csharp
50+
51+
- stage: CreateAzureSdkForNetPR
52+
displayName: Create PR for azure-sdk-for-net
53+
dependsOn:
54+
- CSharp_Publish
55+
- CSharp_Build
56+
condition: and(succeeded(), and(ne(variables['Build.Reason'], 'Manual'), eq(variables['Build.SourceBranchName'], 'main')))
57+
variables:
58+
PackageVersion: $[ stageDependencies.CSharp_Build.Build_linux_20.outputs['ci_build.emitterVersion'] ]
59+
pool:
60+
name: $(LINUXPOOL)
61+
image: $(LINUXVMIMAGE)
62+
os: linux
63+
jobs:
64+
- job: CreatePR
65+
steps:
66+
- checkout: self
67+
- pwsh: |
68+
# Determine the TypeSpec PR URL
69+
$sourceBranch = '$(Build.SourceBranch)'
70+
$repoUrl = '$(Build.Repository.Uri)'
71+
72+
if ($sourceBranch -match "^refs/pull/(\d+)/(head|merge)$") {
73+
$typeSpecPRUrl = "$repoUrl/pull/$($Matches[1])"
74+
} elseif ($sourceBranch -match "^refs/heads/(.+)$") {
75+
$typeSpecPRUrl = "$repoUrl/tree/$($Matches[1])"
76+
} else {
77+
$typeSpecPRUrl = "$repoUrl/tree/$sourceBranch"
78+
}
79+
Write-Host "TypeSpec PR URL: $typeSpecPRUrl"
80+
Write-Host "##vso[task.setvariable variable=TypeSpecPRUrl]$typeSpecPRUrl"
81+
displayName: Set variables for PR creation
82+
83+
- task: PowerShell@2
84+
displayName: Create PR in azure-sdk-for-net
85+
inputs:
86+
pwsh: true
87+
filePath: $(Build.SourcesDirectory)/packages/http-client-csharp/eng/scripts/Submit-AzureSdkForNetPr.ps1
88+
arguments: >
89+
-PackageVersion '$(PackageVersion)'
90+
-TypeSpecPRUrl '$(TypeSpecPRUrl)'
91+
-AuthToken '$(azuresdk-github-pat)'
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
#!/usr/bin/env pwsh
2+
3+
<#
4+
.DESCRIPTION
5+
Creates a pull request in the Azure SDK for .NET repository to update the UnbrandedGeneratorVersion property in eng/Packages.Data.props and the @typespec/http-client-csharp dependency in eng/packages/http-client-csharp/package.json.
6+
.PARAMETER PackageVersion
7+
The version of the Microsoft.TypeSpec.Generator.ClientModel package to update to.
8+
.PARAMETER TypeSpecPRUrl
9+
The URL of the pull request in the TypeSpec repository that triggered this update.
10+
.PARAMETER AuthToken
11+
A GitHub personal access token for authentication.
12+
.PARAMETER BranchName
13+
The name of the branch to create in the azure-sdk-for-net repository.
14+
#>
15+
[CmdletBinding(SupportsShouldProcess = $true)]
16+
param(
17+
[Parameter(Mandatory = $true)]
18+
[string]$PackageVersion,
19+
20+
[Parameter(Mandatory = $true)]
21+
[string]$TypeSpecPRUrl,
22+
23+
[Parameter(Mandatory = $true)]
24+
[string]$AuthToken,
25+
26+
[Parameter(Mandatory = $false)]
27+
[string]$BranchName = "typespec/update-http-client-$PackageVersion"
28+
)
29+
30+
# Import the Generation module to use the Invoke helper function
31+
Import-Module (Join-Path $PSScriptRoot "Generation.psm1")
32+
33+
# Set up variables for the PR
34+
$RepoOwner = "Azure"
35+
$RepoName = "azure-sdk-for-net"
36+
$BaseBranch = "main"
37+
$PROwner = "azure-sdk"
38+
$PRBranch = $BranchName
39+
40+
$PRTitle = "Update UnbrandedGeneratorVersion to $PackageVersion"
41+
$PRBody = @"
42+
This PR updates the UnbrandedGeneratorVersion property in eng/Packages.Data.props and the @typespec/http-client-csharp dependency in eng/packages/http-client-csharp/package.json to version $PackageVersion.
43+
44+
## Details
45+
46+
- Original TypeSpec PR: $TypeSpecPRUrl
47+
48+
## Changes
49+
50+
- Updated eng/Packages.Data.props UnbrandedGeneratorVersion property
51+
- Updated eng/packages/http-client-csharp/package.json dependency version
52+
- Ran npm install to update package-lock.json
53+
54+
This is an automated PR created by the TypeSpec publish pipeline.
55+
"@
56+
57+
Write-Host "Creating PR in $RepoOwner/$RepoName"
58+
Write-Host "Branch: $PRBranch"
59+
Write-Host "Title: $PRTitle"
60+
61+
# Create temp folder for repo
62+
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "azure-sdk-for-net-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
63+
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
64+
Write-Host "Created temp directory: $tempDir"
65+
66+
try {
67+
# Clone the repository
68+
Write-Host "Cloning azure-sdk-for-net repository..."
69+
git clone "https://github.com/$RepoOwner/$RepoName.git" $tempDir
70+
if ($LASTEXITCODE -ne 0) {
71+
throw "Failed to clone repository"
72+
}
73+
74+
Push-Location $tempDir
75+
76+
# Create a new branch
77+
Write-Host "Creating branch $PRBranch..."
78+
git checkout -b $PRBranch
79+
if ($LASTEXITCODE -ne 0) {
80+
throw "Failed to create branch"
81+
}
82+
83+
# Update the dependency in eng/Packages.Data.props
84+
Write-Host "Updating dependency version in eng/Packages.Data.props..."
85+
$propsFilePath = Join-Path $tempDir "eng/Packages.Data.props"
86+
87+
if (-not (Test-Path $propsFilePath)) {
88+
throw "eng/Packages.Data.props not found in the repository"
89+
}
90+
91+
$propsFileContent = Get-Content $propsFilePath -Raw
92+
93+
# Update the UnbrandedGeneratorVersion property in the file
94+
$pattern = '<UnbrandedGeneratorVersion>[^<]*</UnbrandedGeneratorVersion>'
95+
$replacement = '<UnbrandedGeneratorVersion>' + $PackageVersion + '</UnbrandedGeneratorVersion>'
96+
97+
$updatedContent = $propsFileContent -replace $pattern, $replacement
98+
99+
$propsFileUpdated = $false
100+
if ($updatedContent -eq $propsFileContent) {
101+
Write-Warning "No changes were made to eng/Packages.Data.props. The UnbrandedGeneratorVersion property might not exist or have a different format."
102+
Write-Host "Current content around UnbrandedGeneratorVersion:"
103+
$propsFileContent | Select-String -Pattern "UnbrandedGeneratorVersion" -Context 2, 2
104+
} else {
105+
$propsFileUpdated = $true
106+
# Write the updated file back
107+
Set-Content -Path $propsFilePath -Value $updatedContent -NoNewline
108+
}
109+
110+
# Update the dependency in eng/packages/http-client-csharp/package.json
111+
Write-Host "Updating dependency version in eng/packages/http-client-csharp/package.json..."
112+
$packageJsonPath = Join-Path $tempDir "eng/packages/http-client-csharp/package.json"
113+
114+
if (-not (Test-Path $packageJsonPath)) {
115+
throw "eng/packages/http-client-csharp/package.json not found in the repository"
116+
}
117+
118+
$packageJsonContent = Get-Content $packageJsonPath -Raw | ConvertFrom-Json
119+
120+
# Update the Microsoft.TypeSpec.Generator.ClientModel dependency version
121+
$packageJsonUpdated = $false
122+
if ($packageJsonContent.dependencies -and $packageJsonContent.dependencies."@typespec/http-client-csharp") {
123+
$packageJsonContent.dependencies."@typespec/http-client-csharp" = $PackageVersion
124+
$packageJsonUpdated = $true
125+
Write-Host "Updated @typespec/http-client-csharp in dependencies"
126+
# Write the updated package.json back
127+
$packageJsonContent | ConvertTo-Json -Depth 10 | Set-Content -Path $packageJsonPath
128+
} else {
129+
Write-Warning "No @typespec/http-client-csharp dependency found in package.json"
130+
}
131+
132+
# Check if any updates were made - bail early if not
133+
if (-not $propsFileUpdated -and -not $packageJsonUpdated) {
134+
Write-Warning "No updates were made to any files. The package version might already be current or the files might not contain the expected properties."
135+
return
136+
}
137+
138+
# Only run expensive operations if we actually made updates
139+
if ($packageJsonUpdated) {
140+
# Run npm install in the http-client-csharp directory
141+
Write-Host "Running npm install in eng/packages/http-client-csharp..."
142+
$httpClientDir = Join-Path $tempDir "eng/packages/http-client-csharp"
143+
Invoke "npm install" $httpClientDir
144+
if ($LASTEXITCODE -ne 0) {
145+
throw "npm install failed"
146+
}
147+
148+
# Run npm run build
149+
Write-Host "Running npm run build in eng/packages/http-client-csharp..."
150+
Invoke "npm run build" $httpClientDir
151+
if ($LASTEXITCODE -ne 0) {
152+
throw "npm run build failed"
153+
}
154+
155+
# Run Generate.ps1 from the repository root
156+
Write-Host "Running eng/scripts/Generate.ps1..."
157+
Invoke "eng/scripts/Generate.ps1" $tempDir
158+
if ($LASTEXITCODE -ne 0) {
159+
throw "Generate.ps1 failed"
160+
}
161+
}
162+
163+
# Check if there are changes to commit
164+
$gitStatus = git status --porcelain
165+
if (-not $gitStatus) {
166+
Write-Warning "No changes detected. Skipping commit and PR creation."
167+
return
168+
}
169+
170+
# Commit the changes
171+
Write-Host "Committing changes..."
172+
if ($propsFileUpdated) {
173+
git add eng/Packages.Data.props
174+
}
175+
if ($packageJsonUpdated) {
176+
git add eng/packages/http-client-csharp/package.json
177+
git add eng/packages/http-client-csharp/package-lock.json
178+
}
179+
if ($LASTEXITCODE -ne 0) {
180+
throw "Failed to add changes"
181+
}
182+
183+
# Build commit message based on what was updated
184+
$commitMessage = "Update UnbrandedGeneratorVersion to $PackageVersion`n"
185+
if ($propsFileUpdated) {
186+
$commitMessage += "`n- Updated eng/Packages.Data.props"
187+
}
188+
if ($packageJsonUpdated) {
189+
$commitMessage += "`n- Updated eng/packages/http-client-csharp/package.json"
190+
$commitMessage += "`n- Ran npm install to update package-lock.json"
191+
}
192+
193+
git commit -m $commitMessage
194+
if ($LASTEXITCODE -ne 0) {
195+
throw "Failed to commit changes"
196+
}
197+
198+
# Push the branch
199+
Write-Host "Pushing branch to remote..."
200+
$remoteUrl = "https://$AuthToken@github.com/$RepoOwner/$RepoName.git"
201+
git push $remoteUrl $PRBranch
202+
if ($LASTEXITCODE -ne 0) {
203+
throw "Failed to push branch"
204+
}
205+
206+
# Create PR using GitHub CLI
207+
Write-Host "Creating PR in $RepoOwner/$RepoName using gh CLI..."
208+
209+
# Set the authentication token for gh CLI
210+
$env:GH_TOKEN = $AuthToken
211+
212+
# Create the PR using gh CLI
213+
$ghOutput = gh pr create --repo "$RepoOwner/$RepoName" --title $PRTitle --body $PRBody --base $BaseBranch --head $PRBranch 2>&1
214+
215+
if ($LASTEXITCODE -ne 0) {
216+
throw "Failed to create PR using gh CLI: $ghOutput"
217+
}
218+
219+
# Extract PR URL from gh output
220+
$prUrl = $ghOutput.Trim()
221+
Write-Host "Successfully created PR: $prUrl"
222+
223+
} catch {
224+
Write-Error "Error creating PR: $_"
225+
exit 1
226+
} finally {
227+
Pop-Location
228+
# Clean up temp directory
229+
if (Test-Path $tempDir) {
230+
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
231+
}
232+
}

0 commit comments

Comments
 (0)