Skip to content
Merged
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
83 changes: 68 additions & 15 deletions pr-description-check/action.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2025, NVIDIA CORPORATION.
# Copyright (c) 2025-2026, NVIDIA CORPORATION.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -13,41 +13,94 @@
# limitations under the License.

name: 'PR Description Check'
description: 'check if comment exists in PR description'
description: 'check if comment exists in PR description and checklist items are present'
inputs:
github-token:
description: "github token"
required: true
type: string
template-path:
description: "path to the PR template file in the caller repo"
required: false
default: '.github/PULL_REQUEST_TEMPLATE.md'

runs:
using: "composite"
steps:
- name: Get PR description
id: pr_description
- name: Check PR description and checklist
uses: actions/github-script@v7
env:
TEMPLATE_PATH: ${{ inputs.template-path }}
with:
github-token: ${{ inputs.github-token }}
script: |
const pr = context.payload.pull_request;
const prBody = pr.body || '';
const sha = pr.head.sha;
const hasComment = prBody.includes('<!--');
const state = hasComment ? 'failure' : 'success';
const description = hasComment
? 'PR description contains comment in "<!--". Please remove the comment manually.'
const owner = context.repo.owner;
const repo = context.repo.repo;

const errors = [];
const failedChecks = [];

const hasComments = prBody.includes('<!--');
if (hasComments) {
errors.push('PR description contains comment in "<!--". Please remove the comment manually.');
failedChecks.push('has HTML comments');
}

const templatePath = process.env.TEMPLATE_PATH;
let templateContent = null;
try {
const response = await github.rest.repos.getContent({
owner, repo, path: templatePath,
});
// == null catches both null (file >1MB) and undefined (directory listing)
if (response.data.type !== 'file' || response.data.content == null) {
core.info(`"${templatePath}" is not a readable file, skipping checklist check.`);
} else {
templateContent = Buffer.from(response.data.content, 'base64').toString('utf-8');
}
} catch (error) {
if (error.status === 404) {
core.info(`PR template not found at "${templatePath}", skipping checklist check.`);
} else {
throw error;
}
}
Comment thread
pxLi marked this conversation as resolved.

if (templateContent !== null) {
const templateItems = [...templateContent.matchAll(/^- \[ \] (.+)$/gm)]
.map(m => m[1].trim());

if (templateItems.length > 0) {
const bodyItems = new Set(
[...prBody.matchAll(/^- \[[xX ]\] (.+)$/gm)].map(m => m[1].trim())
);
const missingItems = templateItems.filter(item => !bodyItems.has(item));
if (missingItems.length > 0) {
errors.push(
`Missing ${missingItems.length} checklist item(s) from PR template:\n` +
missingItems.map(i => ` - ${i}`).join('\n')
);
failedChecks.push('missing checklist items');
}
}
}

const failed = errors.length > 0;
const statusDesc = failed
? `Failed: ${failedChecks.join(', ')}.`
: 'PR description is valid.';

if (hasComment) {
core.setFailed(description);
if (failed) {
core.setFailed(errors.join('\n\n'));
}

await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: sha,
state: state,
owner, repo, sha,
state: failed ? 'failure' : 'success',
target_url: pr.html_url,
description: description.substring(0, 140),
description: statusDesc.substring(0, 140),
context: 'PR Description'
});
Loading