mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-23 07:18:56 -05:00
fix(ci): replace rebase sync with batched merge PR, add auto-retarget (#8957)
This commit is contained in:
@@ -52,3 +52,5 @@ moonshotai
|
||||
kimi
|
||||
ilike
|
||||
nvmrc
|
||||
retarget
|
||||
retargeted
|
||||
|
||||
74
.github/workflows/auto-retarget.yml
vendored
Normal file
74
.github/workflows/auto-retarget.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
name: Auto-retarget
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: [main]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
retarget:
|
||||
if: >
|
||||
github.repository_owner == 'better-auth' &&
|
||||
!startsWith(github.event.pull_request.head.ref, 'changeset-release/') &&
|
||||
github.actor != 'github-actions[bot]' &&
|
||||
github.actor != 'better-auth-releases[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Detect max bump type from changesets
|
||||
id: bump
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
MAX="patch"
|
||||
FILES=$(gh pr diff "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --name-only | grep '^\.changeset/.*\.md$' | grep -v 'README.md' || true)
|
||||
if [ -z "$FILES" ]; then
|
||||
echo "No changeset files in PR"
|
||||
echo "max=none" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
while IFS= read -r f; do
|
||||
[ -z "$f" ] && continue
|
||||
CONTENT=$(gh api "repos/$GITHUB_REPOSITORY/contents/$f?ref=${{ github.event.pull_request.head.sha }}" --jq '.content' | base64 -d 2>/dev/null || true)
|
||||
[ -z "$CONTENT" ] && continue
|
||||
BUMPS=$(echo "$CONTENT" | sed -n '/^---$/,/^---$/p' | grep -oE '"[^"]+"\s*:\s*(major|minor|patch)' | grep -oE '(major|minor|patch)$' | sort -u)
|
||||
if echo "$BUMPS" | grep -q "major"; then
|
||||
MAX="major"
|
||||
break
|
||||
fi
|
||||
if echo "$BUMPS" | grep -q "minor"; then
|
||||
MAX="minor"
|
||||
fi
|
||||
done <<< "$FILES"
|
||||
echo "max=$MAX" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check next branch exists
|
||||
if: steps.bump.outputs.max == 'minor' || steps.bump.outputs.max == 'major'
|
||||
id: next-check
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if gh api "repos/$GITHUB_REPOSITORY/branches/next" --silent 2>/dev/null; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::warning::next branch does not exist. Cannot retarget."
|
||||
fi
|
||||
|
||||
- name: Retarget PR to next
|
||||
if: (steps.bump.outputs.max == 'minor' || steps.bump.outputs.max == 'major') && steps.next-check.outputs.exists == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
MAX_BUMP: ${{ steps.bump.outputs.max }}
|
||||
run: |
|
||||
gh pr edit "$PR_NUMBER" --base next --repo "$GITHUB_REPOSITORY"
|
||||
gh pr edit "$PR_NUMBER" --add-label "retargeted-to-next" --repo "$GITHUB_REPOSITORY" || true
|
||||
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body \
|
||||
"This PR was automatically retargeted from \`main\` to \`next\` because it contains a **${MAX_BUMP}** changeset. The \`main\` branch only accepts \`patch\` (bug fix) changes. Features and breaking changes go through \`next\` for beta testing before promotion to stable."
|
||||
20
.github/workflows/promote.yml
vendored
20
.github/workflows/promote.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
echo "No changes to commit (previous run already pushed)"
|
||||
else
|
||||
git commit -m "chore: exit pre-release mode for v${VERSION}"
|
||||
git push origin next
|
||||
git push origin next || { echo "::error::Push to next failed. Check branch protection rules."; exit 1; }
|
||||
fi
|
||||
|
||||
- name: Create promote PR
|
||||
@@ -83,14 +83,22 @@ jobs:
|
||||
--base main \
|
||||
--head next \
|
||||
--title "chore: promote v${VERSION} to stable" \
|
||||
--body "Promotes beta to stable. **Use 'Rebase and merge'** to preserve individual commits. Merging publishes v${VERSION} to npm with the \`latest\` tag."
|
||||
--body "Promotes v${VERSION} from beta to stable. Merging this PR publishes all packages to npm with the \`latest\` tag.\n\n**This PR must be merged by an admin using 'Create a merge commit'** (not squash, not rebase). The admin bypass is required because main enforces linear history for regular PRs, but the promotion needs a merge commit to preserve individual contributor commits and their verified signatures."
|
||||
fi
|
||||
|
||||
- name: Summary
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
echo "### Beta promoted" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- **Version:** \`$VERSION\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- **Action:** Review and merge the PR to publish as stable." >> "$GITHUB_STEP_SUMMARY"
|
||||
{
|
||||
echo "### Beta promoted"
|
||||
echo ""
|
||||
echo "- **Version:** \`$VERSION\`"
|
||||
echo "- **Action:** Review and merge the PR with **Create a merge commit** (admin bypass)."
|
||||
echo ""
|
||||
echo "### After promotion"
|
||||
echo "Once the promote PR is merged and the sync PR (main to next) is merged:"
|
||||
echo '```'
|
||||
echo "pnpm changeset pre enter beta && git add .changeset/pre.json && git commit -m 'chore: re-enter beta pre-release mode' && git push origin next"
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
56
.github/workflows/release.yml
vendored
56
.github/workflows/release.yml
vendored
@@ -103,15 +103,11 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
||||
NPM_CONFIG_TAG: ${{ steps.npm-tag.outputs.tag }}
|
||||
|
||||
- name: Rebase next onto main
|
||||
- name: Sync main to next via PR
|
||||
if: github.ref_name == 'main'
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
git config user.name "better-auth-releases[bot]"
|
||||
git config user.email "273320539+better-auth-releases[bot]@users.noreply.github.com"
|
||||
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
|
||||
|
||||
git fetch origin next || { echo "next branch does not exist, skipping"; exit 0; }
|
||||
|
||||
AHEAD=$(git rev-list --count origin/next..origin/main)
|
||||
@@ -121,40 +117,40 @@ jobs:
|
||||
fi
|
||||
echo "Found $AHEAD commit(s) to sync from main to next"
|
||||
|
||||
# Capture next's SHA for force-with-lease (detached HEAD has no tracking ref)
|
||||
NEXT_SHA=$(git rev-parse origin/next)
|
||||
|
||||
git checkout origin/next
|
||||
if git rebase origin/main; then
|
||||
echo "Clean rebase"
|
||||
EXISTING=$(gh pr list --base next --head "${GITHUB_REPOSITORY_OWNER}:main" --state open --repo "$GITHUB_REPOSITORY" --json number --jq '.[0].number // empty')
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "Sync PR #$EXISTING already exists ($AHEAD commits pending)"
|
||||
else
|
||||
echo "::warning::Rebase conflicts — creating draft PR for manual resolution"
|
||||
git rebase --abort
|
||||
SYNC_BODY="Brings stable fixes from main into the next branch."
|
||||
SYNC_BODY="$SYNC_BODY\n\n**This PR must be merged by a maintainer using 'Create a merge commit'** (not squash, not rebase). This preserves individual fix commits and their verified signatures."
|
||||
SYNC_BODY="$SYNC_BODY\n\nIf there are conflicts, resolve them by keeping next's versions for \`package.json\` files (next is always ahead of main in version numbers)."
|
||||
|
||||
gh pr create \
|
||||
--base next \
|
||||
--head main \
|
||||
--title "chore: sync main to next (conflicts)" \
|
||||
--body "Automated rebase failed due to code conflicts. Resolve manually by rebasing next onto main." \
|
||||
--draft \
|
||||
|| true
|
||||
exit 0
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "chore: sync main to next" \
|
||||
--body "$(echo -e "$SYNC_BODY")" || true
|
||||
|
||||
EXISTING=$(gh pr list --base next --head "${GITHUB_REPOSITORY_OWNER}:main" --state open --repo "$GITHUB_REPOSITORY" --json number --jq '.[0].number // empty')
|
||||
fi
|
||||
|
||||
# Re-enter pre-release mode if needed (after a promote cycle),
|
||||
# but only if there's no open promote PR (to avoid invalidating it)
|
||||
if [ ! -f .changeset/pre.json ]; then
|
||||
OPEN_PROMOTE=$(gh pr list --base main --head next --state open --json number --jq 'length')
|
||||
if [ "$OPEN_PROMOTE" -eq 0 ]; then
|
||||
pnpm changeset pre enter beta
|
||||
git add .changeset/pre.json
|
||||
git commit -m "chore: re-enter beta pre-release mode"
|
||||
else
|
||||
echo "Promote PR is open, skipping pre-mode re-entry"
|
||||
# Check for merge conflicts (GitHub computes merge state asynchronously)
|
||||
if [ -n "$EXISTING" ]; then
|
||||
sleep 10
|
||||
for _ in 1 2 3 4 5; do
|
||||
MERGEABLE=$(gh pr view "$EXISTING" --repo "$GITHUB_REPOSITORY" --json mergeable --jq '.mergeable')
|
||||
[ "$MERGEABLE" != "UNKNOWN" ] && break
|
||||
sleep 10
|
||||
done
|
||||
if [ "$MERGEABLE" = "CONFLICTING" ]; then
|
||||
gh pr edit "$EXISTING" --repo "$GITHUB_REPOSITORY" --add-label "has-conflicts" || true
|
||||
echo "::warning::Sync PR #$EXISTING has merge conflicts. Resolve by keeping next's versions for package.json files."
|
||||
elif [ "$MERGEABLE" = "MERGEABLE" ]; then
|
||||
gh pr edit "$EXISTING" --repo "$GITHUB_REPOSITORY" --remove-label "has-conflicts" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
git push origin HEAD:refs/heads/next --force-with-lease=refs/heads/next:"$NEXT_SHA"
|
||||
|
||||
snapshot:
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.snapshot != '' && github.repository_owner == 'better-auth'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/verify-changesets.yml
vendored
4
.github/workflows/verify-changesets.yml
vendored
@@ -127,11 +127,11 @@ jobs:
|
||||
for f in $CHANGESET_FILES; do
|
||||
BUMPS=$(sed -n '/^---$/,/^---$/p' "$f" | grep -oE '(major|minor|patch)' | sort -u)
|
||||
if echo "$BUMPS" | grep -q "major"; then
|
||||
echo "::error::Changeset $f contains a 'major' bump. Only 'patch' bumps are allowed on '$BASE_REF'."
|
||||
echo "::error::Changeset $f contains a 'major' bump. Only 'patch' bumps are allowed on '$BASE_REF'. PRs with minor/major bumps should target 'next' (auto-retarget may not have run yet)."
|
||||
exit 1
|
||||
fi
|
||||
if echo "$BUMPS" | grep -q "minor"; then
|
||||
echo "::error::Changeset $f contains a 'minor' bump. Only 'patch' bumps are allowed on '$BASE_REF'."
|
||||
echo "::error::Changeset $f contains a 'minor' bump. Only 'patch' bumps are allowed on '$BASE_REF'. PRs with minor/major bumps should target 'next' (auto-retarget may not have run yet)."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
3
.github/zizmor.yml
vendored
3
.github/zizmor.yml
vendored
@@ -5,6 +5,9 @@ rules:
|
||||
# never checks out fork code. The pull_request_target trigger is required
|
||||
# for the action to post/delete PR comments.
|
||||
- semantic-pull-request.yml
|
||||
# Intentional: reads .changeset/*.md content via GitHub API only.
|
||||
# No checkout of fork code. No code execution.
|
||||
- auto-retarget.yml
|
||||
cache-poisoning:
|
||||
ignore:
|
||||
# False positive: neither setup-node call has caching enabled (no `cache:` param).
|
||||
|
||||
Reference in New Issue
Block a user