Skip to content

Un to dortmund

Un to dortmund #125

Workflow file for this run

name: PR Automation
on:
pull_request:
types: [opened, reopened, ready_for_review, synchronize, review_request_removed]
permissions:
pull-requests: write
issues: write
contents: read
jobs:
auto-label-and-assign:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get changed files
id: files
uses: actions/github-script@v7
with:
script: |
const response = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
const files = response.data || [];
const labels = new Set();
files.forEach(fileObj => {
const file = fileObj.filename || '';
if (file.match(/\.(ts|tsx|jsx|js)$/)) labels.add('typescript');
if (file.match(/\.(css|scss|postcss)$/)) labels.add('styling');
if (file.match(/test|spec/)) labels.add('tests');
if (file.match(/package\.json|tsconfig|eslint|tailwind/)) labels.add('configuration');
if (file.match(/README|\.md$/)) labels.add('documentation');
if (file.match(/next\.config|public/)) labels.add('build');
});
core.setOutput('labels', Array.from(labels).join(','));
- name: Enforce reviewers config edit policy
uses: actions/github-script@v7
env:
MAINTAINER_ALLOWLIST: ${{ vars.MAINTAINER_ALLOWLIST }}
with:
script: |
const allowlist = (process.env.MAINTAINER_ALLOWLIST || '')
.split(',')
.map(v => v.trim().toLowerCase())
.filter(Boolean);
const actor = context.actor.toLowerCase();
const owner = context.repo.owner.toLowerCase();
const isMaintainer = allowlist.includes(actor) || actor === owner;
if (isMaintainer) return;
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
per_page: 100
});
const changed = files.map(f => f.filename);
if (changed.includes('.github/reviewers.json')) {
core.setFailed('Only maintainers may modify .github/reviewers.json');
}
- name: Auto-label PR
if: steps.files.outputs.labels != ''
uses: actions/github-script@v7
with:
script: |
const labels = '${{ steps.files.outputs.labels }}'.split(',').filter(l => l);
if (labels.length > 0) {
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: labels
});
}
- name: Auto-assign PR to author
uses: actions/github-script@v7
with:
script: |
github.rest.issues.addAssignees({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
assignees: [context.payload.pull_request.user.login]
}).catch(err => {
console.log('Could not assign author: ' + err.message);
})
- name: Auto-request reviewers from JSON config
uses: actions/github-script@v7
env:
MAINTAINER_ALLOWLIST: ${{ vars.MAINTAINER_ALLOWLIST }}
with:
script: |
const fs = require('fs');
function toRegex(glob) {
const escaped = glob
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
.replace(/\*\*/g, '::DOUBLE_STAR::')
.replace(/\*/g, '[^/]*')
.replace(/::DOUBLE_STAR::/g, '.*');
return new RegExp(`^${escaped}$`);
}
const configPath = '.github/reviewers.json';
if (!fs.existsSync(configPath)) {
core.info('No .github/reviewers.json found. Skipping reviewer requests.');
return;
}
let config;
try {
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (e) {
core.setFailed(`Invalid JSON in ${configPath}: ${e.message}`);
return;
}
const defaultReviewers = Array.isArray(config.default_reviewers) ? config.default_reviewers : [];
const pathRules = Array.isArray(config.path_reviewers) ? config.path_reviewers : [];
const allowlist = (process.env.MAINTAINER_ALLOWLIST || '')
.split(',')
.map(v => v.trim().toLowerCase())
.filter(Boolean);
const actor = context.actor.toLowerCase();
const ownerLower = context.repo.owner.toLowerCase();
const actorIsMaintainer = allowlist.includes(actor) || actor === ownerLower;
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
per_page: 100
});
const changed = files.map(f => f.filename);
const collected = new Set(defaultReviewers.filter(Boolean));
for (const rule of pathRules) {
if (!rule || !rule.glob || !Array.isArray(rule.reviewers)) continue;
const re = toRegex(String(rule.glob));
if (changed.some(f => re.test(f))) {
for (const reviewer of rule.reviewers) {
if (reviewer) collected.add(reviewer);
}
}
}
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number
});
const author = pr.data.user?.login;
const alreadyRequested = new Set((pr.data.requested_reviewers || []).map(r => r.login));
const reviewers = Array.from(collected)
.map(r => String(r).trim())
.filter(Boolean)
.filter(r => r !== author)
.filter(r => !alreadyRequested.has(r))
.slice(0, 15);
const action = context.payload.action;
const shouldEnforce = ['opened', 'reopened', 'ready_for_review', 'review_request_removed'].includes(action);
if (!shouldEnforce) {
core.info(`Skipping reviewer enforcement on action: ${action}`);
return;
}
if (action === 'review_request_removed' && actorIsMaintainer) {
core.info('Maintainer removed reviewer; skipping re-request.');
return;
}
if (reviewers.length === 0) {
core.info('No new reviewers to request.');
return;
}
try {
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
reviewers
});
core.info(`Requested reviewers: ${reviewers.join(', ')}`);
} catch (e) {
core.warning(`Could not request reviewers: ${e.message}`);
}