Commit 71ec4f9e600 for woocommerce
commit 71ec4f9e60090b4b36be08b2ab49ef213c4a2ff3
Author: Mike Jolley <mike.jolley@me.com>
Date: Tue Jun 9 12:38:00 2026 +0100
Fix milestone checkbox detection without markers (#64896)
* Fix milestone checkbox detection without markers
* Clarify milestone validation message and add edge-case tests
diff --git a/.github/workflows/scripts/assign-milestone-on-merge.js b/.github/workflows/scripts/assign-milestone-on-merge.js
index f3a2c429ecd..13b0d70a780 100644
--- a/.github/workflows/scripts/assign-milestone-on-merge.js
+++ b/.github/workflows/scripts/assign-milestone-on-merge.js
@@ -1,3 +1,5 @@
+const { getAutoAssignMilestoneSelection } = require('./milestone-selection');
+
/**
* Assigns a milestone to a merged PR based on the checkbox selection.
*
@@ -21,16 +23,14 @@ module.exports = async ({ github, context, core }) => {
return;
}
- const nextVersionMatch = body.match(/- \[([ xX])\].*\*\*.*next WooCommerce version.*\*\*/);
+ const nextVersionSelection = getAutoAssignMilestoneSelection(body);
- if (!nextVersionMatch) {
+ if (!nextVersionSelection.found) {
core.setFailed('Milestone selection checkbox not found in PR description. Cannot assign milestone.');
return;
}
- const nextVersionChecked = nextVersionMatch[1].toLowerCase() === 'x';
-
- if (!nextVersionChecked) {
+ if (!nextVersionSelection.checked) {
core.setFailed('Auto-assign checkbox not selected. Cannot assign milestone.');
return;
}
diff --git a/.github/workflows/scripts/milestone-selection.js b/.github/workflows/scripts/milestone-selection.js
new file mode 100644
index 00000000000..68215b99f15
--- /dev/null
+++ b/.github/workflows/scripts/milestone-selection.js
@@ -0,0 +1,31 @@
+const AUTO_ASSIGN_MILESTONE_CHECKBOX_PATTERN = /(?:^|\r?\n)\s*[-*]\s*\[\s*([xX]?)\s*\][^\r\n]*Automatically assign milestone[^\r\n]*next WooCommerce version[^\r\n]*/;
+
+/**
+ * Finds the PR template checkbox used to request automatic milestone assignment.
+ *
+ * The pattern is non-global, so the first matching checkbox in the body wins.
+ * The PR template only ever renders this checkbox once, so first-match is the
+ * intended contract; duplicate occurrences (if any) are ignored.
+ *
+ * @param {string} body PR body.
+ * @return {{found: boolean, checked: boolean}} Checkbox state.
+ */
+const getAutoAssignMilestoneSelection = (body) => {
+ const match = (body || '').match(AUTO_ASSIGN_MILESTONE_CHECKBOX_PATTERN);
+
+ if (!match) {
+ return {
+ found: false,
+ checked: false,
+ };
+ }
+
+ return {
+ found: true,
+ checked: match[1].toLowerCase() === 'x',
+ };
+};
+
+module.exports = {
+ getAutoAssignMilestoneSelection,
+};
diff --git a/.github/workflows/scripts/milestone-selection.test.js b/.github/workflows/scripts/milestone-selection.test.js
new file mode 100644
index 00000000000..1e8cb32f869
--- /dev/null
+++ b/.github/workflows/scripts/milestone-selection.test.js
@@ -0,0 +1,80 @@
+const assert = require('node:assert/strict');
+const test = require('node:test');
+
+const { getAutoAssignMilestoneSelection } = require('./milestone-selection');
+
+const checkboxLine =
+ '- [x] Automatically assign milestone for the **[next WooCommerce version](../blob/trunk/plugins/woocommerce/woocommerce.php#L6)**';
+
+test('detects checked auto-assign checkbox with marker comments', () => {
+ const body = [
+ '### Milestone',
+ '',
+ '<!-- milestone-target-selection -->',
+ checkboxLine,
+ '<!-- /milestone-target-selection -->',
+ ].join('\n');
+
+ assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+ found: true,
+ checked: true,
+ });
+});
+
+test('detects checked auto-assign checkbox without marker comments', () => {
+ const body = [
+ '### Milestone',
+ '',
+ checkboxLine,
+ '',
+ '> **Note:** Check the box above to have the milestone automatically assigned when merged.',
+ ].join('\n');
+
+ assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+ found: true,
+ checked: true,
+ });
+});
+
+test('detects unchecked auto-assign checkbox', () => {
+ const body = checkboxLine.replace('[x]', '[ ]');
+
+ assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+ found: true,
+ checked: false,
+ });
+});
+
+test('does not detect unrelated checklist items', () => {
+ const body = '- [x] I have tested the next WooCommerce version locally.';
+
+ assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+ found: false,
+ checked: false,
+ });
+});
+
+test('detects checked auto-assign checkbox with "*" bullet and uppercase marker', () => {
+ const body = checkboxLine.replace('- [x]', '* [X]');
+
+ assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+ found: true,
+ checked: true,
+ });
+});
+
+test('detects indented auto-assign checkbox with CRLF line endings', () => {
+ const body = ['### Milestone', '', ` ${checkboxLine}`, ''].join('\r\n');
+
+ assert.deepEqual(getAutoAssignMilestoneSelection(body), {
+ found: true,
+ checked: true,
+ });
+});
+
+test('returns not found for null or undefined body', () => {
+ const expected = { found: false, checked: false };
+
+ assert.deepEqual(getAutoAssignMilestoneSelection(null), expected);
+ assert.deepEqual(getAutoAssignMilestoneSelection(undefined), expected);
+});
diff --git a/.github/workflows/scripts/validate-milestone-selection.js b/.github/workflows/scripts/validate-milestone-selection.js
index 4b700f69e00..1b11bce2555 100644
--- a/.github/workflows/scripts/validate-milestone-selection.js
+++ b/.github/workflows/scripts/validate-milestone-selection.js
@@ -1,3 +1,5 @@
+const { getAutoAssignMilestoneSelection } = require('./milestone-selection');
+
/**
* Validates that a PR has either a milestone set or the auto-assign checkbox selected.
*
@@ -21,21 +23,14 @@ module.exports = async ({ github, context, core }) => {
const body = pr.body || '';
- if (!body.includes('<!-- milestone-target-selection -->')) {
- core.setFailed('Milestone selection section not found in PR description. Please use the PR template and select the milestone option, or manually assign a milestone.');
- return;
- }
+ const nextVersionSelection = getAutoAssignMilestoneSelection(body);
- const nextVersionMatch = body.match(/- \[([ xX])\].*\*\*.*next WooCommerce version.*\*\*/);
-
- if (!nextVersionMatch) {
- core.setFailed('Milestone selection checkbox not found or modified. Please restore the original checkbox format from the PR template.');
+ if (!nextVersionSelection.found) {
+ core.setFailed('Auto-assign milestone checkbox not found. Please add the milestone checkbox from the PR template, or manually assign a milestone.');
return;
}
- const nextVersionChecked = nextVersionMatch[1].toLowerCase() === 'x';
-
- if (!nextVersionChecked) {
+ if (!nextVersionSelection.checked) {
core.setFailed('No milestone option selected. Please check the auto-assign checkbox or manually assign a milestone.');
return;
}