mirror of
https://github.com/slsdetectorgroup/slsDetectorPackage.git
synced 2026-09-21 12:32:10 +02:00
215 lines
7.9 KiB
YAML
215 lines
7.9 KiB
YAML
# This reusable workflow validates the PR release notes. It ignores comments and
|
|
# <details> blocks, checks for notes under the "## Release notes" section. If
|
|
# it is not an Infrastructure PR and release notes are not found, it should find
|
|
# them in an equivalent PR or referenced PR, else validation fails. If it is an
|
|
# Infrastructure PR or release notes are not found in an equivalent or
|
|
# referenced PR, it should add label 'No Release Note'.
|
|
|
|
#test branch
|
|
|
|
name: PR Release Notes
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
pr_number:
|
|
required: true
|
|
type: number
|
|
|
|
permissions:
|
|
pull-requests: write
|
|
contents: read
|
|
issues: write
|
|
|
|
jobs:
|
|
check-release-notes:
|
|
name: Check PR release notes
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Validate presence of release notes
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const prNumber = ${{ inputs.pr_number }};
|
|
|
|
if (!prNumber) {
|
|
core.setFailed('Unable to determine PR number.');
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Strips ignored content such as comments and <details> blocks
|
|
* from the given text.
|
|
*/
|
|
const stripIgnoredContent = (text = '') => {
|
|
return (text || '')
|
|
.replace(/<!--.*?-->/gs, '\n')
|
|
.replace(/<details\b[\s\S]*?<\/details>/gi, '\n')
|
|
.trim();
|
|
};
|
|
|
|
/**
|
|
* Finds the "Release notes" section text without ignored content.
|
|
*/
|
|
const findReleaseNotesSection = (body = '') => {
|
|
const sanitized = stripIgnoredContent(body || '');
|
|
if (!sanitized) return '';
|
|
|
|
const match = sanitized.match(
|
|
/(^|\n)#{1,6}\s*Release notes\s*\n([\s\S]*?)(?=\n#{1,6}\s+\S|\s*$)/i
|
|
);
|
|
if (!match) return '';
|
|
|
|
let section = (match[2] || '')
|
|
.split('\n')
|
|
.filter(line => !/^#{1,6}\s+/.test(line.trim()))
|
|
.join('\n')
|
|
.trim();
|
|
|
|
return section;
|
|
};
|
|
|
|
/**
|
|
* Parses a list of PR numbers from the given text.
|
|
*/
|
|
const parseNumberList = (text) => {
|
|
if (!text) {
|
|
return [];
|
|
}
|
|
return Array.from(
|
|
new Set(
|
|
Array.from(text.matchAll(/#(\d+)/g), match => Number(match[1]))
|
|
)
|
|
);
|
|
};
|
|
|
|
const findReferencedPrNumbers = (body = '') => {
|
|
if (!body) {
|
|
return [];
|
|
}
|
|
const relevantSections = [
|
|
'Equivalent PRs',
|
|
'Referenced PRs'
|
|
];
|
|
const patterns = relevantSections.map(section =>
|
|
new RegExp(
|
|
`(^|\\n)#{1,6}\\s*${section.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')}\\s*\\n([\\s\\S]*?)(?=\\n#{1,6}\\s+\\S|\\s*$)`,
|
|
'i'
|
|
)
|
|
);
|
|
const numbers = [];
|
|
for (const pattern of patterns) {
|
|
const match = body.match(pattern);
|
|
if (match && match[2]) {
|
|
numbers.push(...parseNumberList(match[2]));
|
|
}
|
|
}
|
|
return Array.from(new Set(numbers));
|
|
};
|
|
|
|
const { data: pr } = await github.rest.pulls.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber
|
|
});
|
|
|
|
const baseBranch = pr.base.ref;
|
|
|
|
if (baseBranch === 'main') {
|
|
core.info('PR targets main branch. Release notes validation is not required.');
|
|
return;
|
|
}
|
|
|
|
const { data: labels } =
|
|
await github.rest.issues.listLabelsOnIssue({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: prNumber
|
|
});
|
|
const labelNames = labels.map(label => label.name);
|
|
|
|
const hasInfrastructure = labelNames.includes('Infrastructure');
|
|
core.info('Infrastructure label present: ' + hasInfrastructure);
|
|
|
|
/** check that it has only developing detector labels (Ctb, Xilinx Ctb, Matterhorn) and does not have Eiger, Mythen3, Gotthard2, Jungfrau, Moench labels */
|
|
const developingDetectorLabels = ['Ctb', 'Xilinx Ctb', 'Matterhorn'];
|
|
const otherDetectorLabels = ['Eiger', 'Mythen3', 'Gotthard2', 'Jungfrau', 'Moench'];
|
|
const hasDevelopingDetectorLabel = labelNames.some(
|
|
label => developingDetectorLabels.includes(label)
|
|
);
|
|
const hasOtherDetectorLabel = labelNames.some(
|
|
label => otherDetectorLabels.includes(label)
|
|
);
|
|
const hasOnlyDevelopingDetectors =
|
|
hasDevelopingDetectorLabel && !hasOtherDetectorLabel;
|
|
core.info('Only developing detector labels present: ' + hasOnlyDevelopingDetectors);
|
|
|
|
const releaseNotes = findReleaseNotesSection(pr.body || '');
|
|
core.info('Release notes found in current PR: ' + (releaseNotes ? 'Yes' : 'No'));
|
|
|
|
if (hasInfrastructure || hasOnlyDevelopingDetectors) {
|
|
if (releaseNotes) {
|
|
core.setFailed(
|
|
'Infrastructure PRs or only developing Detector PRs cannot include release notes. Remove the "## Release notes" section or change the PR type.'
|
|
);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add AI assisted release notes and return
|
|
*/
|
|
if (releaseNotes) {
|
|
// Remove any existing details section with heading "AI Assisted Release Notes"
|
|
const updatedBody = pr.body.replace(
|
|
/(^|\n)#{1,6}\s*AI Assisted Release Notes\s*\n([\s\S]*?)(?=\n#{1,6}\s+\S|\s*$)/gi,
|
|
''
|
|
).trim();
|
|
// Add a new section with heading "AI Assisted Release Notes" and placeholder text
|
|
const newBody = `${updatedBody}\n\n## AI Assisted Release Notes\n\n**This section will be populated with AI-assisted release notes.**\n\n`;
|
|
|
|
await github.rest.pulls.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber,
|
|
body: newBody
|
|
});
|
|
core.info('AI Assisted Release Notes section added to PR body.');
|
|
return;
|
|
}
|
|
|
|
/* Find release notes in equivalent or referenced PRs */
|
|
const equivalentAndReferencedPrNumbers = findReferencedPrNumbers(pr.body || '');
|
|
for (const referencedPrNumber of equivalentAndReferencedPrNumbers) {
|
|
if (referencedPrNumber === prNumber) {
|
|
continue;
|
|
}
|
|
try {
|
|
const { data: referencedPr } =
|
|
await github.rest.pulls.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: referencedPrNumber
|
|
});
|
|
const referencedNotes = findReleaseNotesSection(referencedPr.body || '');
|
|
if (referencedNotes) {
|
|
core.info(
|
|
`Release notes found: In equivalent/referenced PR #${referencedPrNumber}. ` +
|
|
`Using that as the source for this PR.`
|
|
);
|
|
return;
|
|
}
|
|
} catch (error) {
|
|
core.warning(
|
|
`Unable to inspect referenced PR #${referencedPrNumber}: ${error.message}`
|
|
);
|
|
}
|
|
}
|
|
|
|
core.setFailed(
|
|
'No release notes were found in the PR body, equivalent PRs, or referenced PRs.'
|
|
);
|