Regenerate NEAR RPC Client (auto PR, merge & release) #59
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: Regenerate NEAR RPC Client (auto PR, merge & release) | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "0 0 * * *" # daily at midnight | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| regenerate-merge-release: | |
| runs-on: ubuntu-latest | |
| steps: | |
| # Avoid infinite loop | |
| - name: Exit if triggered by GitHub Actions bot | |
| if: github.actor == 'github-actions[bot]' | |
| run: | | |
| echo "Triggered by GitHub Actions bot; exiting to avoid loop." | |
| exit 0 | |
| # Checkout repo | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: true | |
| # Setup JDK | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v3 | |
| with: | |
| distribution: temurin | |
| java-version: 21 | |
| # Gradlew executable | |
| - name: Grant execute permission for Gradlew | |
| run: chmod +x ./gradlew | |
| # Run Generator (safe) | |
| - name: Run Generator (safe mode) | |
| id: generator | |
| run: | | |
| set +e | |
| ./gradlew :generator:run --args="--openapi-url https://raw.githubusercontent.com/near/nearcore/master/chain/jsonrpc/openapi/openapi.json" --no-daemon | |
| EXIT_CODE=$? | |
| set -e | |
| if [ $EXIT_CODE -ne 0 ]; then | |
| echo "⚠️ OpenAPI generation failed. Skipping regeneration." | |
| echo "pr_required=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "Generator completed successfully." | |
| echo "pr_required=true" >> "$GITHUB_OUTPUT" | |
| # Build without tests | |
| - name: Build project (without tests) | |
| run: ./gradlew build -x test --stacktrace --no-daemon | |
| # Prepare branch, commit regenerated sources | |
| - name: Prepare branch, commit regenerated sources | |
| id: commit | |
| env: | |
| PAT_TOKEN: ${{ secrets.PAT_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| git config --local user.email "automation@github.com" | |
| git config --local user.name "GitHub Actions Bot" | |
| SHORT_SHA=${GITHUB_SHA:0:8} | |
| BRANCH="regenerate-openapi-${GITHUB_RUN_NUMBER}-${SHORT_SHA}" | |
| git checkout -b "$BRANCH" | |
| # Normalize line endings and ignore timestamp changes | |
| git add -A # Ensure all changes are added to the stage | |
| # 🧩 Debug: show diff stats before the check | |
| echo "=== GIT STATUS ===" | |
| git status | |
| # Show detailed diff | |
| echo "=== GIT DIFF SUMMARY ===" | |
| git diff --cached --stat || true | |
| echo "=== GIT DIFF FULL (trimmed to 100 lines) ===" | |
| git diff --cached | head -n 100 || true | |
| # Check for real content differences only (ignores EOL & mode changes) | |
| if git diff --cached --name-status | grep -q '^[AM]'; then | |
| echo "Changes detected, creating PR." | |
| echo "pr_required=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "No meaningful changes detected — skipping regeneration." | |
| echo "pr_required=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git commit -m "chore: regenerate client from OpenAPI" | |
| git push https://x-access-token:${PAT_TOKEN}@github.com/${GITHUB_REPOSITORY}.git "$BRANCH" | |
| echo "pr_required=true" >> "$GITHUB_OUTPUT" | |
| echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" | |
| # Auto-create and merge PR | |
| - name: Auto-create and merge PR | |
| if: steps.commit.outputs.pr_required == 'true' | |
| uses: actions/github-script@v6 | |
| with: | |
| github-token: ${{ secrets.PAT_TOKEN }} | |
| script: | | |
| const branch = '${{ steps.commit.outputs.branch }}'; | |
| const title = `chore: regenerate client from OpenAPI (${branch})`; | |
| const body = `This PR regenerates the NEAR RPC client and models from the latest OpenAPI spec.\n\nAutomatically merged after generation.`; | |
| const pr = await github.rest.pulls.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title, | |
| head: branch, | |
| base: "main", | |
| body | |
| }); | |
| await github.rest.pulls.merge({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.data.number, | |
| merge_method: "squash" | |
| }); | |
| # Output when no changes | |
| - name: Output when no changes | |
| if: steps.commit.outputs.pr_required != 'true' | |
| run: echo "No regenerated changes — nothing to create a PR for." | |
| # Sync main after merge (force fetch tags + main) | |
| - name: Sync main after merge | |
| if: steps.commit.outputs.pr_required == 'true' | |
| run: | | |
| set -euo pipefail | |
| echo "Waiting for GitHub to update merge state..." | |
| # give GitHub a moment | |
| sleep 10 | |
| # force fetch main and tags from remote to ensure runner sees latest | |
| git fetch origin main --force | |
| git fetch origin --tags --force | |
| git checkout main | |
| git reset --hard origin/main | |
| echo "Main branch and tags synced successfully." | |
| # Determine new tag (SemVer Patch) — robust: read remote tags, sort, and ensure uniqueness | |
| - name: Determine new tag version | |
| if: steps.commit.outputs.pr_required == 'true' | |
| id: tag | |
| run: | | |
| set -euo pipefail | |
| # Make sure we have up-to-date tags | |
| git fetch origin --tags --force | |
| # List remote tags that match semver vMAJOR.MINOR.PATCH | |
| # Use ls-remote to be sure we check remote state | |
| TAGS=$(git ls-remote --tags origin | awk '{print $2}' | sed 's|refs/tags/||' | sed 's/\^{}//g' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' || true) | |
| if [ -z "${TAGS:-}" ]; then | |
| LAST_TAG="v1.0.0" | |
| else | |
| # pick highest semantic version (sort -V works on ubuntu) | |
| LAST_TAG=$(echo "$TAGS" | sort -V | tail -n1) | |
| fi | |
| echo "Last tag detected: ${LAST_TAG}" | |
| # Parse components safely | |
| IFS='.' read -r MAJOR MINOR PATCH <<<"${LAST_TAG#v}" | |
| MAJOR=${MAJOR:-1} | |
| MINOR=${MINOR:-1} | |
| PATCH=${PATCH:-0} | |
| # increment patch | |
| PATCH=$((PATCH+1)) | |
| NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}" | |
| # ensure uniqueness in remote (race protection) - bump patch until unique | |
| while git ls-remote --tags origin | awk '{print $2}' | sed 's|refs/tags/||' | sed 's/\^{}//g' | grep -qx "${NEW_TAG}"; do | |
| PATCH=$((PATCH+1)) | |
| NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}" | |
| done | |
| echo "Determined new tag: ${NEW_TAG}" | |
| echo "new_tag=${NEW_TAG}" >> $GITHUB_OUTPUT | |
| # Create and push tag | |
| - name: Create and push tag | |
| id: create_tag | |
| if: steps.commit.outputs.pr_required == 'true' | |
| env: | |
| PAT_TOKEN: ${{ secrets.PAT_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| NEW_TAG="${{ steps.tag.outputs.new_tag }}" | |
| # final safety check: if tag already exists remotely, skip | |
| if git ls-remote --tags origin | awk '{print $2}' | sed 's|refs/tags/||' | sed 's/\^{}//g' | grep -qx "${NEW_TAG}"; then | |
| echo "Tag ${NEW_TAG} already exists on remote. Skipping." | |
| echo "tag_created=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| git tag -a "${NEW_TAG}" -m "Release ${NEW_TAG} (automated)" | |
| git push "https://x-access-token:${PAT_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "refs/tags/${NEW_TAG}" | |
| echo "tag_created=true" >> $GITHUB_OUTPUT | |
| echo "Created tag ${NEW_TAG}" | |
| # Create GitHub Release | |
| - name: Create GitHub Release | |
| if: steps.create_tag.outputs.tag_created == 'true' | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ steps.tag.outputs.new_tag }} | |
| name: "Release ${{ steps.tag.outputs.new_tag }}" | |
| body: | | |
| 🚀 Automated release generated by workflow. | |
| This release was created automatically after merging the regenerated client into main. | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} |