diff --git a/.github/workflows/ensure-peer-deps-review.yml b/.github/workflows/ensure-peer-deps-review.yml new file mode 100644 index 00000000000..66121e166aa --- /dev/null +++ b/.github/workflows/ensure-peer-deps-review.yml @@ -0,0 +1,27 @@ +# This check runs only on the release PR created by changesets. +# It requires that a label is added to ensure that peer dependency versions have been manually reviewed. + +name: Check peer dependency versions have been reviewed + +on: + pull_request: + types: + - synchronize + - labeled + - unlabeled + - edited + - ready_for_review + +jobs: + check-label: + if: github.head_ref == 'changeset-release/main' + name: Check peer dependency versions have been reviewed + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v5 + + - name: Check for the label presence + env: + GITHUB_EVENT_PULL_REQUEST_LABELS: ${{ toJson(github.event.pull_request.labels) }} + run: node scripts/validate-peer-dependencies-reviewed-label.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d97a0a58d4d..324d8265ce0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,6 +50,51 @@ jobs: echo "hasChangesets=${{steps.pr.outputs.hasChangesets}}" echo "hasChangesets=${{steps.pr.outputs.hasChangesets}}" >> $GITHUB_OUTPUT + # The following two steps are to mark the changesets PR as draft + # This is so a user must mark it as ready for review, which will trigger workflows on this PR + # Otherwise workflows won't run since the PR is created by an action itself + + - name: Find package versions PR + if: steps.pr.outputs.hasChangesets == 'true' + uses: actions/github-script@v7 + id: find + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const head = `${owner}:changeset-release/main`; + + const { data: prs } = await github.rest.pulls.list({ owner, repo, state: "open", head }); + if (!prs.length) core.setFailed(`No open PR from ${head}`); + return { number: prs[0].number }; + + - name: Convert to draft + if: steps.pr.outputs.hasChangesets == 'true' + uses: actions/github-script@v7 + with: + script: | + const { number } = ${{ steps.find.outputs.result }}; + // 1) Get node ID for GraphQL + const query = ` + query($owner:String!, $repo:String!, $number:Int!) { + repository(owner:$owner, name:$repo) { + pullRequest(number:$number) { id, isDraft, number } + } + }`; + const vars = { owner: context.repo.owner, repo: context.repo.repo, number: Number(number) }; + const qres = await github.graphql(query, vars); + const pr = qres.repository.pullRequest; + if (pr.isDraft) return { already: true }; + + // 2) Convert to draft + const mutation = ` + mutation($prId:ID!) { + convertPullRequestToDraft(input:{pullRequestId:$prId}) { + pullRequest { number, isDraft } + } + }`; + await github.graphql(mutation, { prId: pr.id }); + pack: name: Generate tarballs for publishing needs: pr diff --git a/scripts/validate-peer-dependencies-reviewed-label.mjs b/scripts/validate-peer-dependencies-reviewed-label.mjs new file mode 100644 index 00000000000..f52df7ccb27 --- /dev/null +++ b/scripts/validate-peer-dependencies-reviewed-label.mjs @@ -0,0 +1,31 @@ +// @ts-check + +/** + * This checks that the release PR has the "peer dependencies reviewed" label, + * to enforce the manual check on version specifiers before a release. + */ + +const REVIEWED_LABEL = "peer dependencies reviewed"; + +function hasReviewedLabel() { + if (process.env.GITHUB_EVENT_PULL_REQUEST_LABELS === undefined) { + throw new Error("GITHUB_EVENT_PULL_REQUEST_LABELS is not defined"); + } + + const labels = JSON.parse(process.env.GITHUB_EVENT_PULL_REQUEST_LABELS); + + return labels.some((l) => l.name === REVIEWED_LABEL); +} + +async function validatePullRequest() { + if (hasReviewedLabel()) { + console.log(`The PR is labeled as '${REVIEWED_LABEL}'`); + return; + } + + throw new Error( + `Peer dependencies are not reviewed. Add the '${REVIEWED_LABEL}' label to this PR once peer dependency version ranges have been manually reviewed.` + ); +} + +await validatePullRequest();