2121 outputs :
2222 release_created : ${{ steps.release.outputs.release_created }}
2323 tag_name : ${{ steps.release.outputs.tag_name }}
24+ release_pr_title : ${{ steps.pr.outputs.prs_created == 'true' && fromJSON(steps.pr.outputs.pr).title || '' }}
2425 steps :
2526 - name : Run release-please (releases)
2627 id : release
@@ -38,11 +39,94 @@ jobs:
3839 echo "count=${count}" >> "$GITHUB_OUTPUT"
3940
4041 - name : Run release-please (release PR)
42+ id : pr
4143 if : ${{ steps.release.outputs.release_created != 'true' && steps.drafts.outputs.count == '0' }}
4244 uses : googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
4345 with :
4446 skip-github-release : true
4547
48+ milestones :
49+ name : Milestones
50+ needs : release-please
51+ if : ${{ needs.release-please.outputs.release_pr_title != '' }}
52+ runs-on : ubuntu-latest
53+ permissions :
54+ issues : write
55+ pull-requests : write
56+ steps :
57+ - name : Ensure open milestone for upcoming release
58+ id : ensure
59+ env :
60+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
61+ REPO : ${{ github.repository }}
62+ TITLE : ${{ needs.release-please.outputs.release_pr_title }}
63+ run : |
64+ # The release-please release PR title is `chore(main): release X.Y.Z`.
65+ # Parse the version so the open milestone always tracks the next release.
66+ version=$(printf '%s' "${TITLE}" | sed -n -E 's/.*release[[:space:]]+([0-9]+\.[0-9]+\.[0-9]+).*/\1/p')
67+ if [ -z "${version}" ]; then
68+ echo "::warning::Could not parse a version from the release-please PR title '${TITLE}'"
69+ exit 0
70+ fi
71+
72+ target="Release ${version}"
73+ open=$(gh api --method GET "repos/${REPO}/milestones?state=open&per_page=100" \
74+ --jq '[.[] | {number, title}]')
75+ count=$(printf '%s' "${open}" | jq 'length')
76+
77+ if [ "${count}" -eq 0 ]; then
78+ gh api --method POST "repos/${REPO}/milestones" \
79+ -f "title=${target}" \
80+ -f "description=Upcoming release; merged pull requests ship in v${version}."
81+ echo "Created open milestone '${target}'"
82+ elif [ "${count}" -eq 1 ]; then
83+ current=$(printf '%s' "${open}" | jq -r '.[0].title')
84+ if [ "${current}" != "${target}" ]; then
85+ number=$(printf '%s' "${open}" | jq -r '.[0].number')
86+ # Rename (not recreate): pull requests already attached stay attached
87+ # and still ship in the bumped version.
88+ gh api --method PATCH "repos/${REPO}/milestones/${number}" -f "title=${target}"
89+ echo "Renamed open milestone '${current}' to '${target}'"
90+ else
91+ echo "Open milestone '${target}' already exists"
92+ fi
93+ else
94+ echo "::warning::Expected at most one open milestone, found ${count}; leaving them untouched"
95+ exit 0
96+ fi
97+
98+ echo "target=${target}" >> "$GITHUB_OUTPUT"
99+
100+ - name : Attach triggering pull request(s)
101+ if : steps.ensure.outputs.target != ''
102+ env :
103+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
104+ REPO : ${{ github.repository }}
105+ SHA : ${{ github.sha }}
106+ TARGET : ${{ steps.ensure.outputs.target }}
107+ run : |
108+ # milestone.yml can lose a race with this job: it evaluates the open
109+ # milestone on `pull_request: closed`, before release-please has opened
110+ # the next release PR and this job has created the milestone. Resolve the
111+ # pull request(s) for the push tip and attach any that shipped without one,
112+ # so they are neither missing from the cycle's milestone nor the notify job.
113+ prs=$(gh api "repos/${REPO}/commits/${SHA}/pulls" \
114+ --jq '.[] | select(.merged_at != null) | [.number, (.milestone.title // "")] | @tsv')
115+ if [ -z "${prs}" ]; then
116+ echo "No merged pull request associated with ${SHA}; nothing to attach"
117+ exit 0
118+ fi
119+
120+ while IFS=$'\t' read -r number current; do
121+ if [ -z "${number}" ]; then continue; fi
122+ if [ "${current}" = "${TARGET}" ]; then
123+ echo "Pull request #${number} already on '${TARGET}'; skipping"
124+ continue
125+ fi
126+ gh pr edit "${number}" --repo "${REPO}" --milestone "${TARGET}"
127+ echo "Attached pull request #${number} to '${TARGET}'"
128+ done <<< "${prs}"
129+
46130 lint :
47131 name : Lint App
48132 uses : ./.github/workflows/lint.yml
66150 needs : [release-please, tests]
67151 permissions :
68152 contents : write
153+ issues : write
154+ pull-requests : write
69155 with :
70156 tag_name : ${{ needs.release-please.outputs.tag_name }}
71157 secrets :
0 commit comments