From b27a28de4f11142af0e238e5edbf8511e99d2d75 Mon Sep 17 00:00:00 2001 From: Matsu Date: Tue, 10 Mar 2026 14:59:03 +0200 Subject: [PATCH] ci: Workflow to create Patch release for track (#26824) --- ...ine-release-candidate-branch-for-track.mjs | 39 ++++++++++++++ .github/scripts/github-helpers.mjs | 27 +++++++++- .github/workflows/release-create-patch-pr.yml | 54 +++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 .github/scripts/determine-release-candidate-branch-for-track.mjs create mode 100644 .github/workflows/release-create-patch-pr.yml diff --git a/.github/scripts/determine-release-candidate-branch-for-track.mjs b/.github/scripts/determine-release-candidate-branch-for-track.mjs new file mode 100644 index 00000000000..e903467931e --- /dev/null +++ b/.github/scripts/determine-release-candidate-branch-for-track.mjs @@ -0,0 +1,39 @@ +import { + countCommitsBetweenRefs, + ensureEnvVar, + listCommitsBetweenRefs, + resolveRcBranchForTrack, + resolveReleaseTagForTrack, + writeGithubOutput, +} from './github-helpers.mjs'; + +function main() { + const track = /** @type { import('./github-helpers.mjs').ReleaseTrack } */ ( + ensureEnvVar('TRACK') + ); + + const currentTag = resolveReleaseTagForTrack(track); + + const releaseCandidateBranch = resolveRcBranchForTrack(track); + + if (!currentTag?.tag || !releaseCandidateBranch) { + throw new Error( + `Couldn't resolve needed parameters. currentTag.tag=${currentTag?.tag}, releaseCandidateBranch=${releaseCandidateBranch}`, + ); + } + + console.log(`Commits between ${releaseCandidateBranch} and ${currentTag.tag}:`); + console.log(listCommitsBetweenRefs(releaseCandidateBranch, currentTag.tag)); + + const commitCount = countCommitsBetweenRefs(releaseCandidateBranch, currentTag.tag); + + writeGithubOutput({ + release_candidate_branch: releaseCandidateBranch, + should_update: commitCount > 0 ? 'true' : 'false', + }); +} + +// only run when executed directly, not when imported by tests +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/.github/scripts/github-helpers.mjs b/.github/scripts/github-helpers.mjs index 9efb1009d1a..f9f55111d45 100644 --- a/.github/scripts/github-helpers.mjs +++ b/.github/scripts/github-helpers.mjs @@ -68,7 +68,7 @@ export function ensureReleaseTrack(track) { * * @param { typeof RELEASE_TRACKS[number] } track * - * @returns { TagVersionInfo } + * @returns { TagVersionInfo | null } * */ export function resolveReleaseTagForTrack(track) { const commit = getCommitForRef(track); @@ -91,9 +91,13 @@ export function resolveReleaseTagForTrack(track) { * * Returns null if the track tag or release tag is missing. * - * @param { typeof RELEASE_TRACKS[number] } track + * @param { ReleaseTrack } track * */ export function resolveRcBranchForTrack(track) { + if (track === 'v1') { + return '1.x'; + } + const commit = getCommitForRef(track); if (!commit) return null; @@ -241,6 +245,25 @@ export function listTagsPointingAt(commit) { .filter(Boolean); } +/** + * @param {string} from + * @param {string} to + */ +export function listCommitsBetweenRefs(from, to) { + return sh('git', ['--no-pager', 'log', '--format="- %s (%h)', `${to}..origin/${from}`]); +} + +/** + * @param {string} from + * @param {string} to + */ +export function countCommitsBetweenRefs(from, to) { + const output = sh('git', ['rev-list', '--count', `${to}..origin/${from}`]); + const count = parseInt(output); + + return isNaN(count) ? 0 : count; +} + /** * @param {string} branch */ diff --git a/.github/workflows/release-create-patch-pr.yml b/.github/workflows/release-create-patch-pr.yml new file mode 100644 index 00000000000..627f02ac3c6 --- /dev/null +++ b/.github/workflows/release-create-patch-pr.yml @@ -0,0 +1,54 @@ +name: 'Release: Create Patch Release PR' + +on: + workflow_dispatch: + inputs: + track: + description: 'Release Track' + required: true + type: choice + options: [stable, beta, v1] + +jobs: + determine-version-info: + name: Determine publishing track + runs-on: ubuntu-latest + outputs: + release_candidate_branch: ${{ steps.determine-branch.outputs.release_candidate_branch }} + should_update: ${{ steps.determine-branch.outputs.should_update }} + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: ./.github/actions/setup-nodejs + with: + build-command: '' + install-command: npm install --prefix=.github/scripts --no-package-lock + + - name: Determine release candidate branch from track + id: determine-branch + env: + TRACK: ${{ inputs.track }} + run: node .github/scripts/determine-release-candidate-branch-for-track.mjs + + skip-release-pr: + name: Skip release PR (no new commits) + needs: [determine-version-info] + if: needs.determine-version-info.outputs.should_update != 'true' + runs-on: ubuntu-latest + steps: + - name: Log skip reason + run: echo "No new commits found between the release candidate branch and the current release tag. Skipping PR creation." + + create-release-pr: + name: Create release PR + needs: [determine-version-info] + if: needs.determine-version-info.outputs.should_update == 'true' + uses: ./.github/workflows/release-create-pr.yml + secrets: inherit + with: + base-branch: ${{ needs.determine-version-info.outputs.release_candidate_branch }} + release-type: patch