Sync Upstream #136
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 | |
| # This workflow syncs changes from t3-oss/create-t3-turbo upstream repository. | |
| # It merges changes directly to main after running CI checks. | |
| # | |
| # When merge conflicts occur: | |
| # - If ONLY pnpm-lock.yaml conflicts: automatically resolve by accepting upstream's | |
| # lockfile and running 'pnpm install', then push to main | |
| # - If other files conflict: abort merge and create issue for manual resolution | |
| # | |
| # The workflow will: | |
| # 1. Fetch and merge upstream changes | |
| # 2. Run all CI checks (lint, format, typecheck, test) | |
| # 3. Push to main if all checks pass | |
| # 4. Create an issue if checks fail | |
| on: | |
| # https://github.com/Labrys-Group/create-t3-turbo/actions/workflows/sync-upstream.yml | |
| # Click "Run workflow" | |
| workflow_dispatch: # Allow manual triggering | |
| schedule: | |
| # Run daily at 2 AM UTC | |
| - cron: '0 2 * * *' | |
| # You can leverage Vercel Remote Caching with Turbo to speed up your builds | |
| env: | |
| FORCE_COLOR: 3 | |
| TURBO_TEAM: ${{ secrets.TURBO_TEAM }} | |
| TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} | |
| jobs: | |
| sync: | |
| # Only run this workflow in the original repository, not in repos created from template | |
| if: github.repository == 'Labrys-Group/create-t3-turbo' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| issues: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| ref: main | |
| # Use SYNC_TOKEN if available (PAT with admin rights to bypass branch protection) | |
| # Otherwise fall back to GITHUB_TOKEN | |
| token: ${{ secrets.SYNC_TOKEN || secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 # Fetch all history for all branches and tags | |
| - name: Check for open sync issues | |
| id: check_issues | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { data: issues } = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| labels: 'upstream-sync' | |
| }); | |
| if (issues.length > 0) { | |
| console.log(`Found ${issues.length} open upstream-sync issue(s):`); | |
| issues.forEach(issue => { | |
| console.log(`- #${issue.number}: ${issue.title}`); | |
| }); | |
| core.setOutput('has_open_issues', 'true'); | |
| } else { | |
| console.log('No open upstream-sync issues found'); | |
| core.setOutput('has_open_issues', 'false'); | |
| } | |
| - name: Exit if open sync issues exist | |
| if: steps.check_issues.outputs.has_open_issues == 'true' | |
| run: | | |
| echo "⚠️ Skipping sync - open upstream-sync issues exist" | |
| echo "Please resolve the existing issues before running sync again" | |
| exit 0 | |
| - name: Configure Git | |
| if: steps.check_issues.outputs.has_open_issues == 'false' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Add upstream remote | |
| if: steps.check_issues.outputs.has_open_issues == 'false' | |
| run: | | |
| git remote add upstream https://github.com/t3-oss/create-t3-turbo.git || true | |
| git fetch upstream | |
| - name: Check for upstream changes | |
| if: steps.check_issues.outputs.has_open_issues == 'false' | |
| id: check | |
| run: | | |
| # Get the latest commit from upstream main | |
| UPSTREAM_COMMIT=$(git rev-parse upstream/main) | |
| # Check if we have this commit locally | |
| if git merge-base --is-ancestor "$UPSTREAM_COMMIT" HEAD; then | |
| echo "already_synced=true" >> "$GITHUB_OUTPUT" | |
| echo "No new changes from upstream" | |
| else | |
| echo "already_synced=false" >> "$GITHUB_OUTPUT" | |
| echo "New changes detected from upstream" | |
| # Get the list of new commits from upstream | |
| MERGE_BASE=$(git merge-base HEAD upstream/main) | |
| COMMITS=$(git log --pretty=format:"- %s ([%h](https://github.com/t3-oss/create-t3-turbo/commit/%H)) by %an" "$MERGE_BASE"..upstream/main) | |
| # Store commits in output (handle multiline) | |
| { | |
| echo "new_commits<<EOF" | |
| echo "$COMMITS" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Merge upstream changes | |
| if: steps.check.outputs.already_synced == 'false' | |
| id: merge | |
| run: | | |
| # Attempt to merge upstream changes into main | |
| if git merge upstream/main --no-edit --allow-unrelated-histories; then | |
| echo "Merge successful" | |
| echo "merge_status=success" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Merge conflict detected" | |
| echo "merge_status=conflict" >> "$GITHUB_OUTPUT" | |
| # Don't abort - let analyze step handle conflicts | |
| fi | |
| - name: Analyze conflicts | |
| if: steps.check.outputs.already_synced == 'false' && steps.merge.outputs.merge_status == 'conflict' | |
| id: analyze | |
| run: | | |
| # Get list of conflicted files | |
| CONFLICTED_FILES=$(git diff --name-only --diff-filter=U) | |
| # Check if ONLY pnpm-lock.yaml conflicts | |
| if [ "$CONFLICTED_FILES" = "pnpm-lock.yaml" ]; then | |
| echo "only_lockfile=true" >> "$GITHUB_OUTPUT" | |
| echo "Only pnpm-lock.yaml conflicts detected - will auto-resolve" | |
| else | |
| echo "only_lockfile=false" >> "$GITHUB_OUTPUT" | |
| echo "Multiple files conflict - manual resolution required" | |
| fi | |
| # Store conflicted files for issue creation | |
| { | |
| echo "conflicted_files<<EOF" | |
| echo "$CONFLICTED_FILES" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| # Abort merge if not auto-resolving | |
| if [ "$CONFLICTED_FILES" != "pnpm-lock.yaml" ]; then | |
| git merge --abort | |
| fi | |
| - name: Setup Node.js | |
| if: steps.check.outputs.already_synced == 'false' && steps.analyze.outputs.only_lockfile == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Setup pnpm | |
| if: steps.check.outputs.already_synced == 'false' && steps.analyze.outputs.only_lockfile == 'true' | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10.19.0 | |
| run_install: false | |
| - name: Auto-resolve lockfile conflict | |
| if: steps.check.outputs.already_synced == 'false' && steps.analyze.outputs.only_lockfile == 'true' | |
| id: auto_resolve | |
| run: | | |
| echo "Auto-resolving pnpm-lock.yaml conflict..." | |
| # Accept upstream's lockfile (preserves their dependency resolutions) | |
| git checkout --theirs pnpm-lock.yaml | |
| # Update lockfile based on merged package.json | |
| if ! pnpm install --no-frozen-lockfile; then | |
| echo "pnpm install failed - aborting merge" | |
| echo "resolution_status=failed" >> "$GITHUB_OUTPUT" | |
| git merge --abort | |
| exit 1 | |
| fi | |
| # Stage the updated lockfile | |
| git add pnpm-lock.yaml | |
| # Complete the merge commit | |
| git commit --no-edit | |
| echo "resolution_status=success" >> "$GITHUB_OUTPUT" | |
| echo "Lockfile conflict auto-resolved successfully" | |
| - name: Setup for CI checks | |
| if: steps.check.outputs.already_synced == 'false' && (steps.merge.outputs.merge_status == 'success' || (steps.analyze.outputs.only_lockfile == 'true' && steps.auto_resolve.outputs.resolution_status == 'success')) | |
| uses: ./tooling/github/setup | |
| - name: Copy env | |
| if: steps.check.outputs.already_synced == 'false' && (steps.merge.outputs.merge_status == 'success' || (steps.analyze.outputs.only_lockfile == 'true' && steps.auto_resolve.outputs.resolution_status == 'success')) | |
| shell: bash | |
| run: cp .env.example .env | |
| - name: Run CI - Lint | |
| if: steps.check.outputs.already_synced == 'false' && (steps.merge.outputs.merge_status == 'success' || (steps.analyze.outputs.only_lockfile == 'true' && steps.auto_resolve.outputs.resolution_status == 'success')) | |
| id: lint | |
| env: | |
| TURBO_TEAM: ${{ secrets.TURBO_TEAM }} | |
| TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} | |
| run: pnpm lint && pnpm lint:ws | |
| - name: Run CI - Format | |
| if: steps.check.outputs.already_synced == 'false' && (steps.merge.outputs.merge_status == 'success' || (steps.analyze.outputs.only_lockfile == 'true' && steps.auto_resolve.outputs.resolution_status == 'success')) | |
| id: format | |
| env: | |
| TURBO_TEAM: ${{ secrets.TURBO_TEAM }} | |
| TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} | |
| run: pnpm format | |
| - name: Run CI - Typecheck | |
| if: steps.check.outputs.already_synced == 'false' && (steps.merge.outputs.merge_status == 'success' || (steps.analyze.outputs.only_lockfile == 'true' && steps.auto_resolve.outputs.resolution_status == 'success')) | |
| id: typecheck | |
| env: | |
| TURBO_TEAM: ${{ secrets.TURBO_TEAM }} | |
| TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} | |
| run: pnpm typecheck | |
| - name: Run CI - Test | |
| if: steps.check.outputs.already_synced == 'false' && (steps.merge.outputs.merge_status == 'success' || (steps.analyze.outputs.only_lockfile == 'true' && steps.auto_resolve.outputs.resolution_status == 'success')) | |
| id: test | |
| env: | |
| TURBO_TEAM: ${{ secrets.TURBO_TEAM }} | |
| TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} | |
| run: pnpm test | |
| - name: Push to main | |
| if: steps.check.outputs.already_synced == 'false' && (steps.merge.outputs.merge_status == 'success' || (steps.analyze.outputs.only_lockfile == 'true' && steps.auto_resolve.outputs.resolution_status == 'success')) | |
| run: | | |
| git push origin main | |
| echo "✅ Successfully synced and pushed to main" | |
| echo "📝 Commits merged:" | |
| echo "${{ steps.check.outputs.new_commits }}" | |
| - name: Create Issue for CI Failure | |
| if: failure() && steps.check.outputs.already_synced == 'false' && (steps.merge.outputs.merge_status == 'success' || (steps.analyze.outputs.only_lockfile == 'true' && steps.auto_resolve.outputs.resolution_status == 'success')) | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const newCommits = '${{ steps.check.outputs.new_commits }}'; | |
| const lintOutcome = '${{ steps.lint.outcome }}' || 'N/A'; | |
| const formatOutcome = '${{ steps.format.outcome }}' || 'N/A'; | |
| const typecheckOutcome = '${{ steps.typecheck.outcome }}' || 'N/A'; | |
| const testOutcome = '${{ steps.test.outcome }}' || 'N/A'; | |
| const repository = '${{ github.repository }}'; | |
| const runId = '${{ github.run_id }}'; | |
| const actor = '${{ github.actor }}'; | |
| const body = '## ❌ Upstream Sync Failed - CI Checks Failed\n\n' + | |
| 'Attempted to sync changes from upstream but CI checks failed.\n\n' + | |
| '### New Commits from Upstream\n' + newCommits + '\n\n' + | |
| '### What Happened\n' + | |
| 'The merge was successful but one or more CI checks failed:\n' + | |
| '- Lint: ' + lintOutcome + '\n' + | |
| '- Format: ' + formatOutcome + '\n' + | |
| '- Typecheck: ' + typecheckOutcome + '\n' + | |
| '- Test: ' + testOutcome + '\n\n' + | |
| '### What to Do\n' + | |
| '1. Check the [workflow run](https://github.com/' + repository + '/actions/runs/' + runId + ') for details\n' + | |
| '2. Fix the failing checks locally\n' + | |
| '3. Push your fixes to main\n' + | |
| '4. Close this issue once resolved\n\n' + | |
| '### Workflow\n' + | |
| '- Triggered by: @' + actor + '\n' + | |
| '- Workflow run: ' + runId; | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: '❌ Upstream Sync Failed - CI Checks Failed', | |
| body: body, | |
| labels: ['upstream-sync', 'ci-failure'] | |
| }); | |
| - name: Create Issue for Merge Conflict | |
| if: steps.check.outputs.already_synced == 'false' && steps.merge.outputs.merge_status == 'conflict' && steps.analyze.outputs.only_lockfile == 'false' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const conflictedFiles = '${{ steps.analyze.outputs.conflicted_files }}'; | |
| const newCommits = '${{ steps.check.outputs.new_commits }}'; | |
| const actor = '${{ github.actor }}'; | |
| const runId = '${{ github.run_id }}'; | |
| const body = '## ⚠️ Upstream Sync Failed - Manual Resolution Required\n\n' + | |
| 'Attempted to sync changes from upstream [t3-oss/create-t3-turbo](https://github.com/t3-oss/create-t3-turbo) but encountered merge conflicts that require manual resolution.\n\n' + | |
| '### Conflicted Files\n```\n' + conflictedFiles + '\n```\n\n' + | |
| '### New Commits from Upstream\n' + newCommits + '\n\n' + | |
| '### What to Do\n' + | |
| '1. Manually merge the upstream changes:\n' + | |
| ' ```bash\n' + | |
| ' git fetch upstream\n' + | |
| ' git merge upstream/main\n' + | |
| ' ```\n' + | |
| '2. Resolve the conflicts in the files listed above\n' + | |
| '3. Test the changes locally\n' + | |
| '4. Commit and push to main\n' + | |
| '5. Close this issue\n\n' + | |
| '### Workflow\n' + | |
| '- Triggered by: @' + actor + '\n' + | |
| '- Workflow run: ' + runId; | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: '⚠️ Upstream Sync Failed - Manual Resolution Required', | |
| body: body, | |
| labels: ['upstream-sync', 'merge-conflict'] | |
| }); | |
| console.log('Issue created for merge conflicts'); | |