Skip to content

fix: restrict teacher course deletion to owned courses #52

fix: restrict teacher course deletion to owned courses

fix: restrict teacher course deletion to owned courses #52

Workflow file for this run

name: PR Guard
on:
pull_request_target:
types: [opened, reopened, synchronize, labeled, unlabeled, ready_for_review]
branches: [main]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
guard-small-pr:
runs-on: ubuntu-latest
steps:
- name: Validate PR size and rules
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const core = require("@actions/core");
const { context, github } = require("@actions/github");
const pr = context.payload.pull_request;
if (!pr) {
core.info("No pull request found in payload. Skipping.");
return;
}
const owner = context.repo.owner;
const repo = context.repo.repo;
const excludedLogins = new Set(["udaycodespace"]);
const trustedLogins = new Set(["udaycodespace"]);
const bypassLabels = new Set(["approved-small-pr"]);
const authorLogin = String(pr.user?.login || "").toLowerCase().trim();
const authorType = String(pr.user?.type || "");
const prNumber = pr.number;
const additions = Number(pr.additions || 0);
const deletions = Number(pr.deletions || 0);
const changedFiles = Number(pr.changed_files || 0);
const changedLines = additions + deletions;
const labels = (pr.labels || []).map(label => String(label.name || "").toLowerCase().trim());
const state = String(pr.state || "").toLowerCase();
const isDraft = Boolean(pr.draft);
const isMerged = Boolean(pr.merged);
const isBot =
authorType === "Bot" ||
authorLogin.endsWith("[bot]") ||
authorLogin === "github-actions";
if (!authorLogin) {
core.info("Missing author login. Skipping.");
return;
}
if (state !== "open") {
core.info(`PR #${prNumber} is not open. Skipping.`);
return;
}
if (isMerged) {
core.info(`PR #${prNumber} is already merged. Skipping.`);
return;
}
if (isBot) {
core.info(`PR #${prNumber} is from a bot (${authorLogin}). Skipping.`);
return;
}
if (excludedLogins.has(authorLogin) || trustedLogins.has(authorLogin)) {
core.info(`PR #${prNumber} is from trusted/excluded login ${authorLogin}. Skipping.`);
return;
}
if (isDraft) {
core.info(`PR #${prNumber} is a draft. Skipping.`);
return;
}
if (labels.some(label => bypassLabels.has(label))) {
core.info(`PR #${prNumber} has bypass label. Skipping.`);
return;
}
const minimumChangedLines = 20;
if (changedLines >= minimumChangedLines) {
core.info(
`PR #${prNumber} passed size rule: additions=${additions}, deletions=${deletions}, total=${changedLines}, files=${changedFiles}.`
);
return;
}
const marker = "<!-- learnhub-pr-guard -->";
const closeLabel = "auto-closed-small-pr";
const existingComments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: prNumber,
per_page: 100
});
const alreadyCommented = existingComments.some(comment =>
String(comment.body || "").includes(marker)
);
const intro =
`${marker}\n` +
`Hi @${pr.user.login}, thanks for raising this PR.\n\n`;
const explanation =
`The change looks valid, but this PR was closed because it does not currently follow our contribution rules.\n\n` +
`Please make sure:\n` +
`- the PR follows \`CONTRIBUTING.md\`\n` +
`- the contribution is meaningful and properly scoped\n` +
`- the PR has at least **20 changed lines** in total, unless it is pre-approved\n` +
`- if this is a valid smaller change, ask for the \`approved-small-pr\` label first\n\n`;
const stats =
`Current PR stats:\n` +
`- Additions: ${additions}\n` +
`- Deletions: ${deletions}\n` +
`- Total changed lines: ${changedLines}\n` +
`- Changed files: ${changedFiles}\n\n`;
const nextSteps =
`You can update the branch and open a new PR after following the guidelines.\n`;
const body = intro + explanation + stats + nextSteps;
if (!alreadyCommented) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body
});
core.info(`Posted policy comment on PR #${prNumber}.`);
} else {
core.info(`Policy comment already exists on PR #${prNumber}.`);
}
if (!labels.includes(closeLabel)) {
try {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: [closeLabel]
});
core.info(`Added label ${closeLabel} to PR #${prNumber}.`);
} catch (error) {
core.warning(`Could not add label: ${error.message}`);
}
}
await github.rest.pulls.update({
owner,
repo,
pull_number: prNumber,
state: "closed"
});
core.info(
`Closed PR #${prNumber} because changedLines=${changedLines} < ${minimumChangedLines}.`
);