name: PR Milestone Check on: pull_request: types: [opened, reopened, edited, synchronize, milestoned, demilestoned] branches: - "main" - "release-v*.*.*" jobs: check-milestone: name: Check PR Milestone runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Validate PR milestone uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | // Get PR number directly from the event payload const prNumber = context.payload.pull_request.number; // Get PR details const { data: prData } = await github.rest.pulls.get({ owner: 'meilisearch', repo: 'meilisearch', pull_number: prNumber }); // Get base branch name const baseBranch = prData.base.ref; console.log(`Base branch: ${baseBranch}`); // Get PR milestone const prMilestone = prData.milestone; if (!prMilestone) { core.setFailed('PR must have a milestone assigned'); return; } console.log(`PR milestone: ${prMilestone.title}`); // Validate milestone format: vx.y.z const milestoneRegex = /^v\d+\.\d+\.\d+$/; if (!milestoneRegex.test(prMilestone.title)) { core.setFailed(`Milestone "${prMilestone.title}" does not follow the required format vx.y.z`); return; } // For main branch PRs, check if the milestone is the highest one if (baseBranch === 'main') { // Get all milestones const { data: milestones } = await github.rest.issues.listMilestones({ owner: 'meilisearch', repo: 'meilisearch', state: 'open', sort: 'due_on', direction: 'desc' }); // Sort milestones by version number (vx.y.z) const sortedMilestones = milestones .filter(m => milestoneRegex.test(m.title)) .sort((a, b) => { const versionA = a.title.substring(1).split('.').map(Number); const versionB = b.title.substring(1).split('.').map(Number); // Compare major version if (versionA[0] !== versionB[0]) return versionB[0] - versionA[0]; // Compare minor version if (versionA[1] !== versionB[1]) return versionB[1] - versionA[1]; // Compare patch version return versionB[2] - versionA[2]; }); if (sortedMilestones.length === 0) { core.setFailed('No valid milestones found in the repository. Please create at least one milestone with the format vx.y.z'); return; } const highestMilestone = sortedMilestones[0]; console.log(`Highest milestone: ${highestMilestone.title}`); if (prMilestone.title !== highestMilestone.title) { core.setFailed(`PRs targeting the main branch must use the highest milestone (${highestMilestone.title}), but this PR uses ${prMilestone.title}`); return; } } else { // For release branches, the milestone should match the branch version const branchVersion = baseBranch.substring(8); // remove 'release-' if (prMilestone.title !== branchVersion) { core.setFailed(`PRs targeting release branch "${baseBranch}" must use the matching milestone "${branchVersion}", but this PR uses "${prMilestone.title}"`); return; } } console.log('PR milestone validation passed!');