New scan PRs action (#623)

Signed-off-by: Glenn Jocher <[email protected]>
Co-authored-by: UltralyticsAssistant <[email protected]>
This commit is contained in:
Glenn Jocher
2025-10-30 16:46:23 +08:00
committed by GitHub
co-authored by UltralyticsAssistant
parent 846c9c00ea
commit 8be7f6bb65
7 changed files with 461 additions and 182 deletions
-116
View File
@@ -1,116 +0,0 @@
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
# List Open PRs across Ultralytics repositories
# This workflow scans all public repos in the Ultralytics organization and lists open PRs
# For repos with >10 open PRs, only the 10 most recent are shown
name: List Open PRs
on:
workflow_dispatch:
schedule:
- cron: "0 3 * * *" # daily at 03:00 UTC
permissions:
contents: write
pull-requests: write
jobs:
list-prs:
runs-on: ubuntu-latest
steps:
- name: List open PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: python
run: |
import os, json, subprocess
from datetime import datetime, timezone
# Get active public repos
result = subprocess.run(
["gh", "repo", "list", "ultralytics", "--limit", "1000", "--json", "name,url,isArchived", "--visibility", "public"],
capture_output=True, text=True, check=True
)
repos = {r["name"]: r["url"] for r in json.loads(result.stdout) if not r["isArchived"]}
# Get all open PRs
result = subprocess.run(
["gh", "search", "prs", "--owner", "ultralytics", "--state", "open", "--limit", "1000",
"--json", "repository,number,title,url,createdAt", "--sort", "created", "--order", "desc"],
capture_output=True, text=True, check=True
)
all_prs = json.loads(result.stdout)
summary = ["# 🔍 Open Pull Requests - Ultralytics Organization\n"]
for repo_name in sorted(set(pr["repository"]["name"] for pr in all_prs)):
if repo_name not in repos:
continue
repo_prs = [pr for pr in all_prs if pr["repository"]["name"] == repo_name]
summary.append(f"## 📦 [{repo_name}]({repos[repo_name]}) - {len(repo_prs)} open PR{'s' if len(repo_prs) > 1 else ''}")
for pr in repo_prs[:30]:
age_days = (datetime.now(timezone.utc) - datetime.fromisoformat(pr["createdAt"].replace("Z", "+00:00"))).days
if age_days == 0:
emoji = "🆕"
elif age_days <= 7:
emoji = "🟢"
elif age_days <= 30:
emoji = "🟡"
else:
emoji = "🔴"
if age_days == 0:
summary.append(f"- 🔀 [#{pr['number']}]({pr['url']}) {pr['title']} {emoji} NEW")
else:
summary.append(f"- 🔀 [#{pr['number']}]({pr['url']}) {pr['title']} {emoji} {age_days} days")
if len(repo_prs) > 30:
summary.append(f"- ... {len(repo_prs) - 30} more PRs")
summary.append("")
with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as f:
f.write("\n".join(summary))
- name: Merge Dependabot Actions PRs
env:
GH_TOKEN: ${{ secrets._GITHUB_TOKEN }}
shell: python
run: |
import os, json, subprocess
result = subprocess.run(
["gh", "repo", "list", "ultralytics", "--limit", "1000", "--json", "name,isArchived", "--visibility", "public"],
capture_output=True, text=True, check=True
)
repos = [r["name"] for r in json.loads(result.stdout) if not r["isArchived"]]
summary = ["\n# 🤖 Merging Dependabot GitHub Actions PRs\n"]
for repo in repos:
result = subprocess.run(
["gh", "pr", "list", "--repo", f"ultralytics/{repo}", "--author", "app/dependabot",
"--state", "open", "--json", "number,files,mergeable"],
capture_output=True, text=True
)
if result.returncode != 0:
continue
merged = 0
for pr in json.loads(result.stdout):
if merged >= 1 or pr["mergeable"] != "MERGEABLE":
continue
if all(f["path"].startswith(".github/workflows/") for f in pr["files"]):
result = subprocess.run(
["gh", "pr", "merge", str(pr["number"]), "--repo", f"ultralytics/{repo}",
"--squash", "--admin"],
capture_output=True
)
if result.returncode == 0:
summary.append(f"- ✅ Merged ultralytics/{repo}#{pr['number']}")
merged += 1
with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as f:
f.write("\n".join(summary))
+22
View File
@@ -0,0 +1,22 @@
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
# List Open PRs across Ultralytics repositories and auto-merge eligible Dependabot PRs
name: Scan PRs
on:
workflow_dispatch:
schedule:
- cron: "0 6 * * *" # daily at 06:00 UTC
permissions:
contents: write
pull-requests: write
jobs:
scan-prs:
runs-on: ubuntu-latest
steps:
- uses: ultralytics/actions/scan-prs@main
with:
token: ${{ secrets._GITHUB_TOKEN }}
+113 -65
View File
@@ -1,110 +1,158 @@
<a href="https://www.ultralytics.com/"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
# 🚀 Ultralytics Actions: AI-powered formatting, labeling & PR summaries for Python and Markdown
# 🚀 Ultralytics Actions
Welcome to the [Ultralytics Actions](https://github.com/ultralytics/actions) repository, your go-to solution for maintaining consistent code quality across Ultralytics Python and Swift projects. This GitHub Action is designed to automate the formatting of Python, Markdown, and Swift files, ensuring adherence to our coding standards and enhancing project maintainability.
Welcome to [Ultralytics Actions](https://github.com/ultralytics/actions) - a collection of GitHub Actions and Python tools for automating code quality, PR management, and CI/CD workflows across Ultralytics projects.
[![GitHub Actions Marketplace](https://img.shields.io/badge/Marketplace-Ultralytics_Actions-blue?style=flat&logo=github)](https://github.com/marketplace/actions/ultralytics-actions)
[![Actions CI](https://github.com/ultralytics/actions/actions/workflows/ci.yml/badge.svg)](https://github.com/ultralytics/actions/actions/workflows/ci.yml)
[![Ultralytics Actions](https://github.com/ultralytics/actions/actions/workflows/format.yml/badge.svg)](https://github.com/ultralytics/actions/actions/workflows/format.yml)
[![List Open PRs](https://github.com/ultralytics/actions/actions/workflows/open-prs.yml/badge.svg)](https://github.com/ultralytics/actions/actions/workflows/open-prs.yml)
[![Scan PRs](https://github.com/ultralytics/actions/actions/workflows/scan-prs.yml/badge.svg)](https://github.com/ultralytics/actions/actions/workflows/scan-prs.yml)
[![codecov](https://codecov.io/github/ultralytics/actions/graph/badge.svg?token=DoizJ1WS6j)](https://codecov.io/github/ultralytics/actions)
[![Ultralytics Discord](https://img.shields.io/discord/1089800235347353640?logo=discord&logoColor=white&label=Discord&color=blue)](https://discord.com/invite/ultralytics)
[![Ultralytics Forums](https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue)](https://community.ultralytics.com/)
[![Ultralytics Reddit](https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue)](https://reddit.com/r/ultralytics)
## 📄 Actions Description
## 📦 Repository Contents
Ultralytics Actions automatically applies formats, updates, and enhancements using a suite of powerful tools:
This repository provides three main components:
- **Python Code:** Formatted using [Ruff](https://github.com/astral-sh/ruff), an extremely fast Python linter and formatter.
- **Markdown Files:** Styled with [Prettier](https://github.com/prettier/prettier) to ensure consistent documentation appearance.
- **Docstrings:** Cleaned and standardized using [docformatter](https://github.com/PyCQA/docformatter).
- **Swift Code:** Formatted with [`swift-format`](https://github.com/swiftlang/swift-format) to maintain a uniform coding style across Swift projects. _(Note: Requires the `macos-latest` runner.)_
- **Spell Check:** Common misspellings are caught using [codespell](https://github.com/codespell-project/codespell).
- **Broken Links Check:** Broken links in documentation and Markdown files are identified using [Lychee](https://github.com/lycheeverse/lychee).
- **PR Summary:** Concise Pull Request summaries are generated using [OpenAI](https://openai.com/) GPT-5, improving clarity and review efficiency.
- **PR Review:** AI-powered code reviews identify critical bugs, security issues, and code quality concerns with suggested fixes.
- **Auto-labeling:** Applies relevant labels to issues and PRs via [OpenAI](https://openai.com/) GPT-5 for intelligent categorization.
1. **[Ultralytics Actions](#ultralytics-actions-main-action)** - Main GitHub Action for AI-powered code formatting, PR summaries, and auto-labeling
2. **[Standalone Actions](#standalone-actions)** - Reusable composite actions for common CI/CD tasks
3. **[Python Package](#python-package)** - `ultralytics-actions` package for programmatic use
## 🛠️ How It Works
## Ultralytics Actions (Main Action)
Ultralytics Actions triggers on various GitHub events to streamline workflows:
AI-powered formatting, labeling, and PR summaries for Python, Swift, and Markdown files.
- **Push Events:** Automatically formats code when changes are pushed to the `main` branch.
- **Pull Requests:**
- Ensures contributions meet formatting standards before merging.
- Generates a concise summary of changes using GPT-5.
- Provides AI-powered inline code reviews with suggested fixes for critical issues.
- Applies relevant labels using GPT-5 for intelligent categorization.
- **Issues:** Automatically applies relevant labels using GPT-5 when new issues are created.
### 📄 Features
These automated actions help maintain high code quality, improve documentation clarity, and streamline the review process by providing consistent formatting, informative summaries, and appropriate categorization.
- **Python Code:** Formatted using [Ruff](https://github.com/astral-sh/ruff), an extremely fast Python linter and formatter
- **Markdown Files:** Styled with [Prettier](https://github.com/prettier/prettier) to ensure consistent documentation appearance
- **Docstrings:** Cleaned and standardized using [docformatter](https://github.com/PyCQA/docformatter)
- **Swift Code:** Formatted with [`swift-format`](https://github.com/swiftlang/swift-format) _(requires `macos-latest` runner)_
- **Spell Check:** Common misspellings caught using [codespell](https://github.com/codespell-project/codespell)
- **Broken Links Check:** Broken links identified using [Lychee](https://github.com/lycheeverse/lychee)
- **PR Summary:** Concise Pull Request summaries generated using [OpenAI](https://openai.com/) GPT-5
- **PR Review:** AI-powered code reviews identify critical bugs, security issues, and quality concerns with suggested fixes
- **Auto-labeling:** Applies relevant labels to issues and PRs via [OpenAI](https://openai.com/) GPT-5
## 🔧 Setting Up the Action
### 🛠️ How It Works
To integrate this action into your Ultralytics repository:
Triggers on GitHub events to streamline workflows:
1. **Create a Workflow File:** In your repository, create a YAML file under `.github/workflows/`, for example, `ultralytics-actions.yml`.
- **Push Events:** Automatically formats code when changes are pushed to `main`
- **Pull Requests:** Ensures formatting standards, generates summaries, provides AI reviews, and applies labels
- **Issues:** Automatically applies relevant labels using GPT-5
2. **Add the Action:** Configure the Ultralytics Actions in your workflow file as shown below:
### 🔧 Setup
```yaml
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
Create `.github/workflows/ultralytics-actions.yml`:
# Ultralytics Actions https://github.com/ultralytics/actions
# This workflow formats code and documentation in PRs to Ultralytics standards
```yaml
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
name: Ultralytics Actions
# Ultralytics Actions https://github.com/ultralytics/actions
# This workflow formats code and documentation in PRs to Ultralytics standards
on:
issues:
types: [opened]
pull_request:
branches: [main]
types: [opened, closed, synchronize, review_requested]
name: Ultralytics Actions
permissions:
contents: write # Modify code in PRs
pull-requests: write # Add comments and labels to PRs
issues: write # Add comments and labels to issues
on:
issues:
types: [opened]
pull_request:
branches: [main]
types: [opened, closed, synchronize, review_requested]
jobs:
actions:
runs-on: ubuntu-latest
steps:
- name: Run Ultralytics Actions
uses: ultralytics/actions@main
with:
token: ${{ secrets.GITHUB_TOKEN }} # Auto-generated token
labels: true # Auto-label issues/PRs using AI
python: true # Format Python with Ruff and docformatter
prettier: true # Format YAML, JSON, Markdown, CSS
swift: false # Format Swift (requires macos-latest)
dart: false # Format Dart/Flutter
spelling: true # Check spelling with codespell
links: true # Check broken links with Lychee
summary: true # Generate AI-powered PR summaries
openai_api_key: ${{ secrets.OPENAI_API_KEY }} # Powers PR summaries, labels and comments
brave_api_key: ${{ secrets.BRAVE_API_KEY }} # Used for broken link resolution
```
permissions:
contents: write # Modify code in PRs
pull-requests: write # Add comments and labels to PRs
issues: write # Add comments and labels to issues
3. **Customize:** Adjust the `runs-on` runner and the boolean flags (`labels`, `python`, `prettier`, `swift`, `spelling`, `links`, `summary`) based on your project's needs. Remember to add your `OPENAI_API_KEY` as a secret in your repository settings if you enable `labels` or `summary`.
jobs:
actions:
runs-on: ubuntu-latest
steps:
- name: Run Ultralytics Actions
uses: ultralytics/actions@main
with:
token: ${{ secrets.GITHUB_TOKEN }} # Auto-generated token
labels: true # Auto-label issues/PRs using AI
python: true # Format Python with Ruff and docformatter
prettier: true # Format YAML, JSON, Markdown, CSS
swift: false # Format Swift (requires macos-latest)
dart: false # Format Dart/Flutter
spelling: true # Check spelling with codespell
links: true # Check broken links with Lychee
summary: true # Generate AI-powered PR summaries
openai_api_key: ${{ secrets.OPENAI_API_KEY }} # Powers PR summaries, labels and reviews
brave_api_key: ${{ secrets.BRAVE_API_KEY }} # Used for broken link resolution
```
## Standalone Actions
Reusable composite actions for common CI/CD tasks. Each can be used independently in your workflows.
### 1. Retry Action
Retry failed commands with exponential backoff.
```yaml
- uses: ultralytics/actions/retry@main
with:
command: npm install
max_attempts: 3
timeout_minutes: 5
```
[**📖 Full Documentation →**](retry/README.md)
### 2. Cleanup Disk Action
Free up disk space on GitHub runners by removing unnecessary packages and files.
```yaml
- uses: ultralytics/actions/cleanup-disk@main
```
[**📖 Full Documentation →**](cleanup-disk/README.md)
### 3. Scan PRs Action
List open PRs across an organization and auto-merge eligible Dependabot PRs.
```yaml
- uses: ultralytics/actions/scan-prs@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
org: ultralytics # Optional: defaults to ultralytics
visibility: public # Optional: public, private, internal, or all
```
[**📖 Full Documentation →**](scan-prs/README.md)
## Python Package
Install the `ultralytics-actions` Python package directly with Pip:
Install `ultralytics-actions` for programmatic access to action utilities.
[![PyPI - Version](https://img.shields.io/pypi/v/ultralytics-actions?logo=pypi&logoColor=white)](https://pypi.org/project/ultralytics-actions/)
[![Ultralytics Downloads](https://static.pepy.tech/badge/ultralytics-actions)](https://clickpy.clickhouse.com/dashboard/ultralytics-actions)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ultralytics-actions?logo=python&logoColor=gold)](https://pypi.org/project/ultralytics-actions/)
```sh
```bash
pip install ultralytics-actions
```
**Available Modules:**
- `actions.review_pr` - AI-powered PR review
- `actions.summarize_pr` - Generate PR summaries
- `actions.scan_prs` - Scan and manage organization PRs
- `actions.first_interaction` - Welcome message for new contributors
- And more in `actions/` directory
## 💡 Contribute
Ultralytics thrives on community collaboration, and we deeply value your contributions! Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing/) for details on how you can get involved. We also encourage you to share your feedback through our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey). A huge thank you 🙏 to all our contributors!
+4 -1
View File
@@ -12,10 +12,13 @@
# │ │ ├── github_utils.py
# │ │ ├── openai_utils.py
# │ │ └── common_utils.py
# │ ├── dispatch_actions.py
# │ ├── first_interaction.py
# │ ├── review_pr.py
# │ ├── scan_prs.py
# │ ├── summarize_pr.py
# │ ├── summarize_release.py
# │ ├── update_file_headers.py
# │ └── update_markdown_code_blocks.py
# └── tests/
# ├── __init__.py
@@ -23,4 +26,4 @@
# ├── test_summarize_pr.py
# └── ...
__version__ = "0.2.0"
__version__ = "0.2.1"
+205
View File
@@ -0,0 +1,205 @@
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
"""List and auto-merge open PRs across GitHub organization."""
import json
import os
import subprocess
from datetime import datetime, timezone
def get_age_days(created_at):
"""Calculate PR age in days from ISO timestamp."""
return (datetime.now(timezone.utc) - datetime.fromisoformat(created_at.replace("Z", "+00:00"))).days
def get_phase_emoji(age_days):
"""Return emoji and label for PR age phase."""
if age_days == 0:
return "🆕", "NEW"
elif age_days <= 7:
return "🟢", f"{age_days} days"
elif age_days <= 30:
return "🟡", f"{age_days} days"
else:
return "🔴", f"{age_days} days"
def run():
"""List open PRs across organization and auto-merge eligible Dependabot PRs."""
# Get and validate settings
org = os.getenv("ORG", "ultralytics")
visibility = os.getenv("VISIBILITY", "public").lower()
repo_visibility = os.getenv("REPO_VISIBILITY", "public").lower()
valid_visibilities = {"public", "private", "internal", "all"}
if visibility not in valid_visibilities:
print(f"⚠️ Invalid visibility '{visibility}', defaulting to 'public'")
visibility = "public"
# Security: if calling repo is public, restrict to public repos only
if repo_visibility == "public" and visibility != "public":
print(f"⚠️ Security: Public repo cannot scan {visibility} repos. Restricting to public only.")
visibility = "public"
print(f"🔍 Scanning {visibility} repositories in {org} organization...")
# Get active repos with specified visibility
cmd = ["gh", "repo", "list", org, "--limit", "1000", "--json", "name,url,isArchived"]
if visibility != "all":
cmd.extend(["--visibility", visibility])
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
repos = {r["name"]: r["url"] for r in json.loads(result.stdout) if not r["isArchived"]}
if not repos:
print("⚠️ No repositories found")
return
# Get all open PRs
result = subprocess.run(
[
"gh",
"search",
"prs",
"--owner",
org,
"--state",
"open",
"--limit",
"1000",
"--json",
"repository,number,title,url,createdAt",
"--sort",
"created",
"--order",
"desc",
],
capture_output=True,
text=True,
check=True,
)
all_prs = json.loads(result.stdout)
if not all_prs:
print("✅ No open PRs found")
return
# Count PRs by phase
phase_counts = {"new": 0, "green": 0, "yellow": 0, "red": 0}
for pr in all_prs:
age_days = get_age_days(pr["createdAt"])
phase_counts[
"new" if age_days == 0 else "green" if age_days <= 7 else "yellow" if age_days <= 30 else "red"
] += 1
repo_count = len({pr["repository"]["name"] for pr in all_prs if pr["repository"]["name"] in repos})
summary = [
f"# 🔍 Open Pull Requests - {org.title()} Organization\n",
f"**Total:** {len(all_prs)} open PRs across {repo_count} repos",
f"**By Phase:** 🆕 {phase_counts['new']} New | 🟢 {phase_counts['green']} Green (≤7d) | 🟡 {phase_counts['yellow']} Yellow (≤30d) | 🔴 {phase_counts['red']} Red (>30d)\n",
]
for repo_name in sorted({pr["repository"]["name"] for pr in all_prs}):
if repo_name not in repos:
continue
repo_prs = [pr for pr in all_prs if pr["repository"]["name"] == repo_name]
summary.append(
f"## 📦 [{repo_name}]({repos[repo_name]}) - {len(repo_prs)} open PR{'s' if len(repo_prs) > 1 else ''}"
)
for pr in repo_prs[:30]:
emoji, age_str = get_phase_emoji(get_age_days(pr["createdAt"]))
summary.append(f"- 🔀 [#{pr['number']}]({pr['url']}) {pr['title']} {emoji} {age_str}")
if len(repo_prs) > 30:
summary.append(f"- ... {len(repo_prs) - 30} more PRs")
summary.append("")
# Auto-merge Dependabot GitHub Actions PRs
print("\n🤖 Checking for Dependabot PRs to auto-merge...")
summary.append("\n# 🤖 Auto-Merge Dependabot GitHub Actions PRs\n")
total_found = total_merged = total_skipped = 0
for repo_name in repos:
result = subprocess.run(
[
"gh",
"pr",
"list",
"--repo",
f"{org}/{repo_name}",
"--author",
"app/dependabot",
"--state",
"open",
"--json",
"number,title,files,mergeable,statusCheckRollup",
],
capture_output=True,
text=True,
)
if result.returncode != 0:
continue
merged = 0
for pr in json.loads(result.stdout):
if not all(f["path"].startswith(".github/workflows/") for f in pr["files"]):
continue
total_found += 1
pr_ref = f"{org}/{repo_name}#{pr['number']}"
print(f" Found: {pr_ref} - {pr['title']}")
if merged >= 1:
print(f" ⏭️ Skipped (already merged 1 PR in {repo_name})")
total_skipped += 1
continue
if pr["mergeable"] != "MERGEABLE":
print(f" ❌ Skipped (not mergeable: {pr['mergeable']})")
total_skipped += 1
continue
# Check if all status checks passed (normalize rollup structure)
rollup = pr.get("statusCheckRollup")
if isinstance(rollup, list):
checks = rollup
elif isinstance(rollup, dict):
checks = rollup.get("contexts", [])
else:
checks = []
failed_checks = [c for c in checks if c.get("conclusion") not in ["SUCCESS", "SKIPPED", "NEUTRAL"]]
if failed_checks:
for check in failed_checks:
print(f" ❌ Failing check: {check.get('name', 'unknown')} = {check.get('conclusion')}")
total_skipped += 1
continue
print(" ✅ All checks passed, merging...")
result = subprocess.run(
["gh", "pr", "merge", str(pr["number"]), "--repo", f"{org}/{repo_name}", "--squash", "--admin"],
capture_output=True,
text=True,
)
if result.returncode == 0:
print(f" ✅ Successfully merged {pr_ref}")
summary.append(f"- ✅ Merged {pr_ref}")
total_merged += 1
merged += 1
else:
print(f" ❌ Merge failed: {result.stderr.strip()}")
total_skipped += 1
summary.append(f"\n**Summary:** Found {total_found} | Merged {total_merged} | Skipped {total_skipped}")
print(f"\n📊 Dependabot Summary: Found {total_found} | Merged {total_merged} | Skipped {total_skipped}")
# Write to GitHub step summary if available
if summary_file := os.getenv("GITHUB_STEP_SUMMARY"):
with open(summary_file, "a") as f:
f.write("\n".join(summary))
if __name__ == "__main__":
run()
+61
View File
@@ -0,0 +1,61 @@
# Scan PRs Action
List open PRs across an organization and auto-merge eligible Dependabot PRs.
## Features
- **PR Overview**: Lists all open PRs with age-based categorization (New, Green ≤7d, Yellow ≤30d, Red >30d)
- **Auto-merge**: Automatically merges Dependabot PRs that update GitHub Actions workflows when all checks pass
- **Console Logging**: Detailed output showing which PRs were found, merged, or skipped with reasons
## Usage
```yaml
name: Scan PRs
on:
workflow_dispatch:
schedule:
- cron: "0 3 * * *" # daily at 03:00 UTC
permissions:
contents: write
pull-requests: write
jobs:
scan-prs:
runs-on: ubuntu-latest
steps:
- uses: ultralytics/actions/scan-prs@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
org: ultralytics # Optional: defaults to ultralytics
visibility: public # Optional: public, private, internal, or all
```
## Inputs
| Input | Description | Required | Default |
| ------------ | ------------------------------------------------------------------------ | -------- | ------------- |
| `token` | GitHub token with admin permissions for merging | Yes | - |
| `org` | GitHub organization name | No | `ultralytics` |
| `visibility` | Repository visibility to scan: `public`, `private`, `internal`, or `all` | No | `public` |
**Security Note:** If the calling repository is public, scanning is automatically restricted to public repos only, even if a different visibility is specified.
## Auto-merge Criteria
Dependabot PRs are automatically merged if they meet ALL criteria:
1. Update files in `.github/workflows/` only
2. PR is mergeable (no conflicts)
3. All status checks passed (SUCCESS, SKIPPED, or NEUTRAL)
4. Maximum 1 PR merged per repository per run
## Output
The action generates a GitHub step summary with:
- Total PR count across all repos
- PR breakdown by phase (New, Green, Yellow, Red)
- Detailed list of PRs per repository
- Summary of Dependabot PRs found, merged, and skipped
+56
View File
@@ -0,0 +1,56 @@
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
name: "Scan PRs"
author: "Ultralytics"
description: "List open PRs and auto-merge eligible Dependabot PRs across organization"
branding:
icon: "git-pull-request"
color: "blue"
inputs:
token:
description: "GitHub token with admin permissions for merging"
required: true
org:
description: "GitHub organization name"
required: false
default: "ultralytics"
visibility:
description: "Repository visibility to scan: public, private, internal, or all"
required: false
default: "public"
runs:
using: "composite"
steps:
- uses: astral-sh/setup-uv@v7
with:
ignore-empty-workdir: true
enable-cache: false
version: "0.9.4"
- name: Install ultralytics-actions
env:
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_SHA: ${{ github.sha }}
run: |
# Install from git commit if testing in ultralytics/actions repo, otherwise from main
if [ "$GITHUB_REPOSITORY" = "ultralytics/actions" ]; then
echo "Installing from commit: $GITHUB_SHA"
packages="git+https://github.com/ultralytics/actions@${GITHUB_SHA}"
else
packages="git+https://github.com/ultralytics/actions@main"
fi
if [ "$(uname)" = "Darwin" ]; then
uv pip install --system --break-system-packages $packages
else
sudo env "PATH=$PATH" uv pip install --system --break-system-packages $packages
fi
shell: bash
- name: Scan PRs
env:
GH_TOKEN: ${{ inputs.token }}
ORG: ${{ inputs.org }}
VISIBILITY: ${{ inputs.visibility }}
REPO_VISIBILITY: ${{ github.event.repository.visibility }}
run: python -m actions.scan_prs
shell: bash