Commit e85fa9538a for woocommerce

commit e85fa9538a9dcc189d0de1569e6d5d969c5dcb60
Author: Jorge A. Torres <jorge.torres@automattic.com>
Date:   Thu Nov 27 17:21:31 2025 +0000

    Automate cleanup of old milestones at feature freeze time (#62135)

diff --git a/.github/workflows/release-bump-version.yml b/.github/workflows/release-bump-version.yml
index ff831134e7..2ace76d284 100644
--- a/.github/workflows/release-bump-version.yml
+++ b/.github/workflows/release-bump-version.yml
@@ -173,23 +173,3 @@ jobs:
             --head ${branch_name} \
             --label Release

-      - name: Ensure milestone for dev version exists
-        if: ${{ inputs.bump-type == 'dev' }}
-        uses: actions/github-script@v7
-        with:
-          script: |
-            const milestone = '${{ steps.compute-new-version.outputs.nextStable }}';
-
-            try {
-              await github.rest.issues.createMilestone({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                title: milestone,
-              });
-            } catch ( e ) {
-              if ( e.response.data.errors?.some( (err) => 'already_exists' === err.code ) ) {
-                console.log( `Milestone ${ milestone } already exists.` );
-              } else {
-                throw e;
-              }
-            }
diff --git a/.github/workflows/release-code-freeze.yml b/.github/workflows/release-code-freeze.yml
index a169514c81..27840280a8 100644
--- a/.github/workflows/release-code-freeze.yml
+++ b/.github/workflows/release-code-freeze.yml
@@ -76,6 +76,7 @@ jobs:
     outputs:
       nextReleaseBranch: ${{ steps.calculate-versions.outputs.nextReleaseBranch }}
       nextReleaseVersion: ${{ steps.calculate-versions.outputs.nextReleaseVersion }}
+      nextTrunkMainVersion: ${{ steps.calculate-versions.outputs.nextTrunkMainVersion }}
     steps:
       # - name: Verify no PRs open by the github-actions bot are open
       #   uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1
@@ -114,14 +115,14 @@ jobs:
                 return;
             }

-            const trunkMainVersion = m[1];
-            const nextReleaseVersion = trunkVersion.replace( '-dev', '' );
-            const nextReleaseBranch = `release/${ nextReleaseVersion.slice( 0, -2 ) }`;
-            const nextTrunkVersion = `${ ( Number( trunkMainVersion ) + 0.1 ).toFixed( 1 ) }.0-dev`;
+            const trunkMainVersion = m[1]; // e.g. '10.4'
+            const nextReleaseVersion = `${ trunkMainVersion }.0`; // e.g. '10.4.0'
+            const nextReleaseBranch = `release/${ trunkMainVersion }`; // e.g. 'release/10.4'
+            const nextTrunkMainVersion = `${ ( Number( trunkMainVersion ) + 0.1 ).toFixed( 1 ) }`; // e.g. '10.5'

             core.setOutput( 'nextReleaseVersion', nextReleaseVersion );
             core.setOutput( 'nextReleaseBranch', nextReleaseBranch );
-            core.setOutput( 'nextTrunkVersion', nextTrunkVersion );
+            core.setOutput( 'nextTrunkMainVersion', nextTrunkMainVersion );

   run-feature-freeze:
     name: Perform feature freeze
@@ -143,6 +144,63 @@ jobs:
           git checkout trunk
           git checkout -b ${{ needs.prepare-for-feature-freeze.outputs.nextReleaseBranch }}
           git push origin ${{ needs.prepare-for-feature-freeze.outputs.nextReleaseBranch }}
+      - name: Create milestone for next dev cycle
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const trunkMilestone = '${{ needs.prepare-for-feature-freeze.outputs.nextTrunkMainVersion }}.0';
+
+            try {
+              await github.rest.issues.createMilestone( {
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                title: trunkMilestone,
+              } );
+            } catch ( e ) {
+              if ( e.response?.data?.errors?.some( ( err ) => 'already_exists' === err.code ) ) {
+                console.log( `Milestone ${ trunkMilestone } already exists.` );
+              } else {
+                throw e;
+              }
+            }
+
+  cleanup-milestones:
+    name: Clean up old and completed milestones
+    runs-on: ${{ ( github.repository == 'woocommerce/woocommerce' && 'blacksmith-2vcpu-ubuntu-2404' ) || 'ubuntu-latest' }}
+    needs: [prepare-for-feature-freeze, run-feature-freeze]
+    steps:
+      - name: Close completed milestones
+        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1
+        with:
+          script: |
+            const nextTrunkMainVersion = '${{ needs.prepare-for-feature-freeze.outputs.nextTrunkMainVersion }}'; // e.g. '10.5'
+            const minVersionToKeep     = Number( nextTrunkMainVersion ) - 0.2; // e.g. 10.3
+
+            const { data: latestMilestones } = await github.rest.issues.listMilestones( {
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              state: 'open',
+              per_page: 10,
+              sort: 'completeness',
+              direction: 'desc',
+            } );
+
+            // Filter out milestones that do not have any open issues and are older than the minimum version to keep.
+            // We want to keep milestone for at least the last 3 main versions (dev, frozen, stable).
+            const milestonesToClose = latestMilestones.filter(
+              milestone => 0 === milestone.open_issues && /^\d+\.\d+\.0$/.test( milestone.title ) && Number( milestone.title.slice( 0, -2 ) ) < minVersionToKeep
+            );
+
+            for ( const milestone of milestonesToClose ) {
+              await github.rest.issues.updateMilestone( {
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                milestone_number: milestone.number,
+                state: 'closed',
+              } );
+
+              console.log( `Closed milestone ${milestone.title}` );
+            }

   bump-version-in-trunk:
     name: Bump version in trunk for next development cycle
@@ -260,7 +318,7 @@ jobs:
   notify-slack-on-failure:
     name: Notify Slack on Failure
     runs-on: ${{ ( github.repository == 'woocommerce/woocommerce' && 'blacksmith-2vcpu-ubuntu-2404' ) || 'ubuntu-latest' }}
-    needs: [ check-feature-freeze-event, prepare-for-feature-freeze, run-feature-freeze, bump-version-in-trunk, build-dev-release, publish-dev-release, notify-slack, trigger-webhook ]
+    needs: [ check-feature-freeze-event, prepare-for-feature-freeze, run-feature-freeze, cleanup-milestones, bump-version-in-trunk, build-dev-release, publish-dev-release, notify-slack, trigger-webhook ]
     if: always() && ((contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')))
     steps:
       - name: Notify Slack on failure