feat: add pending reviewer ping to auto-update workflow #49
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: Auto Update Tool Versions | |
| on: | |
| schedule: | |
| - cron: "0 2 * * 1-5" # 2:00 AM UTC, Monday to Friday | |
| workflow_dispatch: # Allow manual trigger | |
| push: | |
| paths: | |
| - '.github/workflows/auto-update-tools.yml' # Trigger on changes to this workflow for testing | |
| jobs: | |
| check-and-update-versions: | |
| name: Check and update tool versions | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Fetch latest Scarb version from GitHub releases | |
| id: scarb_version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| latest_version=$(gh api repos/software-mansion/scarb/releases/latest --jq '.tag_name' | sed 's/^v//') | |
| echo "Latest Scarb version found is $latest_version" | |
| echo "LATEST=$latest_version" >> $GITHUB_OUTPUT | |
| - name: Check if Scarb update needed | |
| id: check_scarb | |
| run: | | |
| current_version=$(grep "^SCARB_LATEST_COMPATIBLE_VERSION=" starkup.sh | cut -d'"' -f2) | |
| latest_version=${{ steps.scarb_version.outputs.LATEST }} | |
| echo "Current Scarb version: $current_version" | |
| echo "Latest Scarb version: $latest_version" | |
| if [ "$latest_version" != "$current_version" ]; then | |
| echo "UPDATE_NEEDED=true" >> $GITHUB_OUTPUT | |
| echo "OLD_VERSION=$current_version" >> $GITHUB_OUTPUT | |
| echo "NEW_VERSION=$latest_version" >> $GITHUB_OUTPUT | |
| else | |
| echo "UPDATE_NEEDED=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Fetch latest Starknet Foundry version from GitHub releases | |
| id: foundry_version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| latest_version=$(gh api repos/foundry-rs/starknet-foundry/releases/latest --jq '.tag_name' | sed 's/^v//') | |
| echo "Latest Starknet Foundry version found is $latest_version" | |
| echo "LATEST=$latest_version" >> $GITHUB_OUTPUT | |
| - name: Check if Foundry update needed | |
| id: check_foundry | |
| run: | | |
| current_version=$(grep "^FOUNDRY_LATEST_COMPATIBLE_VERSION=" starkup.sh | cut -d'"' -f2) | |
| latest_version=${{ steps.foundry_version.outputs.LATEST }} | |
| echo "Current Foundry version: $current_version" | |
| echo "Latest Foundry version: $latest_version" | |
| if [ "$latest_version" != "$current_version" ]; then | |
| echo "UPDATE_NEEDED=true" >> $GITHUB_OUTPUT | |
| echo "OLD_VERSION=$current_version" >> $GITHUB_OUTPUT | |
| echo "NEW_VERSION=$latest_version" >> $GITHUB_OUTPUT | |
| else | |
| echo "UPDATE_NEEDED=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Fetch latest Cairo Coverage version from GitHub releases | |
| id: coverage_version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| latest_version=$(gh api repos/software-mansion/cairo-coverage/releases/latest --jq '.tag_name' | sed 's/^v//') | |
| echo "Latest Cairo Coverage version found is $latest_version" | |
| echo "LATEST=$latest_version" >> $GITHUB_OUTPUT | |
| - name: Check if Coverage update needed | |
| id: check_coverage | |
| run: | | |
| current_version=$(grep "^COVERAGE_LATEST_COMPATIBLE_VERSION=" starkup.sh | cut -d'"' -f2) | |
| latest_version=${{ steps.coverage_version.outputs.LATEST }} | |
| echo "Current Coverage version: $current_version" | |
| echo "Latest Coverage version: $latest_version" | |
| if [ "$latest_version" != "$current_version" ]; then | |
| echo "UPDATE_NEEDED=true" >> $GITHUB_OUTPUT | |
| echo "OLD_VERSION=$current_version" >> $GITHUB_OUTPUT | |
| echo "NEW_VERSION=$latest_version" >> $GITHUB_OUTPUT | |
| else | |
| echo "UPDATE_NEEDED=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Fetch latest Cairo Profiler version from GitHub releases | |
| id: profiler_version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| latest_version=$(gh api repos/software-mansion/cairo-profiler/releases/latest --jq '.tag_name' | sed 's/^v//') | |
| echo "Latest Cairo Profiler version found is $latest_version" | |
| echo "LATEST=$latest_version" >> $GITHUB_OUTPUT | |
| - name: Check if Profiler update needed | |
| id: check_profiler | |
| run: | | |
| current_version=$(grep "^PROFILER_LATEST_COMPATIBLE_VERSION=" starkup.sh | cut -d'"' -f2) | |
| latest_version=${{ steps.profiler_version.outputs.LATEST }} | |
| echo "Current Profiler version: $current_version" | |
| echo "Latest Profiler version: $latest_version" | |
| if [ "$latest_version" != "$current_version" ]; then | |
| echo "UPDATE_NEEDED=true" >> $GITHUB_OUTPUT | |
| echo "OLD_VERSION=$current_version" >> $GITHUB_OUTPUT | |
| echo "NEW_VERSION=$latest_version" >> $GITHUB_OUTPUT | |
| else | |
| echo "UPDATE_NEEDED=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Fetch Starknet Devnet version from Starknet Foundry .tool-versions | |
| id: devnet_version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| foundry_version=${{ steps.foundry_version.outputs.LATEST }} | |
| devnet_version=$(curl -sS --fail -H "Authorization: Bearer $GH_TOKEN" https://raw.githubusercontent.com/foundry-rs/starknet-foundry/v${foundry_version}/.tool-versions | grep "^starknet-devnet" | awk '{print $2}') | |
| echo "Starknet Devnet version found in Starknet Foundry v${foundry_version} is $devnet_version" | |
| echo "LATEST=$devnet_version" >> $GITHUB_OUTPUT | |
| - name: Check if Devnet update needed | |
| id: check_devnet | |
| run: | | |
| current_version=$(grep "^DEVNET_LATEST_COMPATIBLE_VERSION=" starkup.sh | cut -d'"' -f2) | |
| latest_version=${{ steps.devnet_version.outputs.LATEST }} | |
| echo "Current Devnet version: $current_version" | |
| echo "Latest Devnet version: $latest_version" | |
| if [ "$latest_version" != "$current_version" ]; then | |
| echo "UPDATE_NEEDED=true" >> $GITHUB_OUTPUT | |
| echo "OLD_VERSION=$current_version" >> $GITHUB_OUTPUT | |
| echo "NEW_VERSION=$latest_version" >> $GITHUB_OUTPUT | |
| else | |
| echo "UPDATE_NEEDED=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Determine if any update is needed | |
| id: check_any | |
| run: | | |
| if [ "${{ steps.check_scarb.outputs.UPDATE_NEEDED }}" = "true" ] || [ "${{ steps.check_foundry.outputs.UPDATE_NEEDED }}" = "true" ] || [ "${{ steps.check_coverage.outputs.UPDATE_NEEDED }}" = "true" ] || [ "${{ steps.check_profiler.outputs.UPDATE_NEEDED }}" = "true" ] || [ "${{ steps.check_devnet.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| echo "UPDATE_NEEDED=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "UPDATE_NEEDED=false" >> $GITHUB_OUTPUT | |
| echo "No updates needed" | |
| fi | |
| - name: Bump starkup version | |
| if: steps.check_any.outputs.UPDATE_NEEDED == 'true' | |
| id: bump_version | |
| run: | | |
| current_script_version=$(grep "^SCRIPT_VERSION=" starkup.sh | cut -d'"' -f2) | |
| echo "Current starkup version: $current_script_version" | |
| IFS='.' read -r major minor patch <<< "$current_script_version" | |
| new_patch=$((patch + 1)) | |
| new_script_version="${major}.${minor}.${new_patch}" | |
| echo "New starkup version: $new_script_version" | |
| echo "NEW_SCRIPT_VERSION=$new_script_version" >> $GITHUB_OUTPUT | |
| - name: Read reviewers file | |
| if: steps.check_any.outputs.UPDATE_NEEDED == 'true' | |
| id: read_reviewers | |
| run: | | |
| # Read reviewers before switching branches | |
| # NOTE: Teams (e.g., org/team-name) are not supported due to GitHub Actions permission limitations. | |
| # Only individual GitHub usernames work. Documentation is in MAINTAINING.md. | |
| # Check if .reviewers file exists, fail if it doesn't | |
| if [ ! -f .reviewers ]; then | |
| echo "Error: .reviewers file not found!" | |
| exit 1 | |
| fi | |
| echo "Reading reviewers from .reviewers file..." | |
| reviewers=$(grep -v '^#' .reviewers | grep -v '^$' | tr '\n' ',' | sed 's/,$//') | |
| # Check if we have any reviewers after filtering | |
| if [ -z "$reviewers" ]; then | |
| echo "Error: No reviewers found in .reviewers file!" | |
| exit 1 | |
| fi | |
| echo "REVIEWERS=$reviewers" >> $GITHUB_OUTPUT | |
| echo "Found reviewers: $reviewers" | |
| - name: Create PR for version update | |
| if: steps.check_any.outputs.UPDATE_NEEDED == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| git config --global user.name 'github-actions[bot]' | |
| git config --global user.email 'github-actions[bot]@users.noreply.github.com' | |
| current_script_version=$(grep "^SCRIPT_VERSION=" starkup.sh | cut -d'"' -f2) | |
| branch_name="auto-update-tools-v${{ steps.bump_version.outputs.NEW_SCRIPT_VERSION }}" | |
| if git ls-remote --heads origin "$branch_name" | grep -q "$branch_name"; then | |
| echo "Branch $branch_name already exists, updating it..." | |
| git fetch origin "$branch_name" | |
| git checkout -b "$branch_name" "origin/$branch_name" || git checkout "$branch_name" | |
| else | |
| echo "Creating new branch $branch_name..." | |
| git checkout -b "$branch_name" | |
| fi | |
| if [ "${{ steps.check_scarb.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| sed -i "s/SCARB_LATEST_COMPATIBLE_VERSION=\"${{ steps.check_scarb.outputs.OLD_VERSION }}\"/SCARB_LATEST_COMPATIBLE_VERSION=\"${{ steps.check_scarb.outputs.NEW_VERSION }}\"/" starkup.sh | |
| fi | |
| if [ "${{ steps.check_foundry.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| sed -i "s/FOUNDRY_LATEST_COMPATIBLE_VERSION=\"${{ steps.check_foundry.outputs.OLD_VERSION }}\"/FOUNDRY_LATEST_COMPATIBLE_VERSION=\"${{ steps.check_foundry.outputs.NEW_VERSION }}\"/" starkup.sh | |
| fi | |
| if [ "${{ steps.check_coverage.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| sed -i "s/COVERAGE_LATEST_COMPATIBLE_VERSION=\"${{ steps.check_coverage.outputs.OLD_VERSION }}\"/COVERAGE_LATEST_COMPATIBLE_VERSION=\"${{ steps.check_coverage.outputs.NEW_VERSION }}\"/" starkup.sh | |
| fi | |
| if [ "${{ steps.check_profiler.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| sed -i "s/PROFILER_LATEST_COMPATIBLE_VERSION=\"${{ steps.check_profiler.outputs.OLD_VERSION }}\"/PROFILER_LATEST_COMPATIBLE_VERSION=\"${{ steps.check_profiler.outputs.NEW_VERSION }}\"/" starkup.sh | |
| fi | |
| if [ "${{ steps.check_devnet.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| sed -i "s/DEVNET_LATEST_COMPATIBLE_VERSION=\"${{ steps.check_devnet.outputs.OLD_VERSION }}\"/DEVNET_LATEST_COMPATIBLE_VERSION=\"${{ steps.check_devnet.outputs.NEW_VERSION }}\"/" starkup.sh | |
| fi | |
| sed -i "s/SCRIPT_VERSION=\"${current_script_version}\"/SCRIPT_VERSION=\"${{ steps.bump_version.outputs.NEW_SCRIPT_VERSION }}\"/" starkup.sh | |
| git add starkup.sh | |
| changes_committed=false | |
| if git commit -m "chore: auto-update tool versions to starkup ${{ steps.bump_version.outputs.NEW_SCRIPT_VERSION }}"; then | |
| changes_committed=true | |
| else | |
| echo "No changes to commit" | |
| fi | |
| git push origin "$branch_name" --force | |
| pr_title="Bump tools; Prepare release ${{ steps.bump_version.outputs.NEW_SCRIPT_VERSION }}" | |
| pr_body="This PR updates tool versions and bumps starkup version to \`${{ steps.bump_version.outputs.NEW_SCRIPT_VERSION }}\` for release.\n\n## Changes\n" | |
| if [ "${{ steps.check_scarb.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| pr_body="${pr_body}- Scarb: \`${{ steps.check_scarb.outputs.OLD_VERSION }}\` → [\`${{ steps.check_scarb.outputs.NEW_VERSION }}\`](https://github.com/software-mansion/scarb/releases/tag/v${{ steps.check_scarb.outputs.NEW_VERSION }})\n" | |
| fi | |
| if [ "${{ steps.check_foundry.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| pr_body="${pr_body}- Starknet Foundry: \`${{ steps.check_foundry.outputs.OLD_VERSION }}\` → [\`${{ steps.check_foundry.outputs.NEW_VERSION }}\`](https://github.com/foundry-rs/starknet-foundry/releases/tag/v${{ steps.check_foundry.outputs.NEW_VERSION }})\n" | |
| fi | |
| if [ "${{ steps.check_coverage.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| pr_body="${pr_body}- Cairo Coverage: \`${{ steps.check_coverage.outputs.OLD_VERSION }}\` → [\`${{ steps.check_coverage.outputs.NEW_VERSION }}\`](https://github.com/software-mansion/cairo-coverage/releases/tag/v${{ steps.check_coverage.outputs.NEW_VERSION }})\n" | |
| fi | |
| if [ "${{ steps.check_profiler.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| pr_body="${pr_body}- Cairo Profiler: \`${{ steps.check_profiler.outputs.OLD_VERSION }}\` → [\`${{ steps.check_profiler.outputs.NEW_VERSION }}\`](https://github.com/software-mansion/cairo-profiler/releases/tag/v${{ steps.check_profiler.outputs.NEW_VERSION }})\n" | |
| fi | |
| if [ "${{ steps.check_devnet.outputs.UPDATE_NEEDED }}" = "true" ]; then | |
| pr_body="${pr_body}- Starknet Devnet: \`${{ steps.check_devnet.outputs.OLD_VERSION }}\` → [\`${{ steps.check_devnet.outputs.NEW_VERSION }}\`](https://github.com/0xSpaceShard/starknet-devnet/releases/tag/v${{ steps.check_devnet.outputs.NEW_VERSION }})\n" | |
| fi | |
| pr_body="${pr_body}- Starkup version: \`${current_script_version}\` → \`${{ steps.bump_version.outputs.NEW_SCRIPT_VERSION }}\`" | |
| pr_body_formatted=$(printf "%b" "$pr_body") | |
| pr_created=false | |
| if gh pr list --head "$branch_name" --json number --jq '.[0].number' | grep -q .; then | |
| pr_number=$(gh pr list --head "$branch_name" --json number --jq '.[0].number') | |
| echo "Updating existing PR #$pr_number..." | |
| gh pr edit "$pr_number" --title "$pr_title" --body "$pr_body_formatted" | |
| else | |
| echo "Creating new PR..." | |
| gh pr create --title "$pr_title" --body "$pr_body_formatted" --label "dependencies" | |
| pr_created=true | |
| fi | |
| # Only request reviews if PR was newly created or changes were committed | |
| # This prevents re-requesting reviews on workflow reruns that don't modify the PR | |
| if [ "$pr_created" = "true" ] || [ "$changes_committed" = "true" ]; then | |
| reviewers="${{ steps.read_reviewers.outputs.REVIEWERS }}" | |
| echo "Requesting reviews from: $reviewers" | |
| gh pr edit --add-reviewer "$reviewers" | |
| else | |
| echo "Skipping reviewer request - no changes were made to the PR" | |
| fi | |
| # Ping pending reviewers if PR already existed (not newly created) | |
| if [ "$pr_created" = "false" ]; then | |
| echo "PR already existed, checking for pending reviewers to ping..." | |
| # Get all requested reviewers (people who haven't submitted a review yet) | |
| pending_reviewers=$(gh pr view "$pr_number" --json reviewRequests --jq '.reviewRequests[].login' 2>/dev/null || echo "") | |
| # Get reviewers who requested changes (not approved) | |
| # State can be: APPROVED, CHANGES_REQUESTED, COMMENTED, DISMISSED, PENDING | |
| changes_requested=$(gh pr view "$pr_number" --json reviews --jq '[.reviews[] | select(.state == "CHANGES_REQUESTED")] | unique_by(.author.login) | .[].author.login' 2>/dev/null || echo "") | |
| # Combine both lists and deduplicate | |
| all_pending="" | |
| for reviewer in $pending_reviewers $changes_requested; do | |
| if [ -n "$reviewer" ] && [[ ! " $all_pending " =~ " $reviewer " ]]; then | |
| all_pending="$all_pending $reviewer" | |
| fi | |
| done | |
| all_pending=$(echo "$all_pending" | xargs) # Trim whitespace | |
| if [ -z "$all_pending" ]; then | |
| echo "No pending reviewers to ping" | |
| else | |
| echo "Pending reviewers: $all_pending" | |
| # Build ping message | |
| ping_message="👋 Friendly reminder: This PR is awaiting your review.\n\n" | |
| for reviewer in $all_pending; do | |
| ping_message="${ping_message}@${reviewer} " | |
| done | |
| ping_message="${ping_message}\n\nPlease take a moment to review when you have time. Thank you!" | |
| # Post comment | |
| echo "Posting ping comment..." | |
| printf "%b" "$ping_message" | gh pr comment "$pr_number" --body-file - | |
| echo "Successfully pinged pending reviewers" | |
| fi | |
| else | |
| echo "PR was newly created, skipping pending reviewer ping" | |
| fi |