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
32 changes: 32 additions & 0 deletions .github/workflows/danger.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: PR Template Validation

# pull_request_target is used (instead of pull_request) so that GITHUB_TOKEN
# has write permissions even for PRs from forks, which is required for Danger
# to post review comments. The Dangerfile is always read from the base branch
# (checked out below), so no untrusted code from the fork is executed.
on:
pull_request_target:
types: [opened, edited, reopened, synchronize]

permissions:
contents: read
pull-requests: write
issues: write

jobs:
danger:
if: ${{ github.repository_owner == 'AOSSIE-Org' }}
runs-on: ubuntu-latest
steps:
- name: Checkout base branch
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Run Danger JS
run: npx --yes danger@13.0.7 ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
91 changes: 91 additions & 0 deletions dangerfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// dangerfile.js — enforces the PR description template
// Docs: https://danger.systems/js/
const body = danger.github.pr.body || "";
const normalizedBody = body.replace(/\r\n/g, "\n");

const issues = [];

function escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function hasCheckedChecklistItem(itemText) {
const escapedItem = escapeRegex(itemText).replace(/\s+/g, "\\s+");
const checkedItemPattern = new RegExp(`-\\s*\\[[xX]\\]\\s*${escapedItem}`, "i");
return checkedItemPattern.test(normalizedBody);
}

// ---------------------------------------------------------------------------
// 1. Required section headings (tolerant to spacing and casing)
// ---------------------------------------------------------------------------
const requiredSections = [
{
label: "### Addressed Issues:",
pattern: /#{3}\s+addressed\s+issues:/i,
},
{
label: "## Checklist",
pattern: /#{2}\s+checklist/i,
},
];

const missingSections = requiredSections
.filter((section) => !section.pattern.test(normalizedBody))
.map((section) => section.label);

if (!normalizedBody.trim()) {
fail("PR description is empty. Please follow the PR template.");
}

if (missingSections.length > 0) {
issues.push(
`**PR description is missing required sections:**\n` +
missingSections.map((s) => `- \`${s}\``).join("\n") +
`\n\nPlease follow the [PR template](.github/PULL_REQUEST_TEMPLATE.md).`
);
}
Comment thread
kpj2006 marked this conversation as resolved.

// ---------------------------------------------------------------------------
// 2. Issue link — warn on placeholder and missing issue reference
// ---------------------------------------------------------------------------
if (/\bfixes\s*#\s*\(\s*issue\s*number\s*\)/i.test(normalizedBody)) {
issues.push(
"Please replace the placeholder `Fixes #(issue number)` with the actual " +
"issue number (e.g. `Fixes #42`)."
);
} else if (!/\b(fixes|closes|resolves)\s*#\d+\b/i.test(normalizedBody)) {
issues.push(
"No issue linked. Consider adding `Fixes #<number>` (e.g. `Fixes #42`) " +
"under the **Addressed Issues** section."
);
}

// ---------------------------------------------------------------------------
// 3. Checklist — required items must be checked
// ---------------------------------------------------------------------------
const requiredChecklistItems = [
"My PR addresses a single issue",
"My code follows the project's code style",
"My changes generate no new warnings or errors",
];
Comment thread
kpj2006 marked this conversation as resolved.

const missingRequired = requiredChecklistItems.filter(
(item) => !hasCheckedChecklistItem(item)
);

if (missingRequired.length > 0) {
issues.push(
"Some required checklist items are not completed:\n" +
missingRequired.map((item) => `- ${item}`).join("\n")
);
}

if (issues.length > 0) {
message(`
### ⚠️ PR Template Check

These are non-blocking, but please fix:

${issues.map((issue) => `- ${issue}`).join("\n")}
`);
}
Loading