Release Notes #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Notes | |
| on: | |
| schedule: | |
| - cron: "0 12 * * 4" # every Thursday at noon UTC | |
| workflow_dispatch: | |
| jobs: | |
| list-merged: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Compute next version | |
| id: version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| CURRENT=$(gh release view --json tagName -q '.tagName' | sed 's/^v//') | |
| MINOR=$(echo "$CURRENT" | cut -d. -f2) | |
| NEXT="4.$((MINOR + 1)).0" | |
| echo "next=$NEXT" >> "$GITHUB_OUTPUT" | |
| echo "**Next version:** $NEXT" >> "$GITHUB_STEP_SUMMARY" | |
| - name: List merged PRs | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh api "repos/${{ github.repository }}/compare/$(gh release view --json tagName -q '.tagName')...${{ github.ref_name }}" --jq '.commits[].sha' | | |
| xargs -P8 -I{} gh api "repos/${{ github.repository }}/commits/{}/pulls" --jq '.[].number' | | |
| sort -u | | |
| xargs -I{} gh pr view {} --json number,author,commits,comments | | |
| jq -s '[.[] | { | |
| pr: .number, | |
| author: .author.login, | |
| commits: [.commits[] | .messageHeadline + (if .messageBody != "" then "\n\n" + .messageBody else "" end)], | |
| summary: ([.comments[] | select(.author.login == "claude")] | last | .body) | |
| }]' | tee "$RUNNER_TEMP/merged-prs.json" | jq . | |
| - name: Build classifier request | |
| run: | | |
| jq --slurpfile prs "$RUNNER_TEMP/merged-prs.json" \ | |
| --rawfile prompt .github/ai/release-notes-classifier.txt \ | |
| '(.messages[] | select(.role == "system") | .content) = $prompt | | |
| (.messages[] | select(.role == "user") | .content) = ($prs[0] | tojson)' \ | |
| .github/ai/release-notes-classifier.json > "$RUNNER_TEMP/request.json" | |
| echo '<details><summary>Classifier request</summary>' >> "$GITHUB_STEP_SUMMARY" | |
| echo '' >> "$GITHUB_STEP_SUMMARY" | |
| echo '```json' >> "$GITHUB_STEP_SUMMARY" | |
| jq . "$RUNNER_TEMP/request.json" >> "$GITHUB_STEP_SUMMARY" | |
| echo '```' >> "$GITHUB_STEP_SUMMARY" | |
| echo '</details>' >> "$GITHUB_STEP_SUMMARY" | |
| - name: Classify PRs | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: | | |
| RESPONSE=$(curl -fsSL https://api.anthropic.com/v1/messages \ | |
| -H "x-api-key: $ANTHROPIC_API_KEY" \ | |
| -H "anthropic-version: 2023-06-01" \ | |
| -H "content-type: application/json" \ | |
| -d @"$RUNNER_TEMP/request.json") || { | |
| echo "::error::Anthropic API call failed" | |
| exit 1 | |
| } | |
| echo "$RESPONSE" | jq '.content[0].text | fromjson | .entries' > "$RUNNER_TEMP/classified.json" | |
| echo '<details><summary>Classified PRs</summary>' >> "$GITHUB_STEP_SUMMARY" | |
| echo '' >> "$GITHUB_STEP_SUMMARY" | |
| echo '```json' >> "$GITHUB_STEP_SUMMARY" | |
| jq . "$RUNNER_TEMP/classified.json" >> "$GITHUB_STEP_SUMMARY" | |
| echo '```' >> "$GITHUB_STEP_SUMMARY" | |
| echo '</details>' >> "$GITHUB_STEP_SUMMARY" | |
| - name: Update CHANGELOG.md and create PR | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| NEXT: ${{ steps.version.outputs.next }} | |
| run: | | |
| jq -r --arg repo "${{ github.repository }}" ' | |
| [group_by(.category)[] | { | |
| order: (.[0].category | if . == "ADDED" then 1 elif . == "FIXED" then 2 elif . == "CHANGED" then 3 else 4 end), | |
| title: (.[0].category | .[0:1] + (.[1:] | ascii_downcase)), | |
| items: (. | sort_by(.system)) | |
| }] | sort_by(.order)[] | | |
| "### \(.title)\n", | |
| (.items[] | "- \(.system): \(.description) (@\(.author)) ([#\(.pr)](https://github.com/\($repo)/pull/\(.pr)))"), | |
| "" | |
| ' "$RUNNER_TEMP/classified.json" | tee "$RUNNER_TEMP/changelog-entries.md" >> "$GITHUB_STEP_SUMMARY" | |
| ENTRIES=$(cat "$RUNNER_TEMP/changelog-entries.md") | |
| if [ -z "$ENTRIES" ]; then | |
| echo "::error::No classified entries found" | |
| exit 1 | |
| fi | |
| TAG="v$NEXT" | |
| BRANCH="changelog/$TAG" | |
| HEADER="## $NEXT - $(date -u +%Y-%m-%d)" | |
| NEW_SECTION=$(printf '%s\n\n%s\n' "$HEADER" "$ENTRIES") | |
| # Insert new section at line 6 (after the file header) | |
| head -n 5 CHANGELOG.md > "$RUNNER_TEMP/CHANGELOG.md" | |
| printf '%s\n' "$NEW_SECTION" >> "$RUNNER_TEMP/CHANGELOG.md" | |
| tail -n +6 CHANGELOG.md >> "$RUNNER_TEMP/CHANGELOG.md" | |
| mv "$RUNNER_TEMP/CHANGELOG.md" CHANGELOG.md | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Create or force-update branch | |
| git checkout -B "$BRANCH" | |
| git add CHANGELOG.md | |
| git commit -m "Update changelog for $TAG" | |
| git push -f origin "$BRANCH" | |
| # Create or update PR | |
| EXISTING_PR=$(gh pr list --head "$BRANCH" --json number -q '.[0].number') | |
| if [ -n "$EXISTING_PR" ]; then | |
| echo "Updating existing PR #$EXISTING_PR" | |
| else | |
| gh pr create \ | |
| --title "Update changelog for $TAG" \ | |
| --body "" \ | |
| --base main \ | |
| --head "$BRANCH" | |
| fi |