diff --git a/packages/http-client-csharp/CONTRIBUTING.md b/packages/http-client-csharp/CONTRIBUTING.md new file mode 100644 index 00000000000..cf5ac90f700 --- /dev/null +++ b/packages/http-client-csharp/CONTRIBUTING.md @@ -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 \ No newline at end of file diff --git a/packages/http-client-csharp/eng/pipeline/publish.yml b/packages/http-client-csharp/eng/pipeline/publish.yml index dc8f5201eee..c3ac1cb0108 100644 --- a/packages/http-client-csharp/eng/pipeline/publish.yml +++ b/packages/http-client-csharp/eng/pipeline/publish.yml @@ -47,3 +47,28 @@ extends: inputs: useGlobalJson: true workingDirectory: $(Build.SourcesDirectory)/packages/http-client-csharp + + - stage: CreateAzureSdkForNetPR + displayName: Create PR for azure-sdk-for-net + dependsOn: CSharp_Publish + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranchName'], 'main')) + pool: + name: $(LINUXPOOL) + image: $(LINUXVMIMAGE) + os: linux + jobs: + - job: CreatePR + steps: + - checkout: self + + - template: /eng/tsp-core/pipelines/templates/install.yml + + - script: | + node ./packages/internal-build-utils/cmd/cli.js create-azure-sdk-for-net-pr \ + --packagePath $(Build.SourcesDirectory)/packages/http-client-csharp \ + --pullRequestUrl "https://github.com/microsoft/typespec/pull/$(System.PullRequest.PullRequestNumber)" \ + --packageUrl "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js-test-autorest/nuget/v3/flat2/Microsoft.TypeSpec.Generator.ClientModel/$(packageVersion)" \ + --githubToken $(azure-sdk-build-github-token) + displayName: Create PR in azure-sdk-for-net + env: + packageVersion: $(Build.BuildNumber) diff --git a/packages/internal-build-utils/src/cli.ts b/packages/internal-build-utils/src/cli.ts index 430d5647f1f..9b0b25da102 100644 --- a/packages/internal-build-utils/src/cli.ts +++ b/packages/internal-build-utils/src/cli.ts @@ -1,6 +1,7 @@ import yargs from "yargs"; import { generateThirdPartyNotice } from "./generate-third-party-notice.js"; import { bumpVersionsForPR, bumpVersionsForPrerelease } from "./prerelease.js"; +import { createAzureSdkForNetPr } from "./create-azure-sdk-for-net-pr.js"; main().catch((e) => { // eslint-disable-next-line no-console @@ -48,5 +49,36 @@ async function main() { demandOption: true, }), (args) => bumpVersionsForPR(args.workspaceRoot, args.pr, args.buildNumber), + ) + .command( + "create-azure-sdk-for-net-pr", + "Create PR in azure-sdk-for-net to update http-client-csharp dependency", + (cmd) => + cmd + .option("packagePath", { + type: "string", + description: "Path to the http-client-csharp package", + demandOption: true, + }) + .option("pullRequestUrl", { + type: "string", + description: "URL of the PR in typespec repository", + demandOption: true, + }) + .option("packageUrl", { + type: "string", + description: "URL to the published NuGet package", + demandOption: true, + }) + .option("githubToken", { + type: "string", + description: "GitHub token for authentication", + demandOption: true, + }) + .option("branchName", { + type: "string", + description: "Branch name to create in azure-sdk-for-net", + }), + (args) => createAzureSdkForNetPr(args), ).argv; } diff --git a/packages/internal-build-utils/src/create-azure-sdk-for-net-pr.ts b/packages/internal-build-utils/src/create-azure-sdk-for-net-pr.ts new file mode 100644 index 00000000000..70149d30111 --- /dev/null +++ b/packages/internal-build-utils/src/create-azure-sdk-for-net-pr.ts @@ -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 { + 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}`, { + 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>/g, + `${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`; + execSync(`git push ${remoteUrl} ${branchName}`, { + 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; + } +} \ No newline at end of file diff --git a/packages/internal-build-utils/src/http-client.ts b/packages/internal-build-utils/src/http-client.ts new file mode 100644 index 00000000000..45f72e5d2e8 --- /dev/null +++ b/packages/internal-build-utils/src/http-client.ts @@ -0,0 +1,16 @@ +/** + * Simple HTTP client for making requests to external APIs + */ + +export interface HttpClient { + fetch(url: string, init?: RequestInit): Promise; +} + +/** + * Default implementation of HttpClient using fetch + */ +export class DefaultHttpClientFetch implements HttpClient { + async fetch(url: string, init?: RequestInit): Promise { + return fetch(url, init); + } +} \ No newline at end of file diff --git a/packages/internal-build-utils/src/index.ts b/packages/internal-build-utils/src/index.ts index d8dbe480a83..1413f15d41e 100644 --- a/packages/internal-build-utils/src/index.ts +++ b/packages/internal-build-utils/src/index.ts @@ -2,3 +2,5 @@ export * from "./common.js"; export * from "./dotnet.js"; export * from "./visualstudio.js"; export * from "./watch.js"; +export * from "./create-azure-sdk-for-net-pr.js"; +export * from "./http-client.js";