name: Milestone Sync on: pull_request_target: types: - opened - reopened - ready_for_review - edited - milestoned - closed branches: - develop permissions: issues: write pull-requests: write concurrency: group: milestone-sync-${{ github.event.pull_request.number }} jobs: milestone-sync: if: github.repository_owner == 'GameServerManagers' runs-on: ubuntu-latest steps: - name: Set PR milestone and update linked issues uses: actions/github-script@v9 with: script: | const { owner, repo } = context.repo; const pr = context.payload.pull_request; const action = context.payload.action; const fixedLabel = 'status: fixed in develop'; if (action === 'closed' && !pr.merged) { return; } let milestone = pr.milestone; if (!milestone && ['opened', 'reopened', 'ready_for_review'].includes(action)) { const open = await github.paginate(github.rest.issues.listMilestones, { owner, repo, state: 'open', per_page: 100 }); open.sort((a, b) => { if (a.due_on && b.due_on) return a.due_on.localeCompare(b.due_on); if (a.due_on) return -1; if (b.due_on) return 1; return a.number - b.number; }); if (open.length === 0) { core.warning('No open milestone to assign.'); } else { milestone = open[0]; await github.rest.issues.update({ owner, repo, issue_number: pr.number, milestone: milestone.number }); core.info(`Set PR #${pr.number} milestone to ${milestone.title}.`); } } const body = (pr.body || '').replace(//g, ''); const issueRef = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?|related to):?\s+(?:([\w.-]+)\/([\w.-]+))?#(\d+)/gi; const issues = new Map(); for (const m of body.matchAll(issueRef)) { if (m[2] && (m[2].toLowerCase() !== owner.toLowerCase() || m[3].toLowerCase() !== repo.toLowerCase())) { continue; } const number = Number(m[4]); const closing = !/^related to$/i.test(m[1]); issues.set(number, issues.get(number) || closing); } for (const [number, closing] of issues) { let issue; try { ({ data: issue } = await github.rest.issues.get({ owner, repo, issue_number: number })); } catch (error) { core.warning(`Could not read #${number}: ${error.message}`); continue; } if (issue.pull_request) { continue; } if (milestone && issue.milestone?.number !== milestone.number) { if (issue.milestone && !pr.merged) { core.info(`#${number} is in ${issue.milestone.title}; it will move to ${milestone.title} when this PR merges.`); } else { await github.rest.issues.update({ owner, repo, issue_number: number, milestone: milestone.number }); core.info(`Set #${number} milestone to ${milestone.title}.`); } } if (pr.merged && closing && issue.state === 'open' && !issue.labels.some((l) => (l.name || l) === fixedLabel)) { await github.rest.issues.addLabels({ owner, repo, issue_number: number, labels: [fixedLabel] }); core.info(`Labelled #${number} "${fixedLabel}".`); } }