Skip to content

Sync Upstream

Sync Upstream #136

Workflow file for this run

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');