mirror of
https://github.com/slsdetectorgroup/slsDetectorPackage.git
synced 2026-11-13 22:03:53 +01:00
added pr automation workflows from developer (#1568)
This commit is contained in:
1 parent
094d0cbb1f
commit
dba175e986
7 files changed
+1453
No files matched your search
@@ -0,0 +1,260 @@
|
||||
# This reusable workflow detects detector labels for pull requests. This workflow is ignored for Infrastructure Prs. It checks whole word matching of detector names in server folders, changed code and PR title/description. If a detector is detected, the corresponding label is added to the PR. Existing labels are left untouched. Detector labels are never removed automatically.
|
||||
|
||||
name: PR Detector Labels
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pr_number:
|
||||
required: true
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: read
|
||||
|
||||
jobs:
|
||||
detector-labels:
|
||||
name: Detect detector labels
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Detect detector labels
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const prNumber = ${{ inputs.pr_number }};
|
||||
|
||||
/*
|
||||
* Get the current PR labels.
|
||||
*/
|
||||
const { data: currentLabels } =
|
||||
await github.rest.issues.listLabelsOnIssue({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const currentLabelNames =
|
||||
currentLabels.map(label => label.name);
|
||||
|
||||
if (currentLabelNames.includes('Infrastructure')) {
|
||||
core.info(
|
||||
'Infrastructure label found. Skipping detector label detection.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(
|
||||
`Infrastructure label not found. Proceeding with detector label detection.`
|
||||
);
|
||||
|
||||
/*
|
||||
* Known detector labels.
|
||||
*/
|
||||
const detectorLabels = [
|
||||
'Ctb',
|
||||
'Eiger',
|
||||
'Moench',
|
||||
'Mythen3',
|
||||
'Jungfrau',
|
||||
'Gotthard2',
|
||||
'Matterhorn',
|
||||
'Xilinx_ctb'
|
||||
];
|
||||
|
||||
|
||||
/*
|
||||
* Check whether a detector name appears as a
|
||||
* complete word, case-insensitively.
|
||||
*/
|
||||
function containsDetector(text, detector) {
|
||||
const regex = new RegExp(
|
||||
`\\b${detector}\\b`,
|
||||
'i'
|
||||
);
|
||||
|
||||
return regex.test(text);
|
||||
}
|
||||
|
||||
/*
|
||||
* Detectors detected by any detection method.
|
||||
*/
|
||||
const detectedDetectors = new Set();
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 1. Get all files changed by the PR
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
const files = await github.paginate(
|
||||
github.rest.pulls.listFiles,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100
|
||||
}
|
||||
);
|
||||
|
||||
core.info(
|
||||
`Found ${files.length} changed files.`
|
||||
);
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 2. Detect detector from changed files
|
||||
* --------------------------------------------------
|
||||
*
|
||||
* We inspect each file once and short-circuit as soon
|
||||
* as a detector-server folder confirms a label. That
|
||||
* avoids scanning the same detector folder again and
|
||||
* avoids checking the rest of the file for unrelated
|
||||
* detector names.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* slsDetectorServers/eigerDetectorServer/...
|
||||
* ↓
|
||||
* Eiger
|
||||
*
|
||||
* slsDetectorServers/xilinx_ctbDetectorServer/...
|
||||
* ↓
|
||||
* Xilinx_ctb
|
||||
*/
|
||||
const ignoredDetectorServerFolders = new Set();
|
||||
|
||||
for (const file of files) {
|
||||
const serverMatch = file.filename.match(
|
||||
/^slsDetectorServers\/([^/]+)DetectorServer\//
|
||||
);
|
||||
|
||||
if (serverMatch) {
|
||||
const detectorFolder = serverMatch[0];
|
||||
|
||||
// Skip all later files under a detected detector folder.
|
||||
if (ignoredDetectorServerFolders.has(detectorFolder)) {
|
||||
core.info(
|
||||
`Skipping file in ignored detector-server folder: ${file.filename}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const detectorName = serverMatch[1];
|
||||
|
||||
/*
|
||||
* Remove "DetectorServer" and capitalize
|
||||
* the first character.
|
||||
*/
|
||||
const detector =
|
||||
detectorName.charAt(0).toUpperCase() +
|
||||
detectorName.slice(1);
|
||||
|
||||
if (detectorLabels.includes(detector)) {
|
||||
detectedDetectors.add(detector);
|
||||
|
||||
core.info(
|
||||
`Detected ${detector} from detector-server path: ${file.filename}`
|
||||
);
|
||||
|
||||
// Once a detector is confirmed from the server folder,
|
||||
// do not inspect this file or the rest of that folder.
|
||||
ignoredDetectorServerFolders.add(detectorFolder);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Only inspect the PR patch, never the full file.
|
||||
if (!file.patch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const detector of detectorLabels) {
|
||||
if (
|
||||
containsDetector(
|
||||
file.patch,
|
||||
detector
|
||||
)
|
||||
) {
|
||||
detectedDetectors.add(detector);
|
||||
|
||||
core.info(
|
||||
`Detected ${detector} in changed code: ${file.filename}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 3. Detect detector from PR title/description
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
|
||||
const { data: pr } =
|
||||
await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
const prText = [
|
||||
pr.title || '',
|
||||
pr.body || ''
|
||||
].join('\n');
|
||||
|
||||
for (const detector of detectorLabels) {
|
||||
if (
|
||||
containsDetector(
|
||||
prText,
|
||||
detector
|
||||
)
|
||||
) {
|
||||
detectedDetectors.add(detector);
|
||||
|
||||
core.info(
|
||||
`Detected ${detector} in PR title or description.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 5. Add detected detector labels
|
||||
* --------------------------------------------------
|
||||
*
|
||||
* Existing labels are left untouched.
|
||||
*
|
||||
* Detector labels are NEVER removed automatically.
|
||||
*/
|
||||
for (const detector of detectedDetectors) {
|
||||
if (!currentLabelNames.includes(detector)) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: [detector]
|
||||
});
|
||||
|
||||
core.info(
|
||||
`Added detector label: ${detector}`
|
||||
);
|
||||
} else {
|
||||
core.info(
|
||||
`Detector label already exists: ${detector}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 6. Summary
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
core.info(
|
||||
`Detected detectors: ${
|
||||
detectedDetectors.size > 0
|
||||
? [...detectedDetectors].join(', ')
|
||||
: 'none'
|
||||
}`
|
||||
);
|
||||
@@ -0,0 +1,182 @@
|
||||
# This workflow is triggered when opening/reopening PR, editing the PR description or changing labels. It ignores unrelated label changes. It runs PR type validation and if it succeeds, it runs PR milestone, release notes, file version and detector labels workflow in parallel.
|
||||
name: PR Edit Trigger
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
models: read
|
||||
|
||||
jobs:
|
||||
check-pr-trigger:
|
||||
name: Check PR Trigger
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
require_validation: ${{ steps.check.outputs.require_validation }}
|
||||
file_label_change: ${{ steps.check.outputs.file_label_change }}
|
||||
pr_edited: ${{ steps.check.outputs.pr_edited }}
|
||||
|
||||
steps:
|
||||
- name: Check PR Trigger
|
||||
id: check
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
let requireValidation = true;
|
||||
let fileLabelChange = false;
|
||||
|
||||
/*
|
||||
* If triggered by label change, ignore unrelated labels.
|
||||
*/
|
||||
const action = context.payload.action;
|
||||
const prEdited = action === 'edited';
|
||||
|
||||
if (action === 'labeled' || action === 'unlabeled') {
|
||||
const changedLabel = context.payload.label?.name;
|
||||
core.info(
|
||||
`Changed Label: ${changedLabel}`
|
||||
);
|
||||
|
||||
const relevantLabels = [
|
||||
'Feature',
|
||||
'Bug Fix',
|
||||
'Infrastructure',
|
||||
'Breaking API'
|
||||
];
|
||||
|
||||
if (!relevantLabels.includes(changedLabel)) {
|
||||
requireValidation = false;
|
||||
}
|
||||
|
||||
// File Major requires Breaking API label
|
||||
const fileLabels = [
|
||||
'File Major',
|
||||
'File Minor',
|
||||
'Breaking API'
|
||||
];
|
||||
if (fileLabels.includes(changedLabel)) {
|
||||
fileLabelChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
core.setOutput(
|
||||
'require_validation',
|
||||
requireValidation.toString()
|
||||
);
|
||||
core.setOutput(
|
||||
'file_label_change',
|
||||
fileLabelChange.toString()
|
||||
);
|
||||
core.setOutput(
|
||||
'pr_edited',
|
||||
prEdited.toString()
|
||||
);
|
||||
|
||||
if (requireValidation) {
|
||||
core.info('PR type validation: Required.');
|
||||
} else {
|
||||
core.info('PR type validation: Not required - Unrelated label.');
|
||||
}
|
||||
if (fileLabelChange) {
|
||||
core.info('File label or Breaking API: Changed.');
|
||||
} else {
|
||||
core.info('File label or Breaking API: No Change.');
|
||||
}
|
||||
|
||||
validate-pr-type:
|
||||
needs: check-pr-trigger
|
||||
if: ${{ needs.check-pr-trigger.outputs.require_validation == 'true' }}
|
||||
uses: ./.github/workflows/pr_type_validation.yaml
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
milestone:
|
||||
needs:
|
||||
- check-pr-trigger
|
||||
- validate-pr-type
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
(
|
||||
needs.check-pr-trigger.outputs.require_validation == 'false' ||
|
||||
needs.validate-pr-type.result == 'success'
|
||||
)
|
||||
}}
|
||||
uses: ./.github/workflows/pr_milestone.yaml
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
detector-labels:
|
||||
needs:
|
||||
- check-pr-trigger
|
||||
- validate-pr-type
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
(
|
||||
needs.check-pr-trigger.outputs.require_validation == 'false' ||
|
||||
needs.validate-pr-type.result == 'success'
|
||||
)
|
||||
}}
|
||||
uses: ./.github/workflows/pr_detector_labels.yaml
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: read
|
||||
|
||||
release-notes:
|
||||
needs:
|
||||
- check-pr-trigger
|
||||
- detector-labels
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
(
|
||||
needs.check-pr-trigger.outputs.require_validation == 'false' ||
|
||||
needs.detector-labels.result == 'success'
|
||||
)
|
||||
}}
|
||||
uses: ./.github/workflows/pr_release_notes.yaml
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
permissions:
|
||||
pull-requests: read
|
||||
contents: read
|
||||
|
||||
file-version:
|
||||
needs:
|
||||
- check-pr-trigger
|
||||
- validate-pr-type
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
(
|
||||
needs.check-pr-trigger.outputs.file_label_change == 'true'
|
||||
) ||
|
||||
(
|
||||
needs.check-pr-trigger.outputs.require_validation == 'false' ||
|
||||
needs.validate-pr-type.result == 'success'
|
||||
)
|
||||
}}
|
||||
uses: ./.github/workflows/pr_file_version.yaml
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
@@ -0,0 +1,295 @@
|
||||
# This reusable workflow checks changes to receiver_defs.h.
|
||||
# It validates the File Major and File Minor labels based on
|
||||
# changes to HDF5_WRITER_VERSION or BINARY_WRITER_VERSION.
|
||||
#
|
||||
# File Major:
|
||||
# The value before the decimal point changes.
|
||||
#
|
||||
# File Minor:
|
||||
# Only the value after the decimal point changes.
|
||||
#
|
||||
# Existing labels are never silently removed. If the labels do not
|
||||
# match the detected version change, the workflow fails.
|
||||
|
||||
name: PR File Version
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pr_number:
|
||||
required: true
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
file-version:
|
||||
name: Check file version
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check file version
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const prNumber = ${{ inputs.pr_number }};
|
||||
|
||||
const filePath =
|
||||
'slsReceiverSoftware/src/receiver_defs.h';
|
||||
|
||||
const versionDefinitions = [
|
||||
'HDF5_WRITER_VERSION',
|
||||
'BINARY_WRITER_VERSION'
|
||||
];
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 1. Get current PR labels.
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
const { data: currentLabels } =
|
||||
await github.rest.issues.listLabelsOnIssue({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const currentLabelNames =
|
||||
currentLabels.map(label => label.name);
|
||||
|
||||
const hasFileMajor =
|
||||
currentLabelNames.includes('File Major');
|
||||
|
||||
const hasFileMinor =
|
||||
currentLabelNames.includes('File Minor');
|
||||
|
||||
const hasBreakingAPI =
|
||||
currentLabelNames.includes('Breaking API');
|
||||
|
||||
/*
|
||||
* File Major and File Minor are mutually exclusive.
|
||||
*/
|
||||
if (hasFileMajor && hasFileMinor) {
|
||||
core.setFailed(
|
||||
'Both "File Major" and "File Minor" labels are present. Remove both labels and let the workflow determine the correct label based on changes to receiver_defs.h.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 2. Get all files changed by the PR.
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
const files = await github.paginate(
|
||||
github.rest.pulls.listFiles,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 3. Check whether receiver_defs.h was changed.
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
const changedFile = files.find(
|
||||
file => file.filename === filePath
|
||||
);
|
||||
|
||||
if (!changedFile) {
|
||||
if (hasFileMajor || hasFileMinor) {
|
||||
core.setFailed(
|
||||
`${filePath} was not changed, but a File Major/File Minor label is present. Remove the label and let the workflow determine the correct label based on changes to receiver_defs.h.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(
|
||||
`${filePath} was not changed. No file version label required.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(
|
||||
`${filePath} was changed. Checking file version definitions.`
|
||||
);
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 4. Check the changed lines in the patch.
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
const patch = changedFile.patch || '';
|
||||
|
||||
const oldVersions = {};
|
||||
const newVersions = {};
|
||||
|
||||
const versionRegex =
|
||||
/^([+-])\s*#define\s+(HDF5_WRITER_VERSION|BINARY_WRITER_VERSION)\s+\((\d+)\.(\d+)\)/;
|
||||
|
||||
for (const line of patch.split('\n')) {
|
||||
const match = line.match(versionRegex);
|
||||
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const changeType = match[1];
|
||||
const name = match[2];
|
||||
const major = Number(match[3]);
|
||||
const minor = Number(match[4]);
|
||||
|
||||
if (changeType === '-') {
|
||||
oldVersions[name] = {
|
||||
major,
|
||||
minor
|
||||
};
|
||||
} else if (changeType === '+') {
|
||||
newVersions[name] = {
|
||||
major,
|
||||
minor
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 5. Determine the file version change.
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
let versionChanged = false;
|
||||
let majorChange = false;
|
||||
|
||||
for (const name of versionDefinitions) {
|
||||
const oldVersion = oldVersions[name];
|
||||
const newVersion = newVersions[name];
|
||||
|
||||
/*
|
||||
* Both old and new definitions must be present
|
||||
* in order to identify a version change.
|
||||
*/
|
||||
if (!oldVersion || !newVersion) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore the definition if the version did not
|
||||
* actually change.
|
||||
*/
|
||||
if (
|
||||
oldVersion.major === newVersion.major &&
|
||||
oldVersion.minor === newVersion.minor
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
versionChanged = true;
|
||||
|
||||
core.info(
|
||||
`${name}: ` +
|
||||
`${oldVersion.major}.${oldVersion.minor} -> ` +
|
||||
`${newVersion.major}.${newVersion.minor}`
|
||||
);
|
||||
|
||||
/*
|
||||
* A change before the decimal point is a
|
||||
* major version change.
|
||||
*/
|
||||
if (oldVersion.major !== newVersion.major) {
|
||||
majorChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 6. No relevant version change.
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
if (!versionChanged) {
|
||||
if (hasFileMajor || hasFileMinor) {
|
||||
core.setFailed(
|
||||
`Neither HDF5_WRITER_VERSION nor BINARY_WRITER_VERSION was changed, but a File Major/File Minor label is present. Remove the labels and let the workflow determine the correct label based on changes to receiver_defs.h.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(
|
||||
'Neither HDF5_WRITER_VERSION nor BINARY_WRITER_VERSION was changed. No file version label required.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 7. Major version change.
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
if (majorChange) {
|
||||
if (hasFileMinor) {
|
||||
core.setFailed(
|
||||
'A major file version change was detected, but the PR has the "File Minor" label. Remove the "File Minor" label and let the workflow determine the correct label based on changes to receiver_defs.h.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasBreakingAPI) {
|
||||
core.setFailed(
|
||||
'A major file version change was detected, and the PR has not recognized "Breaking API". Please check the checkbox "Breaking API" in the PR description so that the appropriate label can be added to the PR and milestone recalculated.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasFileMajor) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: ['File Major']
|
||||
});
|
||||
|
||||
core.info(
|
||||
'Added label: File Major'
|
||||
);
|
||||
} else {
|
||||
core.info(
|
||||
'File Major label and Breaking API label are correctly present.'
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------
|
||||
* 8. Minor version change.
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
if (hasFileMajor) {
|
||||
core.setFailed(
|
||||
'Only a minor file version change was detected, but the PR has the "File Major" label. Remove the "File Major" label and let the workflow determine the correct label based on changes to receiver_defs.h.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasFileMinor) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: ['File Minor']
|
||||
});
|
||||
|
||||
core.info(
|
||||
'Added label: File Minor'
|
||||
);
|
||||
} else {
|
||||
core.info(
|
||||
'File Minor label is correctly present.'
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
# This reusable workflow sets the milestone based on the
|
||||
# current PR labels.
|
||||
|
||||
name: PR Milestone
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pr_number:
|
||||
required: true
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
set-milestone:
|
||||
name: Set PR milestone
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Determine target milestone
|
||||
id: determine-milestone
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const prNumber = ${{ inputs.pr_number }};
|
||||
|
||||
/*
|
||||
* Get the base branch of the PR.
|
||||
*/
|
||||
const { data: pullRequest } =
|
||||
await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
const baseBranch = pullRequest.base.ref;
|
||||
|
||||
/*
|
||||
* 1. Return early if base is main branch. Milestone is already
|
||||
* managed and probably done when merging to main.
|
||||
*/
|
||||
if (baseBranch === 'main') {
|
||||
core.info('PR targets main. Milestone is managed separately.');
|
||||
core.setOutput('target_milestone', '');
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to set the target milestone output.
|
||||
*/
|
||||
const setTargetMilestoneOutput = (targetMilestone) => {
|
||||
core.info(`Target milestone: ${targetMilestone}`);
|
||||
core.setOutput('target_milestone', targetMilestone);
|
||||
};
|
||||
|
||||
/*
|
||||
* 2. If PR targets a release-candidate branch (for example 9.1.0.rc),
|
||||
* set milestone to the release version prefix (for example 9.1.0)
|
||||
* and return early.
|
||||
*/
|
||||
if (baseBranch.endsWith('.rc')) {
|
||||
const targetMilestone = baseBranch.replace(/\.rc$/, '');
|
||||
|
||||
core.info(
|
||||
`PR targets release-candidate branch "${baseBranch}". ` +
|
||||
`Using milestone "${targetMilestone}".`
|
||||
);
|
||||
|
||||
setTargetMilestoneOutput(targetMilestone);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. If PR targets a branch that is not developer, do nothing.
|
||||
*/
|
||||
if (baseBranch !== 'developer') {
|
||||
core.info('PR does not target developer branch. Skipping milestone management.');
|
||||
core.setOutput('target_milestone', '');
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 4. If PR targets developer, check "Equivalent PRs" in the PR body.
|
||||
* If an equivalent PR targets an RC branch (for example 9.1.0.rc),
|
||||
* use the corresponding release milestone (for example 9.1.0).
|
||||
*/
|
||||
if (baseBranch === 'developer') {
|
||||
const equivalentPrSectionMatch = pullRequest.body?.match(
|
||||
/(^|\n)#{1,6}\s*Equivalent PRs\s*\n([\s\S]*?)(?=\n#{1,6}\s+\S|\s*$)/i
|
||||
);
|
||||
|
||||
const equivalentPrNumbers =
|
||||
equivalentPrSectionMatch?.[2]
|
||||
? Array.from(
|
||||
equivalentPrSectionMatch[2].matchAll(/#(\d+)/g),
|
||||
match => Number(match[1])
|
||||
)
|
||||
: [];
|
||||
|
||||
for (const equivalentPrNumber of equivalentPrNumbers) {
|
||||
if (equivalentPrNumber === prNumber) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { data: equivalentPr } =
|
||||
await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: equivalentPrNumber
|
||||
});
|
||||
|
||||
const equivalentBaseBranch = equivalentPr.base.ref;
|
||||
|
||||
if (!equivalentBaseBranch.endsWith('.rc')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetMilestone =
|
||||
equivalentBaseBranch.replace(/\.rc$/, '');
|
||||
|
||||
core.info(
|
||||
`Developer PR references RC PR #${equivalentPrNumber} ` +
|
||||
`(${equivalentBaseBranch}). Using milestone "${targetMilestone}".`
|
||||
);
|
||||
|
||||
setTargetMilestoneOutput(targetMilestone);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 5. Read release targets through the GitHub API
|
||||
* from the base branch of PR.
|
||||
*/
|
||||
const { data: file } =
|
||||
await github.rest.repos.getContent({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: '.github/release-targets.yaml',
|
||||
ref: baseBranch
|
||||
});
|
||||
|
||||
const releaseTargetsFile =
|
||||
Buffer.from(file.content, 'base64').toString('utf8');
|
||||
|
||||
const getTarget = (name) => {
|
||||
const match = releaseTargetsFile.match(
|
||||
new RegExp(`^${name}:\\s*["']?([^"'\\s]+)["']?`, 'm')
|
||||
);
|
||||
|
||||
if (!match) {
|
||||
core.setFailed(
|
||||
`Release target "${name}" was not found.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return match[1];
|
||||
};
|
||||
|
||||
const major = getTarget('major');
|
||||
const minor = getTarget('minor');
|
||||
const bugFix = getTarget('bug_fix');
|
||||
|
||||
if (!major || !minor || !bugFix) {
|
||||
core.setFailed(
|
||||
'Required release targets (major, minor, bug_fix) could not be resolved from yaml file.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get current PR labels.
|
||||
*/
|
||||
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);
|
||||
|
||||
/*
|
||||
* Determine the milestone.
|
||||
*
|
||||
* Breaking API has highest priority.
|
||||
*/
|
||||
let targetMilestone;
|
||||
|
||||
if (labelNames.includes('Breaking API')) {
|
||||
targetMilestone = major;
|
||||
} else if (labelNames.includes('Feature')) {
|
||||
targetMilestone = minor;
|
||||
} else if (
|
||||
labelNames.includes('Bug Fix') ||
|
||||
labelNames.includes('Infrastructure')
|
||||
) {
|
||||
targetMilestone = bugFix;
|
||||
} else {
|
||||
core.setFailed(
|
||||
'No milestone-related labels found.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setTargetMilestoneOutput(targetMilestone);
|
||||
|
||||
- name: Set milestone
|
||||
if: steps.determine-milestone.outputs.target_milestone != ''
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const prNumber = ${{ inputs.pr_number }};
|
||||
const targetMilestone = '${{ steps.determine-milestone.outputs.target_milestone }}';
|
||||
|
||||
const { data: milestones } =
|
||||
await github.rest.issues.listMilestones({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open'
|
||||
});
|
||||
|
||||
const milestone = milestones.find(
|
||||
milestone => milestone.title === targetMilestone
|
||||
);
|
||||
|
||||
if (!milestone) {
|
||||
core.setFailed(
|
||||
`Milestone "${targetMilestone}" was not found.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
milestone: milestone.number
|
||||
});
|
||||
|
||||
core.info(
|
||||
`Milestone set to "${targetMilestone}".`
|
||||
);
|
||||
@@ -0,0 +1,262 @@
|
||||
# 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 and release notes are found, validation fails.
|
||||
|
||||
name: PR Release Notes
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pr_number:
|
||||
required: true
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-release-notes:
|
||||
name: Check PR release notes
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
- name: Validate with labels
|
||||
id: validate-labels
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const prNumber = ${{ inputs.pr_number }};
|
||||
if (!prNumber) {
|
||||
core.setFailed('Unable to determine PR number.');
|
||||
return;
|
||||
}
|
||||
|
||||
core.setOutput('is_main_branch', 'false');
|
||||
core.setOutput('require_release_notes', 'true');
|
||||
|
||||
// base branch: main => no validation needed
|
||||
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.');
|
||||
core.setOutput('is_main_branch', 'true');
|
||||
core.setOutput('require_release_notes', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
// get current PR labels
|
||||
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);
|
||||
|
||||
// infrastructure label
|
||||
const hasInfrastructure = labelNames.includes('Infrastructure');
|
||||
core.info('Infrastructure label: ' + hasInfrastructure);
|
||||
|
||||
// developer detector label
|
||||
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: ' + hasOnlyDevelopingDetectors + '\n');
|
||||
|
||||
if (hasInfrastructure || hasOnlyDevelopingDetectors) {
|
||||
core.setOutput('require_release_notes', 'false');
|
||||
core.info ('Release notes required: false');
|
||||
return;
|
||||
}
|
||||
|
||||
core.info('Release notes required: true');
|
||||
|
||||
|
||||
- name: Find release notes in current PR
|
||||
id: find-current
|
||||
if: steps.validate-labels.outputs.is_main_branch == 'false'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const prNumber = ${{ inputs.pr_number }};
|
||||
core.setOutput('needs_further_action', 'false');
|
||||
|
||||
// some functions to help find release notes in current PR body
|
||||
const stripIgnoredContent = (text = '') => {
|
||||
return (text || '')
|
||||
.replace(/<!--.*?-->/gs, '\n')
|
||||
.replace(/<details\b[\s\S]*?<\/details>/gi, '\n')
|
||||
.trim();
|
||||
};
|
||||
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;
|
||||
};
|
||||
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
// find release notes in the current PR body
|
||||
const releaseNotes = findReleaseNotesSection(pr.body || '');
|
||||
const hasReleaseNotesCurrent = Boolean(releaseNotes);
|
||||
core.info('Release notes in current PR: ' + (hasReleaseNotesCurrent ? 'Found\n' : 'Not found\n'));
|
||||
|
||||
// validate release notes requirement
|
||||
const requireReleaseNotes = '${{ steps.validate-labels.outputs.require_release_notes }}' === 'true';
|
||||
if (!requireReleaseNotes) {
|
||||
if (hasReleaseNotesCurrent) {
|
||||
core.setFailed('Release notes cannot be included for this PR ' +
|
||||
'(Infrastructure/developing detector). \n' +
|
||||
'To fix this, \n' +
|
||||
'\t - remove release notes or\n' +
|
||||
'\t - change PR type or\n' +
|
||||
'\t - add/remove detector labels.\n');
|
||||
return;
|
||||
}
|
||||
core.info('No further action required (Infrastructure/developing detector).');
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasReleaseNotesCurrent) {
|
||||
core.info('No further action required.');
|
||||
return;
|
||||
}
|
||||
|
||||
core.setOutput('needs_further_action', 'true');
|
||||
core.info('Further action required.');
|
||||
|
||||
- name: Find release notes in linked PRs
|
||||
if: >-
|
||||
${{
|
||||
steps.validate-labels.outputs.is_main_branch == 'false' &&
|
||||
steps.find-current.outputs.needs_further_action == 'true'
|
||||
}}
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const prNumber = ${{ inputs.pr_number }};
|
||||
|
||||
// functions to find release notes in linked PRs
|
||||
const stripIgnoredContent = (text = '') => {
|
||||
return (text || '')
|
||||
.replace(/<!--.*?-->/gs, '\n')
|
||||
.replace(/<details\b[\s\S]*?<\/details>/gi, '\n')
|
||||
.trim();
|
||||
};
|
||||
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 '';
|
||||
|
||||
return (match[2] || '')
|
||||
.split('\n')
|
||||
.filter(line => !/^#{1,6}\s+/.test(line.trim()))
|
||||
.join('\n')
|
||||
.trim();
|
||||
};
|
||||
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));
|
||||
};
|
||||
|
||||
// Loop thorough linked PRs to find release notes
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
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.'
|
||||
);
|
||||
@@ -0,0 +1,29 @@
|
||||
# This workflow is triggered when code is pushed to a pull request. It runs the reusable detector label workflow and the file version workflow in parallel.
|
||||
|
||||
name: PR Synchronize Trigger
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
detector-labels:
|
||||
uses: ./.github/workflows/pr_detector_labels.yaml
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: read
|
||||
|
||||
file-version:
|
||||
uses: ./.github/workflows/pr_file_version.yaml
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
@@ -0,0 +1,177 @@
|
||||
# This reusable workflow reads the primary type and breaking api selection from the PR description and validates the PR labels. It ensures that exactly one primary type is selected, and that the Breaking API label is consistent with the checkbox in the PR description. It also adds or removes labels as necessary to match the PR description.
|
||||
|
||||
name: PR Type Validation
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pr_number:
|
||||
required: true
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate_pr_type:
|
||||
name: PR Type Validation
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: PR Type Validation and Breaking API
|
||||
id: validate
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
/*
|
||||
* Read the body contents of PR
|
||||
*/
|
||||
const prNumber = ${{ inputs.pr_number }};
|
||||
|
||||
if (!prNumber) {
|
||||
core.setFailed('Unable to determine PR number.');
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: pr } =
|
||||
await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
const body = pr.body || '';
|
||||
|
||||
/*
|
||||
* Check which primary PR type is selected.
|
||||
*/
|
||||
const feature =
|
||||
/-\s*\[[xX]\]\s*Feature\b/.test(body);
|
||||
|
||||
const bugFix =
|
||||
/-\s*\[[xX]\]\s*Bug Fix\b/.test(body);
|
||||
|
||||
const infrastructure =
|
||||
/-\s*\[[xX]\]\s*Infrastructure\b/.test(body);
|
||||
|
||||
const primaryTypes = [
|
||||
feature && 'Feature',
|
||||
bugFix && 'Bug Fix',
|
||||
infrastructure && 'Infrastructure'
|
||||
].filter(Boolean);
|
||||
|
||||
/*
|
||||
* Exactly one primary PR type must be selected.
|
||||
*/
|
||||
if (primaryTypes.length !== 1) {
|
||||
core.setFailed(
|
||||
'Exactly one primary PR type must be selected. Select exactly one of: Feature, Bug Fix, Infrastructure in the description.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const primaryType = primaryTypes[0];
|
||||
|
||||
/*
|
||||
* Infrastructure PRs cannot be Breaking API.
|
||||
*/
|
||||
const breakingApi =
|
||||
/-\s*\[[xX]\]\s*This is a Breaking API change\b/.test(body);
|
||||
|
||||
if (infrastructure && breakingApi) {
|
||||
core.setFailed(
|
||||
'Infrastructure PRs cannot be Breaking API changes. Deselect Breaking API or change the PR type to Feature or Bug Fix in the description.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(`Primary PR type: ${primaryType}`);
|
||||
core.info(
|
||||
`Breaking API: ${breakingApi ? 'yes' : 'no'}`
|
||||
);
|
||||
|
||||
/*
|
||||
* Get the labels currently on the PR.
|
||||
*/
|
||||
const { data: currentLabels } =
|
||||
await github.rest.issues.listLabelsOnIssue({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const currentLabelNames =
|
||||
currentLabels.map(label => label.name);
|
||||
|
||||
const primaryLabels = [
|
||||
'Feature',
|
||||
'Bug Fix',
|
||||
'Infrastructure'
|
||||
];
|
||||
|
||||
const selectedPrimary =
|
||||
primaryLabels.filter(label =>
|
||||
currentLabelNames.includes(label)
|
||||
);
|
||||
|
||||
if (selectedPrimary.length > 1) {
|
||||
core.setFailed(
|
||||
`Invalid PR label state: multiple primary labels are present: ${selectedPrimary.join(', ')}. Remove these primary type labels 'Feature, Bug Fix, Infrastructure' and let the workflow set the correct label based on the PR description.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedPrimary.length === 1 && selectedPrimary[0] !== primaryType) {
|
||||
core.setFailed(
|
||||
`Invalid PR label state: the PR body selects ${primaryType}, but the existing label is ${selectedPrimary[0]}. Remove the existing label and let the workflow set the correct label based on the PR description.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
currentLabelNames.includes('Breaking API') &&
|
||||
infrastructure
|
||||
) {
|
||||
core.setFailed(
|
||||
'Infrastructure PRs cannot have the Breaking API label.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!breakingApi &&
|
||||
currentLabelNames.includes('Breaking API')
|
||||
) {
|
||||
core.setFailed(
|
||||
'Breaking API label is present but the PR body does not mark the checkbox. Remove the label or check the checkbox in the PR body.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
breakingApi &&
|
||||
!currentLabelNames.includes('Breaking API')
|
||||
) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: ['Breaking API']
|
||||
});
|
||||
|
||||
core.info('Added label: Breaking API');
|
||||
}
|
||||
|
||||
if (!currentLabelNames.includes(primaryType)) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: [primaryType]
|
||||
});
|
||||
|
||||
core.info(`Added label: ${primaryType}`);
|
||||
}
|
||||
|
||||
core.info('PR validation passed.');
|
||||
Reference in new issue
Block a user