Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .github/workflows/auto-assign.yml
Original file line number Diff line number Diff line change
@@ -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}`);
}
71 changes: 71 additions & 0 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
@@ -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 }}
54 changes: 54 additions & 0 deletions .github/workflows/dependabot-issue.yml
Original file line number Diff line number Diff line change
@@ -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.
Loading