-
Notifications
You must be signed in to change notification settings - Fork 277
Add step in publish pipeline to create PR to azure-sdk-for-net #7426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Contributing | ||
|
||
## PR Process | ||
|
||
When making changes to `@typespec/http-client-csharp`, the downstream effects on the Azure SDK for .NET need to be considered. | ||
|
||
### Automated PR Creation | ||
|
||
The publishing pipeline for `@typespec/http-client-csharp` includes automated steps to create a PR in the [azure-sdk-for-net](https://github.com/Azure/azure-sdk-for-net) repository to update the dependency on `Microsoft.TypeSpec.Generator.ClientModel`. | ||
|
||
The process works as follows: | ||
|
||
1. Create your PR to the [microsoft/typespec](https://github.com/microsoft/typespec) repository for `@typespec/http-client-csharp` | ||
2. After your PR is merged and a release happens, the publishing pipeline will: | ||
a. Publish the NuGet packages | ||
b. Automatically create a PR in [azure-sdk-for-net](https://github.com/Azure/azure-sdk-for-net) to update the dependency | ||
|
||
3. The automated PR in azure-sdk-for-net will: | ||
- Update the package references in Directory.Packages.props | ||
- Include a reference to the original TypeSpec PR | ||
- Include details about the changes | ||
|
||
4. Once the PR in azure-sdk-for-net is merged, the update is complete | ||
|
||
### Manual Process (if automation fails) | ||
|
||
If the automated PR creation fails, you can manually create the PR following these steps: | ||
|
||
1. Clone the azure-sdk-for-net repository | ||
2. Create a new branch | ||
3. Update the package references in Directory.Packages.props | ||
4. Create a PR with a description referencing the TypeSpec PR |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,148 @@ | ||||||||||||||||||||||||||||||||
/* eslint-disable no-console */ | ||||||||||||||||||||||||||||||||
import { execSync } from "child_process"; | ||||||||||||||||||||||||||||||||
import { mkdirSync, writeFileSync, readFileSync } from "fs"; | ||||||||||||||||||||||||||||||||
import { join, resolve } from "path"; | ||||||||||||||||||||||||||||||||
import { DefaultHttpClientFetch } from "./http-client.js"; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
interface Options { | ||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||
* Path to the http-client-csharp package | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
packagePath: string; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||
* Pull request URL of the PR in typespec repository | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
pullRequestUrl: string; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||
* Direct URL to the published NuGet package | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
packageUrl: string; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||
* GitHub token for authentication | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
githubToken: string; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||
* Branch name to create in azure-sdk-for-net | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
branchName?: string; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||
* Creates a PR in the azure-sdk-for-net repository to update the http-client-csharp dependency | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
export async function createAzureSdkForNetPr(options: Options): Promise<void> { | ||||||||||||||||||||||||||||||||
const { packagePath, pullRequestUrl, packageUrl, githubToken } = options; | ||||||||||||||||||||||||||||||||
console.log(`Creating PR for azure-sdk-for-net to update dependency on http-client-csharp`); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Create temp folder for repo | ||||||||||||||||||||||||||||||||
const tempDir = join(process.cwd(), "temp", "azure-sdk-for-net"); | ||||||||||||||||||||||||||||||||
mkdirSync(tempDir, { recursive: true }); | ||||||||||||||||||||||||||||||||
console.log(`Created temp directory at ${tempDir}`); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||
// Clone the repository | ||||||||||||||||||||||||||||||||
console.log(`Cloning azure-sdk-for-net repository...`); | ||||||||||||||||||||||||||||||||
execSync(`git clone https://github.com/Azure/azure-sdk-for-net.git ${tempDir}`, { | ||||||||||||||||||||||||||||||||
stdio: "inherit", | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Read package info | ||||||||||||||||||||||||||||||||
const packageJsonPath = resolve(packagePath, "package.json"); | ||||||||||||||||||||||||||||||||
const packageJsonContent = JSON.parse(readFileSync(packageJsonPath, "utf8")); | ||||||||||||||||||||||||||||||||
const packageVersion = packageJsonContent.version; | ||||||||||||||||||||||||||||||||
console.log(`Using package version: ${packageVersion}`); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Generate branch name if not provided | ||||||||||||||||||||||||||||||||
const branchName = options.branchName || `typespec/update-http-client-${packageVersion}`; | ||||||||||||||||||||||||||||||||
console.log(`Using branch name: ${branchName}`); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Create a new branch | ||||||||||||||||||||||||||||||||
console.log(`Creating branch ${branchName}...`); | ||||||||||||||||||||||||||||||||
execSync(`git checkout -b ${branchName}`, { | ||||||||||||||||||||||||||||||||
Check warningCode scanning / CodeQL Unsafe shell command constructed from library input Medium
This string concatenation which depends on
library input Error loading related location Loading shell command Error loading related location Loading
Copilot AutofixAI 10 days ago To fix the issue, we should avoid directly interpolating the Specifically:
Suggested changeset
1
packages/internal-build-utils/src/create-azure-sdk-for-net-pr.ts
Copilot is powered by AI and may make mistakes. Always verify output.
Positive FeedbackNegative Feedback
Refresh and try again.
|
||||||||||||||||||||||||||||||||
stdio: "inherit", | ||||||||||||||||||||||||||||||||
cwd: tempDir, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Update the dependency in Directory.Packages.props (this is the file that usually contains dependency versions in Azure SDK) | ||||||||||||||||||||||||||||||||
console.log(`Updating dependency version in Directory.Packages.props...`); | ||||||||||||||||||||||||||||||||
const propsFilePath = join(tempDir, "Directory.Packages.props"); | ||||||||||||||||||||||||||||||||
const propsFileContent = readFileSync(propsFilePath, "utf8"); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Update the appropriate package reference in the file | ||||||||||||||||||||||||||||||||
const updatedContent = propsFileContent.replace( | ||||||||||||||||||||||||||||||||
/<PackageVersion Include="Microsoft\.TypeSpec\.Generator\.ClientModel".*?>(.*?)<\/PackageVersion>/g, | ||||||||||||||||||||||||||||||||
`<PackageVersion Include="Microsoft.TypeSpec.Generator.ClientModel">${packageVersion}</PackageVersion>` | ||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Write the updated file back | ||||||||||||||||||||||||||||||||
writeFileSync(propsFilePath, updatedContent); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Commit the changes | ||||||||||||||||||||||||||||||||
console.log(`Committing changes...`); | ||||||||||||||||||||||||||||||||
execSync(`git add Directory.Packages.props`, { | ||||||||||||||||||||||||||||||||
stdio: "inherit", | ||||||||||||||||||||||||||||||||
cwd: tempDir, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
execSync(`git commit -m "Update Microsoft.TypeSpec.Generator.ClientModel to ${packageVersion}"`, { | ||||||||||||||||||||||||||||||||
stdio: "inherit", | ||||||||||||||||||||||||||||||||
cwd: tempDir, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Push the branch | ||||||||||||||||||||||||||||||||
console.log(`Pushing branch to remote...`); | ||||||||||||||||||||||||||||||||
// Using HTTPS with token for auth | ||||||||||||||||||||||||||||||||
const remoteUrl = `https://${githubToken}@github.com/Azure/azure-sdk-for-net.git`; | ||||||||||||||||||||||||||||||||
Check warningCode scanning / CodeQL Unsafe shell command constructed from library input Medium
This string concatenation which depends on
library input Error loading related location Loading shell command Error loading related location Loading
Copilot AutofixAI 10 days ago To fix the issue, we should avoid directly embedding the This approach ensures that the
Suggested changeset
1
packages/internal-build-utils/src/create-azure-sdk-for-net-pr.ts
Copilot is powered by AI and may make mistakes. Always verify output.
Positive FeedbackNegative Feedback
Refresh and try again.
|
||||||||||||||||||||||||||||||||
execSync(`git push ${remoteUrl} ${branchName}`, { | ||||||||||||||||||||||||||||||||
Check warningCode scanning / CodeQL Unsafe shell command constructed from library input Medium
This string concatenation which depends on
library input Error loading related location Loading shell command Error loading related location Loading This string concatenation which depends on library input Error loading related location Loading shell command Error loading related location Loading |
||||||||||||||||||||||||||||||||
stdio: "inherit", | ||||||||||||||||||||||||||||||||
cwd: tempDir, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Create PR using GitHub API | ||||||||||||||||||||||||||||||||
console.log(`Creating PR in Azure/azure-sdk-for-net...`); | ||||||||||||||||||||||||||||||||
const client = new DefaultHttpClientFetch(); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const prBody = ` | ||||||||||||||||||||||||||||||||
This PR updates the dependency on Microsoft.TypeSpec.Generator.ClientModel to version ${packageVersion}. | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
## Details | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
- Original TypeSpec PR: ${pullRequestUrl} | ||||||||||||||||||||||||||||||||
- Package URL: ${packageUrl} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
This is an automated PR created by the TypeSpec publish pipeline. | ||||||||||||||||||||||||||||||||
`.trim(); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const prTitle = `Update Microsoft.TypeSpec.Generator.ClientModel to ${packageVersion}`; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const response = await client.fetch(`https://api.github.com/repos/Azure/azure-sdk-for-net/pulls`, { | ||||||||||||||||||||||||||||||||
method: "POST", | ||||||||||||||||||||||||||||||||
headers: { | ||||||||||||||||||||||||||||||||
"Accept": "application/vnd.github.v3+json", | ||||||||||||||||||||||||||||||||
"Authorization": `token ${githubToken}`, | ||||||||||||||||||||||||||||||||
"User-Agent": "Microsoft-TypeSpec", | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
body: JSON.stringify({ | ||||||||||||||||||||||||||||||||
title: prTitle, | ||||||||||||||||||||||||||||||||
body: prBody, | ||||||||||||||||||||||||||||||||
head: branchName, | ||||||||||||||||||||||||||||||||
base: "master", // Assuming the main branch is called 'master' | ||||||||||||||||||||||||||||||||
}), | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
if (response.status >= 400) { | ||||||||||||||||||||||||||||||||
const responseBody = await response.text(); | ||||||||||||||||||||||||||||||||
console.error(`Failed to create PR: ${responseBody}`); | ||||||||||||||||||||||||||||||||
throw new Error(`Failed to create PR: ${response.status}`); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const responseJson = await response.json(); | ||||||||||||||||||||||||||||||||
console.log(`Successfully created PR: ${responseJson.html_url}`); | ||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||
console.error(`Error creating PR:`, error); | ||||||||||||||||||||||||||||||||
throw error; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** | ||
* Simple HTTP client for making requests to external APIs | ||
*/ | ||
|
||
export interface HttpClient { | ||
fetch(url: string, init?: RequestInit): Promise<Response>; | ||
} | ||
|
||
/** | ||
* Default implementation of HttpClient using fetch | ||
*/ | ||
export class DefaultHttpClientFetch implements HttpClient { | ||
async fetch(url: string, init?: RequestInit): Promise<Response> { | ||
return fetch(url, init); | ||
} | ||
} |
Check warning
Code scanning / CodeQL
Shell command built from environment values Medium
Copilot Autofix
AI 10 days ago
To fix the issue, we will replace the use of
execSync
withexecFileSync
and pass the arguments separately instead of interpolating them into the shell command. This approach avoids shell interpretation of thetempDir
value, mitigating the risk of command injection or unintended behavior.Specifically:
execSync
call on line 49 withexecFileSync
.git
command and its arguments as separate parameters toexecFileSync
.tempDir
value is passed as an argument, not interpolated into the command string.