mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 16:38:43 +00:00
220 lines
8.0 KiB
JavaScript
220 lines
8.0 KiB
JavaScript
const {
|
|
BOT_COMMENT_MARKER,
|
|
CODEOWNERS_MARKER,
|
|
TOO_BIG_MARKER,
|
|
DEPRECATED_COMPONENT_MARKER,
|
|
ORG_FORK_MARKER
|
|
} = require('./constants');
|
|
|
|
// Generate review messages
|
|
function generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo, prFiles, totalAdditions, totalDeletions, prAuthor, MAX_LABELS, TOO_BIG_THRESHOLD) {
|
|
const messages = [];
|
|
|
|
// Deprecated component message
|
|
if (finalLabels.includes('deprecated-component') && deprecatedInfo && deprecatedInfo.length > 0) {
|
|
let message = `${DEPRECATED_COMPONENT_MARKER}\n### ⚠️ Deprecated Component\n\n`;
|
|
message += `Hey there @${prAuthor},\n`;
|
|
message += `This PR modifies one or more deprecated components. Please be aware:\n\n`;
|
|
|
|
for (const info of deprecatedInfo) {
|
|
message += `#### Component: \`${info.component}\`\n`;
|
|
message += `${info.message}\n\n`;
|
|
}
|
|
|
|
message += `Consider migrating to the recommended alternative if applicable.`;
|
|
|
|
messages.push(message);
|
|
}
|
|
|
|
// Too big message
|
|
if (finalLabels.includes('too-big')) {
|
|
const testAdditions = prFiles
|
|
.filter(file => file.filename.startsWith('tests/'))
|
|
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
|
const testDeletions = prFiles
|
|
.filter(file => file.filename.startsWith('tests/'))
|
|
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
|
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
|
|
|
const tooManyLabels = originalLabelCount > MAX_LABELS;
|
|
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
|
|
|
let message = `${TOO_BIG_MARKER}\n### 📦 Pull Request Size\n\n`;
|
|
|
|
message +=
|
|
`Hey @${prAuthor}, thanks for the contribution! Just a heads up, ` +
|
|
`this PR is on the large side `;
|
|
|
|
if (tooManyLabels && tooManyChanges) {
|
|
message +=
|
|
`(${nonTestChanges} line changes excluding tests, across ` +
|
|
`${originalLabelCount} different components/areas)`;
|
|
} else if (tooManyLabels) {
|
|
message +=
|
|
`(it touches ${originalLabelCount} different components/areas)`;
|
|
} else {
|
|
message += `(${nonTestChanges} line changes excluding tests)`;
|
|
}
|
|
|
|
message += `, which makes it harder for maintainers to review.\n\n`;
|
|
message +=
|
|
`Smaller, focused PRs tend to be reviewed much faster since they ` +
|
|
`fit into the short gaps between other maintainer work; large ones ` +
|
|
`often have to wait for a rare long uninterrupted block of time. ` +
|
|
`If you can break this up into smaller pieces that can be reviewed ` +
|
|
`independently, it will almost certainly land faster overall.\n\n`;
|
|
message +=
|
|
`Before putting more time in, it's also worth popping into ` +
|
|
`\`#devs\` on [Discord](https://esphome.io/chat) so we can help ` +
|
|
`you scope things and flag anything already in flight.\n\n`;
|
|
message +=
|
|
`For more details (including how to split the work up), see: ` +
|
|
`https://developers.esphome.io/contributing/submitting-your-work/` +
|
|
`#how-to-approach-large-submissions`;
|
|
|
|
messages.push(message);
|
|
}
|
|
|
|
// CODEOWNERS message
|
|
if (finalLabels.includes('needs-codeowners')) {
|
|
const message = `${CODEOWNERS_MARKER}\n### 👥 Code Ownership\n\n` +
|
|
`Hey there @${prAuthor},\n` +
|
|
`Thanks for submitting this pull request! Can you add yourself as a codeowner for this integration? ` +
|
|
`This way we can notify you if a bug report for this integration is reported.\n\n` +
|
|
`In \`__init__.py\` of the integration, please add:\n\n` +
|
|
`\`\`\`python\nCODEOWNERS = ["@${prAuthor}"]\n\`\`\`\n\n` +
|
|
`And run \`script/build_codeowners.py\``;
|
|
|
|
messages.push(message);
|
|
}
|
|
|
|
return messages;
|
|
}
|
|
|
|
// Handle reviews
|
|
async function handleReviews(github, context, finalLabels, originalLabelCount, deprecatedInfo, prFiles, totalAdditions, totalDeletions, MAX_LABELS, TOO_BIG_THRESHOLD) {
|
|
const { owner, repo } = context.repo;
|
|
const pr_number = context.issue.number;
|
|
const prAuthor = context.payload.pull_request.user.login;
|
|
|
|
const reviewMessages = generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo, prFiles, totalAdditions, totalDeletions, prAuthor, MAX_LABELS, TOO_BIG_THRESHOLD);
|
|
const hasReviewableLabels = finalLabels.some(label =>
|
|
['too-big', 'needs-codeowners', 'deprecated-component'].includes(label)
|
|
);
|
|
|
|
const { data: reviews } = await github.rest.pulls.listReviews({
|
|
owner,
|
|
repo,
|
|
pull_number: pr_number
|
|
});
|
|
|
|
const botReviews = reviews.filter(review =>
|
|
review.user.type === 'Bot' &&
|
|
review.state === 'CHANGES_REQUESTED' &&
|
|
review.body && review.body.includes(BOT_COMMENT_MARKER)
|
|
);
|
|
|
|
if (hasReviewableLabels) {
|
|
const reviewBody = `${BOT_COMMENT_MARKER}\n\n${reviewMessages.join('\n\n---\n\n')}`;
|
|
|
|
if (botReviews.length > 0) {
|
|
// Update existing review
|
|
await github.rest.pulls.updateReview({
|
|
owner,
|
|
repo,
|
|
pull_number: pr_number,
|
|
review_id: botReviews[0].id,
|
|
body: reviewBody
|
|
});
|
|
console.log('Updated existing bot review');
|
|
} else {
|
|
// Create new review
|
|
await github.rest.pulls.createReview({
|
|
owner,
|
|
repo,
|
|
pull_number: pr_number,
|
|
body: reviewBody,
|
|
event: 'REQUEST_CHANGES'
|
|
});
|
|
console.log('Created new bot review');
|
|
}
|
|
} else if (botReviews.length > 0) {
|
|
// Dismiss existing reviews
|
|
for (const review of botReviews) {
|
|
try {
|
|
await github.rest.pulls.dismissReview({
|
|
owner,
|
|
repo,
|
|
pull_number: pr_number,
|
|
review_id: review.id,
|
|
message: 'Review dismissed: All requirements have been met'
|
|
});
|
|
console.log(`Dismissed bot review ${review.id}`);
|
|
} catch (error) {
|
|
console.log(`Failed to dismiss review ${review.id}:`, error.message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle maintainer access warning comment
|
|
async function handleMaintainerAccessComment(github, context, maintainerAccess) {
|
|
if (!maintainerAccess) {
|
|
return;
|
|
}
|
|
|
|
const { owner, repo } = context.repo;
|
|
const pr_number = context.issue.number;
|
|
const prAuthor = context.payload.pull_request.user.login;
|
|
|
|
// Check if we already posted the warning (iterate pages to exit early)
|
|
let existingComment;
|
|
for await (const { data: comments } of github.paginate.iterator(
|
|
github.rest.issues.listComments,
|
|
{ owner, repo, issue_number: pr_number }
|
|
)) {
|
|
existingComment = comments.find(comment =>
|
|
comment.user.type === 'Bot' &&
|
|
comment.body && comment.body.includes(ORG_FORK_MARKER)
|
|
);
|
|
if (existingComment) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (existingComment) {
|
|
console.log('Maintainer access warning comment already exists, skipping');
|
|
return;
|
|
}
|
|
|
|
let body;
|
|
if (maintainerAccess.isOrgFork) {
|
|
body = `${ORG_FORK_MARKER}\n### ⚠️ Organization Fork Detected\n\n` +
|
|
`Hey there @${prAuthor},\n` +
|
|
`It looks like this PR was submitted from a fork owned by the **${maintainerAccess.orgName}** organization. ` +
|
|
`GitHub does not allow maintainers to push changes to pull request branches when the fork is owned by an organization. ` +
|
|
`This means we won't be able to make small adjustments or fixups to your PR directly.\n\n` +
|
|
`To allow maintainer collaboration, please re-submit this PR from a personal fork instead.\n\n` +
|
|
`See: [Setting up the local repository](https://developers.esphome.io/contributing/development-environment/?h=org#set-up-the-local-repository) for more details.`;
|
|
} else {
|
|
body = `${ORG_FORK_MARKER}\n### ⚠️ Maintainer Access Disabled\n\n` +
|
|
`Hey there @${prAuthor},\n` +
|
|
`It looks like this PR does not have the "Allow edits from maintainers" option enabled. ` +
|
|
`This means we won't be able to make small adjustments or fixups to your PR directly.\n\n` +
|
|
`Please enable this option in the PR sidebar to allow maintainer collaboration.`;
|
|
}
|
|
|
|
await github.rest.issues.createComment({
|
|
owner,
|
|
repo,
|
|
issue_number: pr_number,
|
|
body
|
|
});
|
|
console.log('Created maintainer access warning comment');
|
|
}
|
|
|
|
module.exports = {
|
|
handleReviews,
|
|
handleMaintainerAccessComment
|
|
};
|