From a2d1fdb0b13e6731ab6024649ccd134167c4d626 Mon Sep 17 00:00:00 2001 From: yiliang114 Date: Mon, 27 Jul 2026 14:57:37 +0800 Subject: [PATCH 1/3] ci(release): comment released-in version on merged PRs After a stable release is published, comment on each merged PR in the release range with a link to the release tag, so contributors can find which version shipped their change directly from the PR timeline. Collects PR numbers from squash-merge commit subjects between the previous and current stable tags, posts a marker-tagged comment, and skips PRs that already carry the marker (re-run safety). Nightly and preview releases are out of scope, since finalize only handles stable tags. --- .github/workflows/finalize-release.yml | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/finalize-release.yml b/.github/workflows/finalize-release.yml index 184320819c8..45344d11ce2 100644 --- a/.github/workflows/finalize-release.yml +++ b/.github/workflows/finalize-release.yml @@ -142,6 +142,41 @@ jobs: echo "::warning::AI release notes were not generated; keeping GitHub-generated notes." fi + - name: 'Comment released-in version on merged PRs' + if: |- + ${{ steps.meta.outputs.is_stable == 'true' }} + continue-on-error: true + env: + GITHUB_TOKEN: '${{ secrets.CI_BOT_PAT }}' + PREVIOUS_TAG: '${{ steps.previous.outputs.tag }}' + RELEASE_TAG: '${{ env.RELEASE_TAG }}' + run: |- + set -euo pipefail + # Squash-merge commit subjects carry the PR as "(#NNN)"; collect those between the two tags. + # The trailing "|| true" keeps "no matches" (empty range) from failing under pipefail. + pr_numbers="$(git log --pretty=format:'%s' "${PREVIOUS_TAG}..${RELEASE_TAG}" | grep -oE '#[0-9]+' | tr -d '#' | sort -un || true)" + if [[ -z "${pr_numbers}" ]]; then + echo "No PR references found between ${PREVIOUS_TAG} and ${RELEASE_TAG}." + exit 0 + fi + echo "Found PRs between ${PREVIOUS_TAG} and ${RELEASE_TAG}:" + echo "${pr_numbers}" + marker='' + release_url="https://github.com/${GITHUB_REPOSITORY}/releases/tag/${RELEASE_TAG}" + while IFS= read -r num; do + if [[ -z "${num}" ]]; then + continue + fi + # Re-run safety: skip PRs that already carry this release marker. + existing="$(gh pr view "${num}" --json comments --jq '.comments[].body' 2>/dev/null || true)" + if grep -qF "${marker}" <<<"${existing}"; then + echo "PR #${num} already has a release comment; skipping." + continue + fi + body="${marker}"$'\n'"Released in [${RELEASE_TAG}](${release_url})." + gh pr comment "${num}" --body "${body}" && echo "Commented on PR #${num}." || echo "::warning::Failed to comment on PR #${num}." + done <<< "${pr_numbers}" + - name: 'Regenerate CHANGELOG.md' if: |- ${{ steps.meta.outputs.is_stable == 'true' }} From e4b2c0188a41ab5e8cda2c7a4b85eea46ad7af4e Mon Sep 17 00:00:00 2001 From: yiliang114 Date: Mon, 27 Jul 2026 18:46:08 +0800 Subject: [PATCH 2/3] fix(ci): narrow release PR extraction --- .github/workflows/finalize-release.yml | 2 +- scripts/tests/ai-release-notes-workflow.test.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/finalize-release.yml b/.github/workflows/finalize-release.yml index 45344d11ce2..123d935ef06 100644 --- a/.github/workflows/finalize-release.yml +++ b/.github/workflows/finalize-release.yml @@ -154,7 +154,7 @@ jobs: set -euo pipefail # Squash-merge commit subjects carry the PR as "(#NNN)"; collect those between the two tags. # The trailing "|| true" keeps "no matches" (empty range) from failing under pipefail. - pr_numbers="$(git log --pretty=format:'%s' "${PREVIOUS_TAG}..${RELEASE_TAG}" | grep -oE '#[0-9]+' | tr -d '#' | sort -un || true)" + pr_numbers="$(git log --pretty=format:'%s' "${PREVIOUS_TAG}..${RELEASE_TAG}" | grep -oE '\(#[0-9]+\)$' | tr -d '()#' | sort -un || true)" if [[ -z "${pr_numbers}" ]]; then echo "No PR references found between ${PREVIOUS_TAG} and ${RELEASE_TAG}." exit 0 diff --git a/scripts/tests/ai-release-notes-workflow.test.js b/scripts/tests/ai-release-notes-workflow.test.js index 1a87ce7647f..696c33f5f4d 100644 --- a/scripts/tests/ai-release-notes-workflow.test.js +++ b/scripts/tests/ai-release-notes-workflow.test.js @@ -85,6 +85,20 @@ describe('stable release notes workflow', () => { ); }); + it('comments released-in version only for squash-merge PR trailers', () => { + const step = getStep( + finalizeWorkflow, + 'Comment released-in version on merged PRs', + ); + + expect(step).toContain("grep -oE '\\(#[0-9]+\\)$'"); + expect(step).toContain("tr -d '()#'"); + expect(step).not.toContain("grep -oE '#[0-9]+'"); + expect(step).toContain("marker=''"); + expect(step).toContain('gh pr view "${num}" --json comments'); + expect(step).toContain('gh pr comment "${num}" --body "${body}"'); + }); + it('does not recreate an already merged release PR during retries', () => { const pr = getStep( finalizeWorkflow, From e6113d651e78ba9a6446fd79a01174e6eb40696e Mon Sep 17 00:00:00 2001 From: yiliang114 Date: Mon, 27 Jul 2026 20:37:17 +0800 Subject: [PATCH 3/3] test(ci): cover release comment safety checks --- scripts/tests/ai-release-notes-workflow.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/tests/ai-release-notes-workflow.test.js b/scripts/tests/ai-release-notes-workflow.test.js index 696c33f5f4d..e37c424317e 100644 --- a/scripts/tests/ai-release-notes-workflow.test.js +++ b/scripts/tests/ai-release-notes-workflow.test.js @@ -91,11 +91,13 @@ describe('stable release notes workflow', () => { 'Comment released-in version on merged PRs', ); + expect(step).toContain('continue-on-error: true'); expect(step).toContain("grep -oE '\\(#[0-9]+\\)$'"); expect(step).toContain("tr -d '()#'"); expect(step).not.toContain("grep -oE '#[0-9]+'"); expect(step).toContain("marker=''"); expect(step).toContain('gh pr view "${num}" --json comments'); + expect(step).toContain('grep -qF "${marker}" <<<"${existing}"'); expect(step).toContain('gh pr comment "${num}" --body "${body}"'); });