diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml new file mode 100644 index 0000000..0fde858 --- /dev/null +++ b/.github/workflows/auto-assign.yml @@ -0,0 +1,95 @@ +name: Auto Assign Owner + +on: + issues: + types: [opened] + pull_request_target: + types: [opened] + +permissions: + issues: write + pull-requests: write + contents: read + +jobs: + check-and-assign: + runs-on: ubuntu-latest + steps: + - name: Check collaborator count and assign owner + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Get collaborators with push or admin access + const { data: collaborators } = await github.rest.repos.listCollaborators({ + owner, + repo, + permission: 'push' + }); + + // Filter to only users with push or admin (not just read) + const pushCollaborators = collaborators.filter(c => + c.permissions?.push || c.permissions?.admin + ); + + console.log(`Found ${pushCollaborators.length} collaborators with push access`); + + // Only auto-assign if there's 1 or fewer collaborators with push access + if (pushCollaborators.length > 1) { + console.log('Multiple collaborators found, skipping auto-assign'); + return; + } + + // Determine the assignee (repo owner) + const assignee = owner; + + if (context.eventName === 'issues') { + const issue = context.payload.issue; + + // Skip if already assigned + if (issue.assignees && issue.assignees.length > 0) { + console.log('Issue already has assignees, skipping'); + return; + } + + // Skip if author is a bot + if (issue.user.type === 'Bot') { + console.log('Issue author is a bot, skipping'); + return; + } + + await github.rest.issues.addAssignees({ + owner, + repo, + issue_number: issue.number, + assignees: [assignee] + }); + + console.log(`Assigned issue #${issue.number} to ${assignee}`); + + } else if (context.eventName === 'pull_request_target') { + const pr = context.payload.pull_request; + + // Skip if already assigned + if (pr.assignees && pr.assignees.length > 0) { + console.log('PR already has assignees, skipping'); + return; + } + + // Skip if author is a bot + if (pr.user.type === 'Bot') { + console.log('PR author is a bot, skipping'); + return; + } + + await github.rest.issues.addAssignees({ + owner, + repo, + issue_number: pr.number, + assignees: [assignee] + }); + + console.log(`Assigned PR #${pr.number} to ${assignee}`); + } diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..ef3542f --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,71 @@ +# Dependabot Auto-Merge +# +# Automatically approves and merges Dependabot PRs for patch/minor updates. +# Major updates require human review. +# +# Prerequisites: +# 1. Enable auto-merge in repo settings (Settings → General → Allow auto-merge) +# 2. Branch protection on main requiring: +# - Status checks to pass +# - At least 1 approval +# +# Security: +# - Only auto-merges patch/minor updates +# - Blocks PRs with high-severity vulnerabilities +# - Major updates always require human review + +name: Dependabot Auto-Merge + +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Dependency Review + uses: actions/dependency-review-action@v4 + with: + fail-on-severity: high + + - name: Fetch Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Auto-approve patch/minor updates + if: steps.metadata.outputs.update-type != 'version-update:semver-major' + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable auto-merge for patch/minor + if: steps.metadata.outputs.update-type != 'version-update:semver-major' + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Comment on major updates + if: steps.metadata.outputs.update-type == 'version-update:semver-major' + run: | + gh pr comment "$PR_URL" --body "⚠️ **Major version update** - requires manual review. + + **Update type:** ${{ steps.metadata.outputs.update-type }} + **Dependency:** ${{ steps.metadata.outputs.dependency-names }} + **From:** ${{ steps.metadata.outputs.previous-version }} + **To:** ${{ steps.metadata.outputs.new-version }} + + Please review the changelog for breaking changes before approving." + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/dependabot-issue.yml b/.github/workflows/dependabot-issue.yml new file mode 100644 index 0000000..5c7d27f --- /dev/null +++ b/.github/workflows/dependabot-issue.yml @@ -0,0 +1,54 @@ +name: Create Issue for Dependabot PRs + +on: + pull_request_target: + types: [opened] + +permissions: + issues: write + pull-requests: read + +jobs: + create-tracking-issue: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - name: Create tracking issue for Dependabot PR + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Extract version info from PR title + // Typical format: "Bump actions/checkout from 3 to 4" + const title = pr.title; + + const issueTitle = `deps: ${title}`; + const issueBody = `## Dependabot Update + + ${pr.body || 'Automated dependency update.'} + + ## Pull Request + + - PR: #${pr.number} + - Author: @${pr.user.login} + - URL: ${pr.html_url} + + --- + This issue was automatically created to track the Dependabot update. + `; + + const { data: issue } = await github.rest.issues.create({ + owner, + repo, + title: issueTitle, + body: issueBody, + labels: ['dependencies', 'github-actions'] + }); + + console.log(`Created tracking issue #${issue.number} for PR #${pr.number}`); + + // Note: We don't update the PR body because Dependabot PRs have + // restricted permissions. The issue references the PR instead.