From 33893cb92c1ffddaa11df32429945645e6f38622 Mon Sep 17 00:00:00 2001 From: Cryptojym Date: Sat, 7 Jun 2025 00:23:18 -0600 Subject: [PATCH] Add GitHub automation workflows - CI/CD pipeline - Auto-approval for PRs - Auto-merge functionality - Auto-PR creation - Post-merge automation This enables full automation for the repository. --- .github/actions/auto-merge/action.yml | 24 +++++ .github/workflows/auto-merge-advanced.yml | 39 ++++++++ .github/workflows/auto-pr-workflow.yml | 115 ++++++++++++++++++++++ .github/workflows/automerge.yml | 28 ++++++ .github/workflows/ci.yml | 69 +++++++++++++ .github/workflows/pr-merge-overseer.yml | 82 +++++++++++++++ 6 files changed, 357 insertions(+) create mode 100644 .github/actions/auto-merge/action.yml create mode 100644 .github/workflows/auto-merge-advanced.yml create mode 100644 .github/workflows/auto-pr-workflow.yml create mode 100644 .github/workflows/automerge.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pr-merge-overseer.yml diff --git a/.github/actions/auto-merge/action.yml b/.github/actions/auto-merge/action.yml new file mode 100644 index 0000000..2feab02 --- /dev/null +++ b/.github/actions/auto-merge/action.yml @@ -0,0 +1,24 @@ +name: "Enable PR Auto-Merge" +description: "Composite action to enable auto-merge on a pull request using the GitHub API." + +inputs: + pr_number: + description: "Pull request number to merge" + required: true + merge_method: + description: "Merge method: merge, squash, or rebase" + required: false + default: "squash" + token: + description: "GitHub token with pull-request write permissions" + required: true + +runs: + using: "composite" + steps: + - name: Enable auto-merge for PR + uses: peter-evans/enable-pull-request-automerge@v3 + with: + token: ${{ inputs.token }} + pull-request-number: ${{ inputs.pr_number }} + merge-method: ${{ inputs.merge_method }} \ No newline at end of file diff --git a/.github/workflows/auto-merge-advanced.yml b/.github/workflows/auto-merge-advanced.yml new file mode 100644 index 0000000..91bb44c --- /dev/null +++ b/.github/workflows/auto-merge-advanced.yml @@ -0,0 +1,39 @@ +name: Auto Merge on Successful CI + +on: + workflow_run: + workflows: ["CI"] + types: + - completed + +permissions: + pull-requests: write + contents: write + +jobs: + enable-auto-merge: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} + steps: + - name: Get PR number + id: pr + run: | + # Extract PR number from workflow run + PR_NUMBER=$(echo '${{ toJson(github.event.workflow_run.pull_requests) }}' | jq -r '.[0].number // empty') + if [ -z "$PR_NUMBER" ]; then + echo "No PR associated with this workflow run" + exit 0 + fi + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + + - name: Enable auto-merge + if: steps.pr.outputs.pr_number + run: | + gh pr merge ${{ steps.pr.outputs.pr_number }} \ + --auto \ + --squash \ + --delete-branch \ + --repo ${{ github.repository }} \ + || echo "Auto-merge may already be enabled or PR not ready" \ No newline at end of file diff --git a/.github/workflows/auto-pr-workflow.yml b/.github/workflows/auto-pr-workflow.yml new file mode 100644 index 0000000..77052d2 --- /dev/null +++ b/.github/workflows/auto-pr-workflow.yml @@ -0,0 +1,115 @@ +name: Auto Create PR + +on: + push: + branches: + - '**' + - '!main' + - '!master' + +permissions: + contents: read + pull-requests: write + +jobs: + create-pr: + runs-on: ubuntu-latest + # Only run if this is not already a PR branch + if: github.event_name == 'push' && !contains(github.event.ref, 'pull/') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract branch info + id: branch + run: | + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + echo "name=$BRANCH_NAME" >> $GITHUB_OUTPUT + + # Generate PR title from branch name + # Convert branch name to title case and replace separators + PR_TITLE=$(echo "$BRANCH_NAME" | sed 's/[-_]/ /g' | sed 's/\b\(.\)/\u\1/g') + echo "title=$PR_TITLE" >> $GITHUB_OUTPUT + + - name: Check if PR already exists + id: check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_EXISTS=$(gh pr list --head "${{ steps.branch.outputs.name }}" --json number --jq '.[0].number // empty') + if [ -n "$PR_EXISTS" ]; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "number=$PR_EXISTS" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Get commit messages for PR body + if: steps.check.outputs.exists == 'false' + id: commits + run: | + # Get commits that are in this branch but not in main + COMMITS=$(git log origin/main..HEAD --pretty=format:"- %s" --reverse) + + # Create PR body + { + echo "body<> $GITHUB_OUTPUT + + - name: Create Pull Request + if: steps.check.outputs.exists == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr create \ + --title "${{ steps.branch.outputs.title }}" \ + --body "${{ steps.commits.outputs.body }}" \ + --head "${{ steps.branch.outputs.name }}" \ + --base main + + - name: Enable auto-merge + if: steps.check.outputs.exists == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Wait a moment for PR to be fully created + sleep 2 + + # Get the PR number that was just created + PR_NUMBER=$(gh pr list --head "${{ steps.branch.outputs.name }}" --json number --jq '.[0].number') + + # Enable auto-merge if repository supports it + gh pr merge "$PR_NUMBER" --auto --squash || echo "Auto-merge not available or already enabled" + + - name: Add labels + if: steps.check.outputs.exists == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=$(gh pr list --head "${{ steps.branch.outputs.name }}" --json number --jq '.[0].number') + + # Add auto-generated label + gh pr edit "$PR_NUMBER" --add-label "auto-pr" || echo "Label doesn't exist yet" + + # Add feature/fix/chore label based on branch name + if [[ "${{ steps.branch.outputs.name }}" == feat* ]] || [[ "${{ steps.branch.outputs.name }}" == feature* ]]; then + gh pr edit "$PR_NUMBER" --add-label "feature" || true + elif [[ "${{ steps.branch.outputs.name }}" == fix* ]] || [[ "${{ steps.branch.outputs.name }}" == bug* ]]; then + gh pr edit "$PR_NUMBER" --add-label "bug" || true + elif [[ "${{ steps.branch.outputs.name }}" == chore* ]] || [[ "${{ steps.branch.outputs.name }}" == refactor* ]]; then + gh pr edit "$PR_NUMBER" --add-label "chore" || true + fi \ No newline at end of file diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000..fe395e5 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,28 @@ +name: Enable Auto-Merge + +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + +permissions: + pull-requests: write + contents: write + +jobs: + approve-and-enable: + runs-on: ubuntu-latest + steps: + - name: Auto-approve pull request + uses: hmarr/auto-approve-action@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable auto-merge for PR + uses: peter-evans/enable-pull-request-automerge@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pull-request-number: ${{ github.event.pull_request.number }} + merge-method: squash \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a844272 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12"] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements*.txt') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.python-version }}- + ${{ runner.os }}-pip- + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r requirements-dev.txt + + - name: Run linting + run: | + # Add your linting commands here + # Examples: + # ruff check . + # black --check . + # flake8 . + echo "Add your linting commands here" + + - name: Run tests with coverage + run: | + # Adjust this command for your test framework + pytest --cov=. --cov-report=xml || echo "No tests found" + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} + + # Optional: Build Docker image + - name: Build Docker image + if: hashFiles('Dockerfile') != '' + run: docker build -t ${{ github.repository }}:${{ github.sha }} . + + # Optional: Security scan + - name: Run security scan + continue-on-error: true + run: | + pip install safety + safety check || true \ No newline at end of file diff --git a/.github/workflows/pr-merge-overseer.yml b/.github/workflows/pr-merge-overseer.yml new file mode 100644 index 0000000..b59f2c2 --- /dev/null +++ b/.github/workflows/pr-merge-overseer.yml @@ -0,0 +1,82 @@ +# ================================================ +# Post-Merge Automation +# ------------------------------------------------ +# PURPOSE +# When a Pull Request (PR) is merged, this workflow +# handles follow-up tasks like closing issues, +# adding labels, and updating external tools. +# ------------------------------------------------ +# TRIGGER +# – on: pull_request.closed when merged == true +# ================================================ + +name: Post-Merge Automation + +on: + pull_request: + types: [closed] + +jobs: + post-merge: + # Only proceed if the PR was actually merged (not just closed) + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Close linked issues + - name: Close linked issues + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const pr = context.payload.pull_request; + const body = pr.body || ''; + + // Find issue references like #123, fixes #456, closes #789 + const issueRefs = body.match(/(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*#(\d+)/gi) || []; + + for (const ref of issueRefs) { + const issueNumber = ref.match(/#(\d+)/)[1]; + try { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + state: 'closed' + }); + console.log(`Closed issue #${issueNumber}`); + } catch (error) { + console.log(`Failed to close issue #${issueNumber}: ${error.message}`); + } + } + + # Add merged label + - name: Add merged label + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels: ['merged'] + }); + + # Optional: Update external tools + # Uncomment and configure as needed + + # - name: Update Todoist + # if: ${{ secrets.TODOIST_API_KEY != '' }} + # run: | + # echo "Update Todoist tasks related to PR #${{ github.event.pull_request.number }}" + # # Add your Todoist API calls here + + # - name: Update Linear + # if: ${{ secrets.LINEAR_API_KEY != '' }} + # run: | + # echo "Update Linear issues related to PR #${{ github.event.pull_request.number }}" + # # Add your Linear API calls here \ No newline at end of file