diff --git a/.github/workflows/post-merge-cleanup.yml b/.github/workflows/post-merge-cleanup.yml new file mode 100644 index 0000000..2958c60 --- /dev/null +++ b/.github/workflows/post-merge-cleanup.yml @@ -0,0 +1,38 @@ +name: Clean up next branch after merge + +on: + pull_request: + types: [closed] + branches: + - master + +jobs: + cleanup: + runs-on: ubuntu-latest + permissions: + contents: write + # Only run if PR was merged (not just closed) and it was from next branch + if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'next' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Setup Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Reset next branch to master + run: | + git checkout -B next origin/master + + - name: Push updated next branch + run: | + git push --force-with-lease origin next + + - name: Summary + run: | + echo "::notice::Post-merge cleanup completed" diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 0000000..e3e8571 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,274 @@ +name: Prepare release PR from next branch + +on: + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to prepare release for' + required: true + type: string + version_type: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + default: patch + +env: + REPO_B_TARGET: 'main' + REPO_B_NEXT: 'next' + +jobs: + prepare-release: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Setup Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Checkout next branch + run: | + git checkout -B ${{ env.REPO_B_NEXT }} origin/${{ env.REPO_B_NEXT }} + + - name: Verify PR exists and get info + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Verifying PR #${{ github.event.inputs.pr_number }} exists..." + PR_STATE=$(gh pr view ${{ github.event.inputs.pr_number }} --json state --jq '.state' 2>/dev/null || echo "NOT_FOUND") + + if [ "$PR_STATE" = "NOT_FOUND" ]; then + echo "::error::PR #${{ github.event.inputs.pr_number }} not found" + exit 1 + fi + + if [ "$PR_STATE" != "OPEN" ]; then + echo "::error::PR #${{ github.event.inputs.pr_number }} is $PR_STATE, expected OPEN" + exit 1 + fi + + echo "PR #${{ github.event.inputs.pr_number }} is open and ready for release preparation" + + - name: Determine version bump + id: version + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Fetching latest release from GitHub..." + LATEST_RELEASE=$(gh release list --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || echo "") + + if [ -n "$LATEST_RELEASE" ]; then + CURRENT_VERSION="$LATEST_RELEASE" + echo "Found latest release: $CURRENT_VERSION" + else + echo "No releases found, starting from v0.0.0" + CURRENT_VERSION="v0.0.0" + fi + + # Parse version components (remove 'v' prefix if present) + version_number=$(echo $CURRENT_VERSION | sed 's/^v//') + major=$(echo $version_number | cut -d. -f1) + minor=$(echo $version_number | cut -d. -f2) + patch=$(echo $version_number | cut -d. -f3) + + echo "Current version components: major=$major, minor=$minor, patch=$patch" + + # Calculate new version based on input + case "${{ github.event.inputs.version_type }}" in + major) + major=$((major + 1)) + minor=0 + patch=0 + echo "Major version bump requested" + ;; + minor) + minor=$((minor + 1)) + patch=0 + echo "Minor version bump requested" + ;; + patch) + patch=$((patch + 1)) + echo "Patch version bump requested" + ;; + esac + + NEW_VERSION="v${major}.${minor}.${patch}" + echo "Version bump: $CURRENT_VERSION → $NEW_VERSION" + + echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + + - name: Generate changelog entry + run: | + echo "Generating changelog entry for ${{ steps.version.outputs.new_version }}" + + # Get all commits in this PR (from target branch to current HEAD) + ALL_PR_COMMITS=$(git rev-list --reverse origin/${{ env.REPO_B_TARGET }}..HEAD) + + # Create changelog entry + changelog_entry="## ${{ steps.version.outputs.new_version }} ($(date +%Y-%m-%d)) + + Full Changelog: [${{ steps.version.outputs.current_version }}...${{ steps.version.outputs.new_version }}](https://github.com/${{ github.repository }}/compare/${{ steps.version.outputs.current_version }}...${{ steps.version.outputs.new_version }}) + " + + # Add features section + has_feat_commits=false + for commit in $ALL_PR_COMMITS; do + if [ -n "$commit" ]; then + commit_msg=$(git log --format="%s" -1 "$commit") + if echo "$commit_msg" | grep -q "feat:"; then + if [ "$has_feat_commits" = false ]; then + changelog_entry="${changelog_entry} + ### Features + " + has_feat_commits=true + fi + # Remove "feat: " prefix and add to changelog with commit link + feature_desc=$(echo "$commit_msg" | sed 's/feat: //') + short_sha=$(echo "$commit" | cut -c1-7) + changelog_entry="${changelog_entry}* ${feature_desc} ([${short_sha}](https://github.com/${{ github.repository }}/commit/${commit})) + " + fi + fi + done + + if [ "$has_feat_commits" = true ]; then + changelog_entry="${changelog_entry} + " + fi + + # Add fixes section + has_fixes=false + for commit in $ALL_PR_COMMITS; do + if [ -n "$commit" ]; then + commit_msg=$(git log --format="%s" -1 "$commit") + if echo "$commit_msg" | grep -q "fix:"; then + if [ "$has_fixes" = false ]; then + changelog_entry="${changelog_entry} + ### Bug Fixes + " + has_fixes=true + fi + # Remove "fix: " prefix and add to changelog with commit link + fix_desc=$(echo "$commit_msg" | sed 's/fix: //') + short_sha=$(echo "$commit" | cut -c1-7) + changelog_entry="${changelog_entry}* ${fix_desc} ([${short_sha}](https://github.com/${{ github.repository }}/commit/${commit})) + " + fi + fi + done + + if [ "$has_fixes" = true ]; then + changelog_entry="${changelog_entry} + " + fi + + # Add other changes section + has_other=false + for commit in $ALL_PR_COMMITS; do + if [ -n "$commit" ]; then + commit_msg=$(git log --format="%s" -1 "$commit") + if ! echo "$commit_msg" | grep -q -E "^(feat:|fix:|Update sync state)"; then + if [ "$has_other" = false ]; then + changelog_entry="${changelog_entry} + ### Other Changes + " + has_other=true + fi + short_sha=$(echo "$commit" | cut -c1-7) + changelog_entry="${changelog_entry}* ${commit_msg} ([${short_sha}](https://github.com/${{ github.repository }}/commit/${commit})) + " + fi + fi + done + + # Save changelog entry to temp file + echo "$changelog_entry" > /tmp/changelog_entry.md + echo "Generated changelog entry:" + cat /tmp/changelog_entry.md + + - name: Update version files + run: | + # Update pyproject.toml if it exists + if [ -f pyproject.toml ]; then + echo "Updating version in pyproject.toml" + # Remove 'v' prefix for pyproject.toml + VERSION_NUMBER=$(echo "${{ steps.version.outputs.new_version }}" | sed 's/^v//') + + # Update version in pyproject.toml using sed + sed -i "s/^version = .*/version = \"$VERSION_NUMBER\"/" pyproject.toml + + echo "Updated pyproject.toml version to: $VERSION_NUMBER" + git add pyproject.toml + fi + + - name: Update CHANGELOG.md + run: | + echo "Updating CHANGELOG.md" + # Insert new entry at the top (after title if it exists) + if head -1 CHANGELOG.md | grep -q "^# "; then + # Has title, insert after first line + head -1 CHANGELOG.md > /tmp/changelog_new.md + echo "" >> /tmp/changelog_new.md + cat /tmp/changelog_entry.md >> /tmp/changelog_new.md + tail -n +2 CHANGELOG.md >> /tmp/changelog_new.md + else + # No title, insert at beginning + cat /tmp/changelog_entry.md > /tmp/changelog_new.md + cat CHANGELOG.md >> /tmp/changelog_new.md + fi + mv /tmp/changelog_new.md CHANGELOG.md + + git add CHANGELOG.md + echo "Updated CHANGELOG.md" + + - name: Create release commit + run: | + git commit -m "release: ${{ steps.version.outputs.new_version }}" + + echo "Created release commit for ${{ steps.version.outputs.new_version }}" + + git push origin ${{ env.REPO_B_NEXT }} + + - name: Update PR with release info + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Create updated PR body + PR_BODY="## Release ${{ steps.version.outputs.new_version }} + + ## Changelog + + $(cat /tmp/changelog_entry.md) + + --- + *Release commit added by prepare-release workflow.* + *This PR is now ready to be reviewed and merged.*" + + # Update the PR title and body + gh pr edit ${{ github.event.inputs.pr_number }} \ + --title "Release ${{ steps.version.outputs.new_version }}" \ + --body "$PR_BODY" + + echo "Updated PR #${{ github.event.inputs.pr_number }} with release information" + + + - name: Summary + run: | + echo "::notice::Release preparation completed successfully" + echo "::notice::PR #${{ github.event.inputs.pr_number }} updated with release commit for ${{ steps.version.outputs.new_version }}" + echo "::notice::Review the PR and merge when ready" diff --git a/.sync_state b/.sync_state new file mode 100644 index 0000000..2471320 --- /dev/null +++ b/.sync_state @@ -0,0 +1 @@ +1d2c8905b08c612264d01f337870cd7465ad546e