chore: sync package versions #3400
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 PR Creation | |
| on: | |
| push: | |
| branches: | |
| - "dev/*" | |
| - "develop" | |
| - "test" | |
| - "ai/dev/*" | |
| - "ai/test/*" | |
| - "ai/checklist/*" | |
| # Manual re-trigger. Needed because a push that happens while Actions is stopped | |
| # (quota, outage) is never replayed, leaving the branch promoted but PR-less with | |
| # no way to recover short of an empty commit. Run it from the branch to promote. | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| pr-to-develop: | |
| name: Create/Update PR from dev/* → develop | |
| if: startsWith(github.ref, 'refs/heads/dev/') && !contains(github.event.head_commit.message, '🔄 Update version') && !contains(github.event.head_commit.message, '🤖 Update client APIs') && !contains(github.event.head_commit.message, 'proxy-smart-releaser') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate token | |
| id: generate_token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| client-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check if PR already exists | |
| id: check-pr | |
| run: | | |
| PR_EXISTS=$(gh pr list --base develop --head ${{ github.ref_name }} --state open --json number --jq length) | |
| echo "pr_exists=$PR_EXISTS" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if develop branch exists | |
| id: check-branch | |
| run: | | |
| git fetch origin --prune | |
| if git ls-remote --heads origin develop | grep -q "refs/heads/develop"; then | |
| echo "Develop branch exists on remote" | |
| echo "needs_branch_creation=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Develop branch doesn't exist → needs creation" | |
| echo "needs_branch_creation=true" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Create develop branch if it doesn't exist | |
| if: steps.check-branch.outputs.needs_branch_creation == 'true' && steps.check-pr.outputs.pr_exists == '0' | |
| run: | | |
| # Try to create develop branch from main, fallback to current branch if main doesn't exist | |
| if git ls-remote --heads origin main | grep -q "refs/heads/main"; then | |
| git fetch origin main:main | |
| git checkout -b develop main | |
| echo "Created develop branch from main" | |
| else | |
| echo "Main branch doesn't exist, creating develop from current branch" | |
| git checkout -b develop | |
| fi | |
| git push origin develop | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if branches differ | |
| id: check-diff | |
| run: | | |
| # Now check for differences between current dev branch and develop (develop branch should exist now) | |
| # Switch back to the original dev branch first | |
| git checkout ${{ github.ref_name }} | |
| # Fetch develop branch without checking it out | |
| git fetch origin develop | |
| COMMITS_AHEAD=$(git rev-list --count origin/develop..HEAD) | |
| COMMITS_BEHIND=$(git rev-list --count HEAD..origin/develop) | |
| echo "commits_ahead=$COMMITS_AHEAD" >> $GITHUB_OUTPUT | |
| echo "commits_behind=$COMMITS_BEHIND" >> $GITHUB_OUTPUT | |
| echo "${{ github.ref_name }} is $COMMITS_AHEAD commits ahead and $COMMITS_BEHIND commits behind develop" | |
| if [ "$COMMITS_AHEAD" -gt 0 ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| fi | |
| # Count TODOs in the codebase | |
| TODO_COUNT=$(grep -r " TODO:" \ | |
| --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" \ | |
| --include="*.vue" --include="*.py" --include="*.java" --include="*.cs" \ | |
| --include="*.go" --include="*.rs" --include="*.cpp" --include="*.c" \ | |
| --include="*.h" . | wc -l || echo "0") | |
| echo "todo_count=$TODO_COUNT" >> $GITHUB_OUTPUT | |
| echo "Found $TODO_COUNT TODOs in the codebase" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Create Pull Request | |
| if: steps.check-pr.outputs.pr_exists == '0' && steps.check-diff.outputs.has_changes == 'true' | |
| run: | | |
| gh pr create \ | |
| --base develop \ | |
| --head ${{ github.ref_name }} \ | |
| --title "🔄 Auto-PR: Merge \`${{ github.ref_name }}\` → \`develop\`" \ | |
| --body "**Automated Pull Request** 🤖 | |
| This PR was automatically created to merge changes from \`${{ github.ref_name }}\` into \`develop\`. | |
| **Changes:** | |
| - Commits ahead of develop: ${{ steps.check-diff.outputs.commits_ahead }} | |
| - Commits behind develop: ${{ steps.check-diff.outputs.commits_behind }} | |
| - TODOs remaining in codebase: ${{ steps.check-diff.outputs.todo_count }} | |
| **Review:** Please review the changes before merging." | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Skip PR creation - no changes | |
| if: steps.check-pr.outputs.pr_exists == '0' && steps.check-diff.outputs.has_changes == 'false' | |
| run: | | |
| echo "⏭️ Skipping PR creation - no new commits in ${{ github.ref_name }} compared to develop" | |
| - name: Update existing PR | |
| if: steps.check-pr.outputs.pr_exists != '0' | |
| run: | | |
| echo "PR from ${{ github.ref_name }} to develop already exists. Updating with latest info..." | |
| # Get the PR number | |
| PR_NUMBER=$(gh pr list --base develop --head ${{ github.ref_name }} --state open --json number --jq '.[0].number') | |
| # Update PR body with current stats | |
| gh pr edit $PR_NUMBER --body "**Automated Pull Request** 🤖 | |
| This PR was automatically created to merge changes from \`${{ github.ref_name }}\` into \`develop\`. | |
| **Changes:** | |
| - Commits ahead of develop: ${{ steps.check-diff.outputs.commits_ahead }} | |
| - Commits behind develop: ${{ steps.check-diff.outputs.commits_behind }} | |
| - TODOs remaining in codebase: ${{ steps.check-diff.outputs.todo_count }} | |
| **Review:** Please review the changes before merging. | |
| _Last updated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')_" | |
| echo "Updated PR #$PR_NUMBER with latest commit and TODO counts" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| pr-to-test: | |
| name: Create/Update PR from develop → test | |
| # Unconditional: the PR must exist for whatever is on develop, whoever pushed | |
| # it. Previously this job skipped on automation commits, so a real change | |
| # followed by a version-sync left no PR at all. The loop guard now sits on the | |
| # only step that pushes (Resolve version conflicts), which is where it belongs. | |
| if: github.ref == 'refs/heads/develop' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate token | |
| id: generate_token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| client-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if PR already exists | |
| id: check-pr | |
| run: | | |
| PR_EXISTS=$(gh pr list --base test --head develop --state open --json number --jq length) | |
| echo "pr_exists=$PR_EXISTS" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if test branch exists | |
| id: check-branch | |
| run: | | |
| git fetch origin --prune | |
| if git ls-remote --heads origin test | grep -q "refs/heads/test"; then | |
| echo "Test branch exists on remote" | |
| echo "needs_branch_creation=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Test branch doesn't exist → needs creation" | |
| echo "needs_branch_creation=true" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Create test branch if it doesn't exist | |
| if: steps.check-branch.outputs.needs_branch_creation == 'true' && steps.check-pr.outputs.pr_exists == '0' | |
| run: | | |
| # Try to create test branch from main, fallback to develop if main doesn't exist | |
| if git ls-remote --heads origin main | grep -q "refs/heads/main"; then | |
| git fetch origin main:main | |
| git checkout -b test main | |
| echo "Created test branch from main" | |
| else | |
| echo "Main branch doesn't exist, creating test from develop" | |
| git checkout -b test develop | |
| fi | |
| git push origin test | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if branches differ | |
| id: check-diff | |
| run: | | |
| # Now check for differences between develop and test (test branch should exist now) | |
| # Switch back to develop branch first | |
| git checkout develop | |
| # Fetch test branch without checking it out | |
| git fetch origin test | |
| COMMITS_AHEAD=$(git rev-list --count origin/test..HEAD) | |
| COMMITS_BEHIND=$(git rev-list --count HEAD..origin/test) | |
| echo "commits_ahead=$COMMITS_AHEAD" >> $GITHUB_OUTPUT | |
| echo "commits_behind=$COMMITS_BEHIND" >> $GITHUB_OUTPUT | |
| echo "Develop is $COMMITS_AHEAD commits ahead and $COMMITS_BEHIND commits behind test" | |
| if [ "$COMMITS_AHEAD" -gt 0 ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| fi | |
| # Count TODOs in the codebase | |
| TODO_COUNT=$(grep -r " TODO:" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.vue" --include="*.py" --include="*.java" --include="*.cs" --include="*.go" --include="*.rs" --include="*.cpp" --include="*.c" --include="*.h" . | wc -l || echo "0") | |
| echo "todo_count=$TODO_COUNT" >> $GITHUB_OUTPUT | |
| echo "Found $TODO_COUNT TODOs in the codebase" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| # When develop is behind test (e.g. version bump commits on test), | |
| # the PR will have merge conflicts on package.json version strings. | |
| # Resolve by merging test into develop, keeping develop's versions. | |
| # version-operations will re-stamp the correct beta version after merge anyway. | |
| # THE LOOP GUARD. This step pushes to develop, which re-triggers this | |
| # workflow. Skipping on our own absorb commit is what stops it recursing — | |
| # it used to sit on the job, which also suppressed PR creation entirely. | |
| - name: Resolve version conflicts with test | |
| if: steps.check-diff.outputs.has_changes == 'true' && steps.check-diff.outputs.commits_behind != '0' && !contains(github.event.head_commit.message, 'proxy-smart-releaser') | |
| run: | | |
| echo "::group::Resolve version conflicts" | |
| echo "Develop is ${{ steps.check-diff.outputs.commits_behind }} commits behind test — absorbing..." | |
| git config user.name "proxy-smart-releaser[bot]" | |
| git config user.email "proxy-smart-releaser[bot]@users.noreply.github.com" | |
| # Merge test into develop, keeping develop's side on any conflicts | |
| if git merge origin/test -X ours --no-edit \ | |
| -m "chore: absorb test branch (resolve version conflicts) [proxy-smart-releaser]"; then | |
| echo "Merge completed cleanly" | |
| else | |
| echo "⚠️ Conflicts remain after -X ours — force-resolving all as ours" | |
| CONFLICTED=$(git diff --name-only --diff-filter=U) | |
| if [ -n "$CONFLICTED" ]; then | |
| echo "$CONFLICTED" | xargs git checkout --ours | |
| git add -A | |
| fi | |
| git commit --no-edit \ | |
| -m "chore: absorb test branch (resolve version conflicts) [proxy-smart-releaser]" || true | |
| fi | |
| # Push only if HEAD actually moved | |
| if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/develop)" ]; then | |
| # Fetch + merge (not rebase!) to handle concurrent pushes from release-alpha. | |
| # Rebase fails silently on merge commits; merge -X ours is safe. | |
| git fetch origin develop | |
| git merge origin/develop -X ours --no-edit \ | |
| -m "chore: merge latest develop [proxy-smart-releaser]" || { | |
| CONFLICTED=$(git diff --name-only --diff-filter=U) | |
| if [ -n "$CONFLICTED" ]; then | |
| echo "$CONFLICTED" | xargs git checkout --ours | |
| git add -A | |
| fi | |
| git commit --no-edit \ | |
| -m "chore: merge latest develop [proxy-smart-releaser]" || true | |
| } | |
| git push origin develop || { | |
| echo "⚠️ Push failed — will absorb on next cycle." | |
| exit 0 | |
| } | |
| echo "✅ Pushed merge commit — PR should now be conflict-free" | |
| else | |
| echo "✅ Already up-to-date with test" | |
| fi | |
| echo "::endgroup::" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Create Pull Request | |
| if: steps.check-pr.outputs.pr_exists == '0' && steps.check-diff.outputs.has_changes == 'true' | |
| run: | | |
| gh pr create \ | |
| --base test \ | |
| --head develop \ | |
| --title "🧪 Auto-PR: Merge \`develop\` → \`test\`" \ | |
| --body "**Automated Pull Request** 🤖 | |
| Commits ahead of test: ${{ steps.check-diff.outputs.commits_ahead }} | |
| Commits behind test: ${{ steps.check-diff.outputs.commits_behind }} | |
| TODOs remaining in codebase: ${{ steps.check-diff.outputs.todo_count }}" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Skip PR creation – no changes | |
| if: steps.check-pr.outputs.pr_exists == '0' && steps.check-diff.outputs.has_changes == 'false' | |
| run: echo "⏭️ No new commits in develop compared to test" | |
| - name: Update existing PR | |
| if: steps.check-pr.outputs.pr_exists != '0' | |
| run: | | |
| echo "PR from develop to test already exists. Updating with latest info..." | |
| # Get the PR number | |
| PR_NUMBER=$(gh pr list --base test --head develop --state open --json number --jq '.[0].number') | |
| # Update PR body with current stats | |
| gh pr edit $PR_NUMBER --body "**Automated Pull Request** 🤖 | |
| Commits ahead of test: ${{ steps.check-diff.outputs.commits_ahead }} | |
| Commits behind test: ${{ steps.check-diff.outputs.commits_behind }} | |
| TODOs remaining in codebase: ${{ steps.check-diff.outputs.todo_count }} | |
| _Last updated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')_" | |
| echo "Updated PR #$PR_NUMBER with latest commit and TODO counts" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| # Gate for AUTO-MERGE only, deliberately after the PR exists. It used to run | |
| # first, so a 10-minute wait delayed the PR itself and an absorb commit (which | |
| # never has an Alpha Release) burned the whole timeout. Creating a PR is always | |
| # safe; only merging it needs a green build. | |
| - name: Wait for Alpha Release check | |
| # Nothing to gate when there is no diff, and an absorb commit never gets an | |
| # Alpha Release — waiting on one would just burn the full timeout. | |
| if: steps.check-diff.outputs.has_changes == 'true' && !contains(github.event.head_commit.message, 'proxy-smart-releaser') | |
| run: | | |
| SHA="${{ github.sha }}" | |
| CHECK_PREFIX="Alpha Release" | |
| MAX_WAIT=600 # 10 min — Alpha Release typically finishes in ~3 min | |
| POLL_INTERVAL=15 | |
| ELAPSED=0 | |
| echo "Waiting for '$CHECK_PREFIX' on $SHA..." | |
| while [ "$ELAPSED" -lt "$MAX_WAIT" ]; do | |
| # Match any check run whose name starts with "Alpha Release" | |
| # GitHub nests job names as "Workflow / Job / Nested Job" | |
| CHECKS=$(gh api "repos/${{ github.repository }}/commits/$SHA/check-runs" \ | |
| --jq "[.check_runs[] | select(.name | startswith(\"$CHECK_PREFIX\"))]" \ | |
| 2>/dev/null || echo "[]") | |
| COUNT=$(echo "$CHECKS" | jq 'length') | |
| COMPLETED=$(echo "$CHECKS" | jq '[.[] | select(.status == "completed")] | length') | |
| FAILED=$(echo "$CHECKS" | jq '[.[] | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out")] | length') | |
| echo " [$CHECK_PREFIX] found=$COUNT completed=$COMPLETED failed=$FAILED (${ELAPSED}s)" | |
| if [ "$COUNT" -gt 0 ] && [ "$COUNT" -eq "$COMPLETED" ]; then | |
| if [ "$FAILED" -gt 0 ]; then | |
| echo "❌ Alpha Release failed ($FAILED check(s) failed) — aborting." | |
| echo " A failing build must not be promoted to test." | |
| exit 1 | |
| fi | |
| # success or skipped — both are fine | |
| echo "✅ Alpha Release completed ($COMPLETED checks passed) — proceeding." | |
| break | |
| fi | |
| # If check doesn't exist yet (not_found), keep waiting — it may not have registered yet | |
| sleep "$POLL_INTERVAL" | |
| ELAPSED=$((ELAPSED + POLL_INTERVAL)) | |
| done | |
| if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then | |
| echo "⚠️ Timed out after ${MAX_WAIT}s — Alpha Release still not completed." | |
| echo " Proceeding with PR creation (auto-merge requires branch protection checks anyway)." | |
| fi | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Enable auto-merge | |
| if: steps.check-diff.outputs.has_changes == 'true' | |
| run: | | |
| PR_NUMBER=$(gh pr list --base test --head develop --state open --json number --jq '.[0].number') | |
| if [ -z "$PR_NUMBER" ]; then echo "No open PR — nothing to auto-merge"; exit 0; fi | |
| gh pr merge $PR_NUMBER --auto --merge || echo "⚠️ Auto-merge could not be enabled (may need repo setting)" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| pr-to-main: | |
| name: Create/Update PR from test → main | |
| # Unconditional, same reasoning as pr-to-test: the last push to test is often | |
| # `chore: absorb main branch ... [proxy-smart-releaser]`, and skipping on it | |
| # meant no test → main PR was ever opened for the work underneath. | |
| if: github.ref == 'refs/heads/test' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate token | |
| id: generate_token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| client-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if PR already exists | |
| id: check-pr | |
| run: | | |
| PR_EXISTS=$(gh pr list --base main --head test --state open --json number --jq length) | |
| echo "pr_exists=$PR_EXISTS" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if branches differ | |
| id: check-diff | |
| run: | | |
| # Fetch all branches to ensure we have the latest refs | |
| git fetch origin --prune | |
| # Check if main branch exists on remote | |
| if git ls-remote --heads origin main | grep -q "refs/heads/main"; then | |
| echo "Main branch exists on remote" | |
| git fetch origin main:main | |
| # Check if there are any commits between main and test | |
| COMMITS_AHEAD=$(git rev-list --count main..test) | |
| COMMITS_BEHIND=$(git rev-list --count test..main) | |
| echo "commits_ahead=$COMMITS_AHEAD" >> $GITHUB_OUTPUT | |
| echo "commits_behind=$COMMITS_BEHIND" >> $GITHUB_OUTPUT | |
| echo "Branch test is $COMMITS_AHEAD commits ahead and $COMMITS_BEHIND commits behind main" | |
| # Only create PR if we have commits to contribute | |
| if [ "$COMMITS_AHEAD" -gt 0 ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "Main branch doesn't exist on remote, this will be the first PR to create main" | |
| echo "commits_ahead=1" >> $GITHUB_OUTPUT | |
| echo "commits_behind=0" >> $GITHUB_OUTPUT | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| fi | |
| # Count TODOs in the codebase | |
| TODO_COUNT=$(grep -r " TODO:" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.vue" --include="*.py" --include="*.java" --include="*.cs" --include="*.go" --include="*.rs" --include="*.cpp" --include="*.c" --include="*.h" . | wc -l || echo "0") | |
| echo "todo_count=$TODO_COUNT" >> $GITHUB_OUTPUT | |
| echo "Found $TODO_COUNT TODOs in the codebase" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| # Same as pr-to-test: resolve version conflicts by merging main into test | |
| # Loop guard — this step pushes to test. See pr-to-test. | |
| - name: Resolve version conflicts with main | |
| if: steps.check-diff.outputs.has_changes == 'true' && steps.check-diff.outputs.commits_behind != '0' && !contains(github.event.head_commit.message, 'proxy-smart-releaser') | |
| run: | | |
| echo "::group::Resolve version conflicts" | |
| echo "Test is ${{ steps.check-diff.outputs.commits_behind }} commits behind main — absorbing..." | |
| git config user.name "proxy-smart-releaser[bot]" | |
| git config user.email "proxy-smart-releaser[bot]@users.noreply.github.com" | |
| git checkout test | |
| if git merge origin/main -X ours --no-edit \ | |
| -m "chore: absorb main branch (resolve version conflicts) [proxy-smart-releaser]"; then | |
| echo "Merge completed cleanly" | |
| else | |
| echo "⚠️ Conflicts remain after -X ours — force-resolving all as ours" | |
| CONFLICTED=$(git diff --name-only --diff-filter=U) | |
| if [ -n "$CONFLICTED" ]; then | |
| echo "$CONFLICTED" | xargs git checkout --ours | |
| git add -A | |
| fi | |
| git commit --no-edit \ | |
| -m "chore: absorb main branch (resolve version conflicts) [proxy-smart-releaser]" || true | |
| fi | |
| if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/test)" ]; then | |
| # Fetch + merge (not rebase!) to handle concurrent pushes from release-beta. | |
| # Rebase fails silently on merge commits; merge -X ours is safe. | |
| git fetch origin test | |
| git merge origin/test -X ours --no-edit \ | |
| -m "chore: merge latest test [proxy-smart-releaser]" || { | |
| CONFLICTED=$(git diff --name-only --diff-filter=U) | |
| if [ -n "$CONFLICTED" ]; then | |
| echo "$CONFLICTED" | xargs git checkout --ours | |
| git add -A | |
| fi | |
| git commit --no-edit \ | |
| -m "chore: merge latest test [proxy-smart-releaser]" || true | |
| } | |
| git push origin test || { | |
| echo "⚠️ Push failed — will absorb on next cycle." | |
| exit 0 | |
| } | |
| echo "✅ Pushed merge commit — PR should now be conflict-free" | |
| else | |
| echo "✅ Already up-to-date with main" | |
| fi | |
| echo "::endgroup::" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Create Pull Request | |
| if: steps.check-pr.outputs.pr_exists == '0' && steps.check-diff.outputs.has_changes == 'true' | |
| run: | | |
| gh pr create \ | |
| --base main \ | |
| --head test \ | |
| --title "🚀 Auto-PR: Merge \`test\` → \`main\`" \ | |
| --body "**Automated Pull Request** 🤖 | |
| This PR was automatically created to merge changes from \`test\` into \`main\`. | |
| **Changes:** | |
| - Commits ahead of main: ${{ steps.check-diff.outputs.commits_ahead }} | |
| - Commits behind main: ${{ steps.check-diff.outputs.commits_behind }} | |
| - TODOs remaining in codebase: ${{ steps.check-diff.outputs.todo_count }} | |
| **Review:** Please review the changes before merging." | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Skip PR creation - no changes | |
| if: steps.check-pr.outputs.pr_exists == '0' && steps.check-diff.outputs.has_changes == 'false' | |
| run: | | |
| echo "⏭️ Skipping PR creation - no new commits in test compared to main" | |
| - name: Update existing PR | |
| if: steps.check-pr.outputs.pr_exists != '0' | |
| run: | | |
| echo "PR from test to main already exists. Updating with latest info..." | |
| # Get the PR number | |
| PR_NUMBER=$(gh pr list --base main --head test --state open --json number --jq '.[0].number') | |
| # Update PR body with current stats | |
| gh pr edit $PR_NUMBER --body "**Automated Pull Request** 🤖 | |
| This PR was automatically created to merge changes from \`test\` into \`main\`. | |
| **Changes:** | |
| - Commits ahead of main: ${{ steps.check-diff.outputs.commits_ahead }} | |
| - Commits behind main: ${{ steps.check-diff.outputs.commits_behind }} | |
| - TODOs remaining in codebase: ${{ steps.check-diff.outputs.todo_count }} | |
| **Review:** Please review the changes before merging. | |
| _Last updated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')_" | |
| echo "Updated PR #$PR_NUMBER with latest commit and TODO counts" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| ai-dev-to-ai-test: | |
| name: Create/Update PR from ai/dev/* → ai/test/* | |
| if: startsWith(github.ref, 'refs/heads/ai/dev/') && !contains(github.event.head_commit.message, '🔄 Update version') && !contains(github.event.head_commit.message, '🤖 Update client APIs') && !contains(github.event.head_commit.message, 'proxy-smart-releaser') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate token | |
| id: generate_token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| client-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Extract branch name | |
| id: extract-branch | |
| run: | | |
| # Extract the branch name after ai/dev/ | |
| BRANCH_NAME=${{ github.ref_name }} | |
| TARGET_BRANCH="ai/test/${BRANCH_NAME#ai/dev/}" | |
| echo "target_branch=$TARGET_BRANCH" >> $GITHUB_OUTPUT | |
| echo "Target branch: $TARGET_BRANCH" | |
| - name: Check if PR already exists | |
| id: check-pr | |
| run: | | |
| PR_EXISTS=$(gh pr list --base ${{ steps.extract-branch.outputs.target_branch }} --head ${{ github.ref_name }} --state open --json number --jq length) | |
| echo "pr_exists=$PR_EXISTS" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if target branch exists | |
| id: check-branch | |
| run: | | |
| git fetch origin --prune | |
| if git ls-remote --heads origin ${{ steps.extract-branch.outputs.target_branch }} | grep -q "refs/heads/${{ steps.extract-branch.outputs.target_branch }}"; then | |
| echo "Target branch exists on remote" | |
| echo "needs_branch_creation=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Target branch doesn't exist → needs creation" | |
| echo "needs_branch_creation=true" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Create target branch if it doesn't exist | |
| if: steps.check-branch.outputs.needs_branch_creation == 'true' && steps.check-pr.outputs.pr_exists == '0' | |
| run: | | |
| # Create target ai/test branch from current ai/dev branch | |
| git checkout -b ${{ steps.extract-branch.outputs.target_branch }} | |
| git push origin ${{ steps.extract-branch.outputs.target_branch }} | |
| echo "Created ${{ steps.extract-branch.outputs.target_branch }} branch from current ai/dev branch" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if branches differ | |
| id: check-diff | |
| run: | | |
| git checkout ${{ github.ref_name }} | |
| git fetch origin ${{ steps.extract-branch.outputs.target_branch }} | |
| COMMITS_AHEAD=$(git rev-list --count origin/${{ steps.extract-branch.outputs.target_branch }}..HEAD) | |
| COMMITS_BEHIND=$(git rev-list --count HEAD..origin/${{ steps.extract-branch.outputs.target_branch }}) | |
| echo "commits_ahead=$COMMITS_AHEAD" >> $GITHUB_OUTPUT | |
| echo "commits_behind=$COMMITS_BEHIND" >> $GITHUB_OUTPUT | |
| echo "${{ github.ref_name }} is $COMMITS_AHEAD commits ahead and $COMMITS_BEHIND commits behind ${{ steps.extract-branch.outputs.target_branch }}" | |
| if [ "$COMMITS_AHEAD" -gt 0 ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create Pull Request | |
| if: steps.check-pr.outputs.pr_exists == '0' && steps.check-diff.outputs.has_changes == 'true' | |
| run: | | |
| gh pr create \ | |
| --base ${{ steps.extract-branch.outputs.target_branch }} \ | |
| --head ${{ github.ref_name }} \ | |
| --title "🤖 AI-PR: Merge \`${{ github.ref_name }}\` → \`${{ steps.extract-branch.outputs.target_branch }}\`" \ | |
| --body "**AI-Generated Pull Request** 🤖 | |
| This PR was automatically created to merge AI-enhanced changes from \`${{ github.ref_name }}\` into \`${{ steps.extract-branch.outputs.target_branch }}\`. | |
| **Changes:** | |
| - Commits ahead of target: ${{ steps.check-diff.outputs.commits_ahead }} | |
| - Commits behind target: ${{ steps.check-diff.outputs.commits_behind }} | |
| **Review:** AI-generated code - please review carefully before merging." | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Update existing PR | |
| if: steps.check-pr.outputs.pr_exists != '0' | |
| run: | | |
| PR_NUMBER=$(gh pr list --base ${{ steps.extract-branch.outputs.target_branch }} --head ${{ github.ref_name }} --state open --json number --jq '.[0].number') | |
| gh pr edit $PR_NUMBER --body "**AI-Generated Pull Request** 🤖 | |
| This PR was automatically created to merge AI-enhanced changes from \`${{ github.ref_name }}\` into \`${{ steps.extract-branch.outputs.target_branch }}\`. | |
| **Changes:** | |
| - Commits ahead of target: ${{ steps.check-diff.outputs.commits_ahead }} | |
| - Commits behind target: ${{ steps.check-diff.outputs.commits_behind }} | |
| **Review:** AI-generated code - please review carefully before merging. | |
| _Last updated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')_" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| ai-test-to-dev: | |
| name: Create/Update PR from ai/test/* → dev/* | |
| if: startsWith(github.ref, 'refs/heads/ai/test/') && !contains(github.event.head_commit.message, '🔄 Update version') && !contains(github.event.head_commit.message, '🤖 Update client APIs') && !contains(github.event.head_commit.message, 'proxy-smart-releaser') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate token | |
| id: generate_token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| client-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Extract branch name | |
| id: extract-branch | |
| run: | | |
| # Extract the branch name after ai/test/ | |
| BRANCH_NAME=${{ github.ref_name }} | |
| TARGET_BRANCH="dev/${BRANCH_NAME#ai/test/}" | |
| echo "target_branch=$TARGET_BRANCH" >> $GITHUB_OUTPUT | |
| echo "Target branch: $TARGET_BRANCH" | |
| - name: Check if PR already exists | |
| id: check-pr | |
| run: | | |
| PR_EXISTS=$(gh pr list --base ${{ steps.extract-branch.outputs.target_branch }} --head ${{ github.ref_name }} --state open --json number --jq length) | |
| echo "pr_exists=$PR_EXISTS" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if target branch exists | |
| id: check-branch | |
| run: | | |
| git fetch origin --prune | |
| if git ls-remote --heads origin ${{ steps.extract-branch.outputs.target_branch }} | grep -q "refs/heads/${{ steps.extract-branch.outputs.target_branch }}"; then | |
| echo "Target branch exists on remote" | |
| echo "needs_branch_creation=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Target branch doesn't exist → needs creation" | |
| echo "needs_branch_creation=true" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Create target branch if it doesn't exist | |
| if: steps.check-branch.outputs.needs_branch_creation == 'true' && steps.check-pr.outputs.pr_exists == '0' | |
| run: | | |
| # Create target dev branch from develop if it exists, otherwise from main | |
| if git ls-remote --heads origin develop | grep -q "refs/heads/develop"; then | |
| git fetch origin develop:develop | |
| git checkout -b ${{ steps.extract-branch.outputs.target_branch }} develop | |
| echo "Created ${{ steps.extract-branch.outputs.target_branch }} branch from develop" | |
| elif git ls-remote --heads origin main | grep -q "refs/heads/main"; then | |
| git fetch origin main:main | |
| git checkout -b ${{ steps.extract-branch.outputs.target_branch }} main | |
| echo "Created ${{ steps.extract-branch.outputs.target_branch }} branch from main" | |
| else | |
| echo "Neither develop nor main exists, creating from current branch" | |
| git checkout -b ${{ steps.extract-branch.outputs.target_branch }} | |
| fi | |
| git push origin ${{ steps.extract-branch.outputs.target_branch }} | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if branches differ | |
| id: check-diff | |
| run: | | |
| git checkout ${{ github.ref_name }} | |
| git fetch origin ${{ steps.extract-branch.outputs.target_branch }} | |
| COMMITS_AHEAD=$(git rev-list --count origin/${{ steps.extract-branch.outputs.target_branch }}..HEAD) | |
| COMMITS_BEHIND=$(git rev-list --count HEAD..origin/${{ steps.extract-branch.outputs.target_branch }}) | |
| echo "commits_ahead=$COMMITS_AHEAD" >> $GITHUB_OUTPUT | |
| echo "commits_behind=$COMMITS_BEHIND" >> $GITHUB_OUTPUT | |
| echo "${{ github.ref_name }} is $COMMITS_AHEAD commits ahead and $COMMITS_BEHIND commits behind ${{ steps.extract-branch.outputs.target_branch }}" | |
| if [ "$COMMITS_AHEAD" -gt 0 ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create Pull Request | |
| if: steps.check-pr.outputs.pr_exists == '0' && steps.check-diff.outputs.has_changes == 'true' | |
| run: | | |
| gh pr create \ | |
| --base ${{ steps.extract-branch.outputs.target_branch }} \ | |
| --head ${{ github.ref_name }} \ | |
| --title "🧪 AI-Test-PR: Merge \`${{ github.ref_name }}\` → \`${{ steps.extract-branch.outputs.target_branch }}\`" \ | |
| --body "**AI Test → Dev Pull Request** 🧪 | |
| This PR was automatically created to merge AI-generated test changes from \`${{ github.ref_name }}\` into \`${{ steps.extract-branch.outputs.target_branch }}\`. | |
| **Changes:** | |
| - Commits ahead of target: ${{ steps.check-diff.outputs.commits_ahead }} | |
| - Commits behind target: ${{ steps.check-diff.outputs.commits_behind }} | |
| **Review:** AI-generated tests and code - please review test coverage and implementation." | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Update existing PR | |
| if: steps.check-pr.outputs.pr_exists != '0' | |
| run: | | |
| PR_NUMBER=$(gh pr list --base ${{ steps.extract-branch.outputs.target_branch }} --head ${{ github.ref_name }} --state open --json number --jq '.[0].number') | |
| gh pr edit $PR_NUMBER --body "**AI Test → Dev Pull Request** 🧪 | |
| This PR was automatically created to merge AI-generated test changes from \`${{ github.ref_name }}\` into \`${{ steps.extract-branch.outputs.target_branch }}\`. | |
| **Changes:** | |
| - Commits ahead of target: ${{ steps.check-diff.outputs.commits_ahead }} | |
| - Commits behind target: ${{ steps.check-diff.outputs.commits_behind }} | |
| **Review:** AI-generated tests and code - please review test coverage and implementation. | |
| _Last updated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')_" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| ai-checklist-to-ai-dev: | |
| name: Create/Update PR from ai/checklist/* → ai/dev/* | |
| if: startsWith(github.ref, 'refs/heads/ai/checklist/') && !contains(github.event.head_commit.message, '🔄 Update version') && !contains(github.event.head_commit.message, '🤖 Update client APIs') && !contains(github.event.head_commit.message, 'proxy-smart-releaser') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate token | |
| id: generate_token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| client-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Extract branch name | |
| id: extract-branch | |
| run: | | |
| # Extract the branch name after ai/checklist/ | |
| BRANCH_NAME=${{ github.ref_name }} | |
| TARGET_BRANCH="ai/dev/${BRANCH_NAME#ai/checklist/}" | |
| echo "target_branch=$TARGET_BRANCH" >> $GITHUB_OUTPUT | |
| echo "Target branch: $TARGET_BRANCH" | |
| - name: Check if PR already exists | |
| id: check-pr | |
| run: | | |
| PR_EXISTS=$(gh pr list --base ${{ steps.extract-branch.outputs.target_branch }} --head ${{ github.ref_name }} --state open --json number --jq length) | |
| echo "pr_exists=$PR_EXISTS" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if target branch exists | |
| id: check-branch | |
| run: | | |
| git fetch origin --prune | |
| if git ls-remote --heads origin ${{ steps.extract-branch.outputs.target_branch }} | grep -q "refs/heads/${{ steps.extract-branch.outputs.target_branch }}"; then | |
| echo "Target branch exists on remote" | |
| echo "needs_branch_creation=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Target branch doesn't exist → needs creation" | |
| echo "needs_branch_creation=true" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Create target branch if it doesn't exist | |
| if: steps.check-branch.outputs.needs_branch_creation == 'true' && steps.check-pr.outputs.pr_exists == '0' | |
| run: | | |
| # Create target ai/dev branch from current ai/checklist branch | |
| git checkout -b ${{ steps.extract-branch.outputs.target_branch }} | |
| git push origin ${{ steps.extract-branch.outputs.target_branch }} | |
| echo "Created ${{ steps.extract-branch.outputs.target_branch }} branch from ${{ github.ref_name }}" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Check if branches differ | |
| id: check-diff | |
| run: | | |
| git checkout ${{ github.ref_name }} | |
| git fetch origin ${{ steps.extract-branch.outputs.target_branch }} | |
| COMMITS_AHEAD=$(git rev-list --count origin/${{ steps.extract-branch.outputs.target_branch }}..HEAD) | |
| COMMITS_BEHIND=$(git rev-list --count HEAD..origin/${{ steps.extract-branch.outputs.target_branch }}) | |
| echo "commits_ahead=$COMMITS_AHEAD" >> $GITHUB_OUTPUT | |
| echo "commits_behind=$COMMITS_BEHIND" >> $GITHUB_OUTPUT | |
| echo "${{ github.ref_name }} is $COMMITS_AHEAD commits ahead and $COMMITS_BEHIND commits behind ${{ steps.extract-branch.outputs.target_branch }}" | |
| if [ "$COMMITS_AHEAD" -gt 0 ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create Pull Request | |
| if: steps.check-pr.outputs.pr_exists == '0' && steps.check-diff.outputs.has_changes == 'true' | |
| run: | | |
| gh pr create \ | |
| --base ${{ steps.extract-branch.outputs.target_branch }} \ | |
| --head ${{ github.ref_name }} \ | |
| --title "✅ AI-Checklist-PR: Merge \`${{ github.ref_name }}\` → \`${{ steps.extract-branch.outputs.target_branch }}\`" \ | |
| --body "**AI Checklist → Test Pull Request** ✅ | |
| This PR was automatically created to merge AI-implemented checklist items from \`${{ github.ref_name }}\` into \`${{ steps.extract-branch.outputs.target_branch }}\`. | |
| **Changes:** | |
| - Commits ahead of target: ${{ steps.check-diff.outputs.commits_ahead }} | |
| - Commits behind target: ${{ steps.check-diff.outputs.commits_behind }} | |
| **Review:** AI-implemented checklist features - please review implementation and prepare for testing." | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} | |
| - name: Update existing PR | |
| if: steps.check-pr.outputs.pr_exists != '0' | |
| run: | | |
| PR_NUMBER=$(gh pr list --base ${{ steps.extract-branch.outputs.target_branch }} --head ${{ github.ref_name }} --state open --json number --jq '.[0].number') | |
| gh pr edit $PR_NUMBER --body "**AI Checklist → Test Pull Request** ✅ | |
| This PR was automatically created to merge AI-implemented checklist items from \`${{ github.ref_name }}\` into \`${{ steps.extract-branch.outputs.target_branch }}\`. | |
| **Changes:** | |
| - Commits ahead of target: ${{ steps.check-diff.outputs.commits_ahead }} | |
| - Commits behind target: ${{ steps.check-diff.outputs.commits_behind }} | |
| **Review:** AI-implemented checklist features - please review implementation and prepare for testing. | |
| _Last updated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')_" | |
| env: | |
| GH_TOKEN: ${{ steps.generate_token.outputs.token }} |