diff --git a/.github/workflows/promote-shipped-apis.yml b/.github/workflows/promote-shipped-apis.yml new file mode 100644 index 000000000..3e18427b0 --- /dev/null +++ b/.github/workflows/promote-shipped-apis.yml @@ -0,0 +1,114 @@ +name: Promote Shipped APIs + +on: + push: + branches: + - main + - support/v2 + +jobs: + promote-apis: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.RELEASE_PLEASE_TOKEN_PROVIDER_APP_ID }} + private-key: ${{ secrets.RELEASE_PLEASE_TOKEN_PROVIDER_PEM }} + + - name: Configure git + shell: pwsh + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global url."https://x-access-token:$env:GH_TOKEN@github.com/".insteadOf "https://github.com/" + + - name: Check for existing PR + id: check_pr + shell: pwsh + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + $branch = "${{ github.ref_name }}" + $prs = gh pr list --state open --head "promote-shipped-apis-$branch" --json number --jq '.[0].number' 2>$null + if ($prs) { + echo "pr_number=$prs" >> $env:GITHUB_OUTPUT + echo "pr_exists=true" >> $env:GITHUB_OUTPUT + Write-Host "Found existing PR: $prs" + } else { + echo "pr_exists=false" >> $env:GITHUB_OUTPUT + Write-Host "No existing PR found" + } + + - name: Checkout existing PR branch + if: steps.check_pr.outputs.pr_exists == 'true' + shell: pwsh + run: | + $branch = "${{ github.ref_name }}" + $prBranch = "promote-shipped-apis-$branch" + + git fetch origin + git checkout $prBranch + + - name: Merge trigger branch into PR branch + if: steps.check_pr.outputs.pr_exists == 'true' + shell: pwsh + run: | + $branch = "${{ github.ref_name }}" + git merge origin/$branch -m "Merge $branch into promote branch" + + - name: Run promote unshipped script + shell: pwsh + run: | + & .\scripts\promoteUnshipped.ps1 + + - name: Check for changes + id: check_changes + shell: pwsh + run: | + $changes = git diff --name-only -- "*Shipped.txt" + if ($changes) { + echo "has_changes=true" >> $env:GITHUB_OUTPUT + Write-Host "Changed files: $changes" + } else { + echo "has_changes=false" >> $env:GITHUB_OUTPUT + Write-Host "No changes detected" + } + + - name: Commit and push changes + if: steps.check_changes.outputs.has_changes == 'true' + shell: pwsh + run: | + git add *Shipped.txt + git commit -m "chore: promote shipped APIs" + + $branch = "${{ github.ref_name }}" + $prBranch = "promote-shipped-apis-$branch" + git push origin $prBranch + + - name: Create new PR + if: steps.check_pr.outputs.pr_exists == 'false' && steps.check_changes.outputs.has_changes == 'true' + shell: pwsh + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + $branch = "${{ github.ref_name }}" + $prBranch = "promote-shipped-apis-$branch" + $title = "automatic promotion of shipped APIs for $branch" + + git checkout -b $prBranch + git push origin $prBranch + + gh pr create --title "$title" --base "$branch" --head "$prBranch" --body "Automatically promotes unshipped APIs to shipped after running the promotion script." + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 195723567..7719a3839 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,4 +74,53 @@ The recommended commit types used are: - __ci__ for CI configuration file changes e.g. updating a pipeline - __chore__ for miscallaneous non-sdk changesin the repo e.g. removing an unused file -Adding an exclamation mark after the commit type (`feat!`) or footer with the prefix __BREAKING CHANGE:__ will cause an increment of the _major_ version. \ No newline at end of file +Adding an exclamation mark after the commit type (`feat!`) or footer with the prefix __BREAKING CHANGE:__ will cause an increment of the _major_ version. + +## Updates to public API surface + +Because we need to maintain a compatible public API surface within a major version, this project is using the public API analyzers to ensure no prior public API is changed/removed inadvertently. + +This means that: + +- All entries in an __Unshipped__ document need to be moved to the __Shipped__ document before any public release. +- All new APIs being added need to be __Unshipped__ document before the pull request can be merged, otherwise build will fail with a message like the example below. + +```txt +Error: /home/runner/work/OpenAPI.NET/OpenAPI.NET/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs(39,46): error RS0016: Symbol 'OAuth2MetadataUrl.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [/home/runner/work/OpenAPI.NET/OpenAPI.NET/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj::TargetFramework=net8.0] +``` + +### Update the unshipped document + +To update the unshipped document, simply run the following commands + +```shell +# add the missing public api entries +dotnet format --diagnostics RS0016 +# discard changes to cs files to avoid creating conflicts +git checkout *.cs +``` + +### Move items from unshipped to unshipped document + +```pwsh +. ./scripts/promoteUnshipped.ps1 +``` + +> Note: the promotion of APIs is automated through the dedicated workflow and should result in pull requests being automatically opened. + +## Updating the benchmark information + +To ensure performance of the library does not degrade over time, we have continuous benchmarks running. You might see the continuous integration failing if your pull request changed any model under __src/Microsoft.OpenApi/Models__. + +```txt +Benchmark result for EmptyApiSchema does not match the existing benchmark result (original!=new). Allocated bytes differ: 408 != 416 +``` + +To update the benchmarks, run the following script: + +```shell +cd performance/benchmark +dotnet run -c Release +``` + +Then commit the report files using a "chore" commit. diff --git a/scripts/promoteUnshipped.ps1 b/scripts/promoteUnshipped.ps1 new file mode 100644 index 000000000..ad7ea908a --- /dev/null +++ b/scripts/promoteUnshipped.ps1 @@ -0,0 +1,13 @@ +$nullableConstant = "#nullable enable" +$unshippedDocuments = ls *.Unshipped* -R | Select-Object -ExpandProperty FullName +foreach ($unshippedDocumentPath in $unshippedDocuments) { + $shippedDocumentPath = $unshippedDocumentPath -replace '\.Unshipped', '.Shipped' + $unshippedDocumentContent = Get-Content $unshippedDocumentPath -Raw + $unshippedDocumentContent = ($unshippedDocumentContent -replace [regex]::Escape($nullableConstant), '').Trim() + if ([string]::IsNullOrWhiteSpace($unshippedDocumentContent)) { + Write-Host "No content to promote for $unshippedDocumentPath, skipping." -ForegroundColor Yellow + continue + } + Add-Content -Path $shippedDocumentPath -Value $unshippedDocumentContent -Verbose + Set-Content -Path $unshippedDocumentPath -Value $nullableConstant -Verbose +} \ No newline at end of file