Sync Upstream PRs #3
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: Sync Upstream PRs | |
| on: | |
| schedule: | |
| # Run daily at 6 AM UTC | |
| - cron: '0 6 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| sync_mode: | |
| description: 'What to sync' | |
| required: true | |
| default: 'all_open_prs' | |
| type: choice | |
| options: | |
| - all_open_prs | |
| - main_branch | |
| - specific_pr | |
| pr_number: | |
| description: 'Specific PR number (only for specific_pr mode)' | |
| required: false | |
| type: string | |
| env: | |
| UPSTREAM_REPO: ionic-team/capacitor | |
| UPSTREAM_BRANCH: main | |
| jobs: | |
| # Job 1: Fetch all open PRs from upstream and create corresponding PRs | |
| sync-open-prs: | |
| if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sync_mode == 'all_open_prs') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |
| - name: Configure Git | |
| run: | | |
| git config user.name "Capacitor+ Bot" | |
| git config user.email "bot@capgo.app" | |
| - name: Fetch and sync all open PRs from upstream | |
| env: | |
| GH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |
| run: | | |
| echo "Fetching open PRs from upstream repository..." | |
| # Get all open PRs from upstream (only from forks/external contributors) | |
| OPEN_PRS=$(gh pr list \ | |
| --repo ${{ env.UPSTREAM_REPO }} \ | |
| --state open \ | |
| --json number,title,headRefName,headRepositoryOwner,author,url,isDraft \ | |
| --limit 100) | |
| echo "Found PRs:" | |
| echo "$OPEN_PRS" | jq -r '.[] | "PR #\(.number): \(.title) by @\(.author.login)"' | |
| # Filter to non-draft PRs and exclude our own PRs (riderx is the maintainer) | |
| # This prevents syncing PRs we created ourselves on the upstream repo | |
| ALL_PRS=$(echo "$OPEN_PRS" | jq '[.[] | select(.isDraft == false and .author.login != "riderx")]') | |
| echo "" | |
| echo "All open PRs (non-draft):" | |
| echo "$ALL_PRS" | jq -r '.[] | "PR #\(.number): \(.title) by @\(.author.login)"' | |
| PR_COUNT=$(echo "$ALL_PRS" | jq 'length') | |
| echo "" | |
| echo "Total PRs to sync: $PR_COUNT" | |
| if [ "$PR_COUNT" -eq 0 ]; then | |
| echo "No PRs to sync" | |
| exit 0 | |
| fi | |
| # Process each PR | |
| echo "$ALL_PRS" | jq -c '.[]' | while read -r pr; do | |
| PR_NUMBER=$(echo "$pr" | jq -r '.number') | |
| PR_TITLE=$(echo "$pr" | jq -r '.title') | |
| PR_AUTHOR=$(echo "$pr" | jq -r '.author.login') | |
| PR_URL=$(echo "$pr" | jq -r '.url') | |
| echo "" | |
| echo "==========================================" | |
| echo "Processing PR #$PR_NUMBER: $PR_TITLE" | |
| echo "Author: @$PR_AUTHOR" | |
| echo "==========================================" | |
| # Check if we already have a PR for this upstream PR | |
| EXISTING_PR=$(gh pr list \ | |
| --state open \ | |
| --search "upstream PR #$PR_NUMBER" \ | |
| --json number \ | |
| --limit 1) | |
| if [ "$(echo "$EXISTING_PR" | jq 'length')" -gt 0 ]; then | |
| echo "PR for upstream #$PR_NUMBER already exists, skipping..." | |
| continue | |
| fi | |
| # Also check closed PRs to avoid re-creating rejected ones | |
| CLOSED_PR=$(gh pr list \ | |
| --state closed \ | |
| --search "upstream PR #$PR_NUMBER" \ | |
| --json number \ | |
| --limit 1) | |
| if [ "$(echo "$CLOSED_PR" | jq 'length')" -gt 0 ]; then | |
| echo "PR for upstream #$PR_NUMBER was previously closed, skipping..." | |
| continue | |
| fi | |
| # Fetch the PR branch from upstream | |
| echo "Fetching PR #$PR_NUMBER from upstream..." | |
| git fetch https://github.com/${{ env.UPSTREAM_REPO }}.git pull/$PR_NUMBER/head:upstream-pr-$PR_NUMBER || { | |
| echo "Failed to fetch PR #$PR_NUMBER, skipping..." | |
| continue | |
| } | |
| # Create a new branch for this PR | |
| SYNC_BRANCH="sync/upstream-pr-$PR_NUMBER" | |
| # Delete the branch if it exists | |
| git branch -D "$SYNC_BRANCH" 2>/dev/null || true | |
| git push origin --delete "$SYNC_BRANCH" 2>/dev/null || true | |
| # Create sync branch from plus | |
| git checkout plus | |
| git checkout -b "$SYNC_BRANCH" | |
| # Try to merge the PR | |
| echo "Merging PR #$PR_NUMBER..." | |
| if git merge upstream-pr-$PR_NUMBER --no-edit -m "chore: sync upstream PR #$PR_NUMBER from @$PR_AUTHOR"; then | |
| echo "Merge successful!" | |
| # Push the branch | |
| git push origin "$SYNC_BRANCH" --force | |
| # Create PR body | |
| PR_BODY=$(cat <<EOF | |
| ## Upstream PR Sync | |
| This PR syncs changes from an external contributor's PR on the official Capacitor repository. | |
| ### Original PR | |
| - **PR:** [#$PR_NUMBER]($PR_URL) | |
| - **Title:** $PR_TITLE | |
| - **Author:** @$PR_AUTHOR | |
| ### Automation | |
| - CI will run automatically | |
| - Claude Code will review for security/breaking changes | |
| - If approved, this PR will be auto-merged | |
| - A new release will be published automatically | |
| --- | |
| *Synced from upstream by Capacitor+ Bot* | |
| EOF | |
| ) | |
| # Create the PR | |
| gh pr create \ | |
| --base plus \ | |
| --head "$SYNC_BRANCH" \ | |
| --title "chore: sync upstream PR #$PR_NUMBER - $PR_TITLE" \ | |
| --body "$PR_BODY" \ | |
| --label "upstream-sync,automated" || { | |
| echo "Failed to create PR, it may already exist" | |
| } | |
| echo "Created PR for upstream #$PR_NUMBER" | |
| else | |
| echo "Merge conflict detected for PR #$PR_NUMBER" | |
| git merge --abort | |
| # Create an issue for manual intervention | |
| gh issue create \ | |
| --title "Merge conflict: Upstream PR #$PR_NUMBER - $PR_TITLE" \ | |
| --body "The sync of upstream PR #$PR_NUMBER from @$PR_AUTHOR encountered merge conflicts. | |
| **Original PR:** $PR_URL | |
| Please resolve the conflicts manually." \ | |
| --label "upstream-sync,needs-attention,merge-conflict" || { | |
| echo "Issue may already exist" | |
| } | |
| fi | |
| # Clean up | |
| git checkout plus | |
| git branch -D upstream-pr-$PR_NUMBER 2>/dev/null || true | |
| done | |
| echo "" | |
| echo "Sync complete!" | |
| # Job 2: Sync main branch from upstream | |
| sync-main-branch: | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.sync_mode == 'main_branch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |
| - name: Configure Git | |
| run: | | |
| git config user.name "Capacitor+ Bot" | |
| git config user.email "bot@capgo.app" | |
| - name: Sync main branch from upstream | |
| env: | |
| GH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |
| run: | | |
| git remote add upstream https://github.com/${{ env.UPSTREAM_REPO }}.git || true | |
| git fetch upstream | |
| git checkout plus | |
| git fetch upstream main | |
| # Check if there are new commits from upstream | |
| UPSTREAM_COMMITS=$(git rev-list plus..upstream/main --count) | |
| if [ "$UPSTREAM_COMMITS" -eq 0 ]; then | |
| echo "No new commits from upstream main branch" | |
| exit 0 | |
| fi | |
| echo "Found $UPSTREAM_COMMITS new commits from upstream" | |
| # Generate branch name with date | |
| SYNC_BRANCH="sync/upstream-main-$(date +%Y%m%d-%H%M%S)" | |
| # Create sync branch from plus | |
| git checkout -b "$SYNC_BRANCH" plus | |
| # Merge upstream changes | |
| if git merge upstream/main --no-edit -m "chore: sync with upstream main branch"; then | |
| git push origin "$SYNC_BRANCH" | |
| PR_BODY=$(cat <<'EOF' | |
| ## Upstream Main Branch Sync | |
| This PR syncs the latest changes from the official Capacitor main branch. | |
| ### Automation | |
| - CI will run automatically | |
| - Claude Code will review for security/breaking changes | |
| - If approved, this PR will be auto-merged | |
| --- | |
| *Synced from upstream by Capacitor+ Bot* | |
| EOF | |
| ) | |
| gh pr create \ | |
| --base plus \ | |
| --head "$SYNC_BRANCH" \ | |
| --title "chore: sync with upstream main $(date +%Y-%m-%d)" \ | |
| --body "$PR_BODY" \ | |
| --label "upstream-sync,automated" | |
| else | |
| echo "Merge conflicts detected" | |
| git merge --abort | |
| gh issue create \ | |
| --title "Merge conflict: Upstream main sync $(date +%Y-%m-%d)" \ | |
| --body "The automated upstream main branch sync encountered merge conflicts. Please resolve them manually." \ | |
| --label "upstream-sync,needs-attention,merge-conflict" | |
| fi | |
| # Job 3: Sync a specific PR by number | |
| sync-specific-pr: | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.sync_mode == 'specific_pr' && github.event.inputs.pr_number != '' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |
| - name: Configure Git | |
| run: | | |
| git config user.name "Capacitor+ Bot" | |
| git config user.email "bot@capgo.app" | |
| - name: Sync specific PR | |
| env: | |
| GH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |
| PR_NUMBER: ${{ github.event.inputs.pr_number }} | |
| run: | | |
| echo "Syncing specific PR #$PR_NUMBER from upstream..." | |
| # Get PR info | |
| PR_INFO=$(gh pr view $PR_NUMBER --repo ${{ env.UPSTREAM_REPO }} --json title,author,url,state) | |
| PR_TITLE=$(echo "$PR_INFO" | jq -r '.title') | |
| PR_AUTHOR=$(echo "$PR_INFO" | jq -r '.author.login') | |
| PR_URL=$(echo "$PR_INFO" | jq -r '.url') | |
| PR_STATE=$(echo "$PR_INFO" | jq -r '.state') | |
| echo "PR #$PR_NUMBER: $PR_TITLE" | |
| echo "Author: @$PR_AUTHOR" | |
| echo "State: $PR_STATE" | |
| if [ "$PR_STATE" != "OPEN" ]; then | |
| echo "Warning: PR #$PR_NUMBER is not open (state: $PR_STATE)" | |
| fi | |
| # Fetch the PR | |
| git fetch https://github.com/${{ env.UPSTREAM_REPO }}.git pull/$PR_NUMBER/head:upstream-pr-$PR_NUMBER | |
| # Create branch for this PR | |
| SYNC_BRANCH="sync/upstream-pr-$PR_NUMBER" | |
| # Delete existing branch if any | |
| git branch -D "$SYNC_BRANCH" 2>/dev/null || true | |
| git push origin --delete "$SYNC_BRANCH" 2>/dev/null || true | |
| git checkout plus | |
| git checkout -b "$SYNC_BRANCH" | |
| # Merge the PR commits | |
| if git merge upstream-pr-$PR_NUMBER --no-edit -m "chore: sync upstream PR #$PR_NUMBER from @$PR_AUTHOR"; then | |
| git push origin "$SYNC_BRANCH" --force | |
| PR_BODY=$(cat <<EOF | |
| ## Upstream PR Sync | |
| This PR syncs changes from an external contributor's PR on the official Capacitor repository. | |
| ### Original PR | |
| - **PR:** [#$PR_NUMBER]($PR_URL) | |
| - **Title:** $PR_TITLE | |
| - **Author:** @$PR_AUTHOR | |
| ### Automation | |
| - CI will run automatically | |
| - Claude Code will review for security/breaking changes | |
| - If approved, this PR will be auto-merged | |
| --- | |
| *Synced from upstream by Capacitor+ Bot* | |
| EOF | |
| ) | |
| gh pr create \ | |
| --base plus \ | |
| --head "$SYNC_BRANCH" \ | |
| --title "chore: sync upstream PR #$PR_NUMBER - $PR_TITLE" \ | |
| --body "$PR_BODY" \ | |
| --label "upstream-sync,automated" | |
| echo "Successfully created PR for upstream #$PR_NUMBER" | |
| else | |
| echo "Merge conflicts detected for PR #$PR_NUMBER" | |
| git merge --abort | |
| gh issue create \ | |
| --title "Merge conflict: Upstream PR #$PR_NUMBER - $PR_TITLE" \ | |
| --body "The sync of upstream PR #$PR_NUMBER from @$PR_AUTHOR encountered merge conflicts. | |
| **Original PR:** $PR_URL | |
| Please resolve the conflicts manually." \ | |
| --label "upstream-sync,needs-attention,merge-conflict" | |
| fi |