Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/actions/check-release/action.yml
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an action rather than a workflow to support the uses syntax in a step, rather than as a job. It's related to this comment.

Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Check release
description: Check for conflicts in packages being released in this PR.

inputs:
pull-request:
description: 'The pull request number to get changed files from.'
required: true

runs:
using: composite
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Get target reference
id: get-target
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
run: |
if [ "$EVENT_NAME" = "pull_request" ]; then
echo "TARGET=$(git merge-base HEAD refs/remotes/origin/main)" >> "$GITHUB_OUTPUT"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Wrong Target Prevents PR Conflict Detection

For pull_request events, TARGET is set to the merge-base of HEAD and main, but it should be the current state of main itself. Since BEFORE (line 64) is also a merge-base, the diff comparison at line 65 will compare two merge-bases that are likely identical, resulting in an empty diff that fails to detect conflicts with changes that landed on main after the PR was created.

Fix in Cursor Fix in Web

elif [ "$EVENT_NAME" = "merge_group" ]; then
echo "TARGET=$(git rev-parse HEAD^)" >> "$GITHUB_OUTPUT"
else
echo "::error::This action only supports `pull_request` and `merge_group` events."
exit 1
fi
- name: Check commits for changes in released packages
shell: bash
env:
GH_TOKEN: ${{ github.token }}
PULL_REQUEST: ${{ inputs.pull-request }}
TARGET: ${{ steps.get-target.outputs.TARGET }}
run: |
set -euo pipefail
git fetch origin main
mapfile -t PACKAGES < <(find packages -maxdepth 2 -name "package.json" -not -path "*/node_modules/*")
RELEASED_PACKAGES=()
# Get all packages being released in this PR
MERGE_BASE=$(git merge-base HEAD refs/remotes/origin/main)
for package in "${PACKAGES[@]}"; do
MAIN_VERSION=$(git show "$MERGE_BASE:$package" | jq -r .version)
HEAD_VERSION=$(jq -r .version "$package")
if [ "$HEAD_VERSION" != "$MAIN_VERSION" ]; then
package_name=$(jq -r ".name" "$package")
echo "📦 Package \`$package_name\` is being released (version \`$MAIN_VERSION\` -> \`$HEAD_VERSION\`)"
RELEASED_PACKAGES+=("$package")
fi
done
# Fetch the pull request branch to compare changes.
PULL_REQUEST_BRANCH=$(gh pr view "$PULL_REQUEST" --json headRefName --template "{{ .headRefName }}")
echo "🔍 Checking for release conflicts with files changed ahead of PR branch \`$PULL_REQUEST_BRANCH\`..."
git fetch origin "$PULL_REQUEST_BRANCH"
# Get all files changed ahead of this PR.
BEFORE=$(git merge-base "refs/remotes/origin/main" "origin/$PULL_REQUEST_BRANCH")
git diff --name-only "$BEFORE..$TARGET" > changed-files.txt
CONFLICTS=()
for package in "${RELEASED_PACKAGES[@]}"; do
package_directory=$(dirname "$package")
if grep -q "^$package_directory/" changed-files.txt; then
CONFLICTS+=("$package_directory")
fi
done
if [ ${#CONFLICTS[@]} -ne 0 ]; then
mapfile -t CONFLICTS < <(printf "%s\n" "${CONFLICTS[@]}" | sort -u)
fi
if [ ${#CONFLICTS[@]} -ne 0 ]; then
for conflict in "${CONFLICTS[@]}"; do
package_name=$(jq -r ".name" "$conflict/package.json")
echo "::error::Release conflict detected in \`$package_name\`. This package is being released in this PR, but files in the package were also modified ahead of this PR. Please ensure that all changes are included in the release."
done
exit 1
else
echo "✅ No release conflicts detected."
fi
55 changes: 55 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches: [main]
pull_request:
merge_group:

concurrency:
group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.sha || github.ref }}
Expand Down Expand Up @@ -62,6 +63,59 @@ jobs:
needs: check-workflows
uses: ./.github/workflows/lint-build-test.yml

check-release:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't add a conditional here, but only to the step below. This way we can add check-release as dependency to all-jobs-complete. Otherwise, if check-release (the job) is skipped, all-jobs-complete would be skipped too.

name: Check release
needs: check-workflows
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Get merge base
id: merge-base
if: github.event_name != 'push'
env:
BASE_REF: ${{ github.event.pull_request.base.ref || github.event.merge_group.base_ref }}
run: |
echo "MERGE_BASE=$(git merge-base HEAD "refs/remotes/origin/$BASE_REF")" >> "$GITHUB_OUTPUT"

- name: Check if the commit or pull request is a release
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered reusing the is-release job below, but it adds a little bit of complexity around the conditionals, and we don't need the extra options below for merged commits.

Copy link
Member

@Gudahtt Gudahtt Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We could maybe skip this for push events? Just because it wouldn't be relevant anymore for push, and this might make it a bit more clear why we have to similarly-named jobs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could skip the individual steps on push, but not the job itself, since that would cause all-jobs-complete to be skipped.

id: is-release
if: github.event_name != 'push'
uses: MetaMask/action-is-release@d063725cd15ee145d7e795a2e77db31a3e3f2c92
with:
commit-starts-with: 'Release [version],Release v[version],Release/[version],Release/v[version],Release `[version]`'
commit-message: ${{ github.event.pull_request.title }}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used so we can determine if an unmerged pull request is a release pull request. github.event.pull_request.title is not defined for other events (push and merge_group), so doesn't affect those.

before: ${{ steps.merge-base.outputs.MERGE_BASE }}

- name: Get pull request number
if: github.event_name != 'push'
id: pr-number
env:
EVENT_NAME: ${{ github.event_name }}
PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
MERGE_GROUP_HEAD_REF: ${{ github.event.merge_group.head_ref }}
run: |
if [ "$EVENT_NAME" = "pull_request" ]; then
echo "PR_NUMBER=$PULL_REQUEST_NUMBER" >> "$GITHUB_OUTPUT"
elif [ "$EVENT_NAME" = "merge_group" ]; then
PR_NUMBER_REGEX='/pr-([0-9]+)-'
if [[ "$MERGE_GROUP_HEAD_REF" =~ $PR_NUMBER_REGEX ]]; then
echo "PR_NUMBER=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT"
else
echo "::error::Could not extract PR number from merge group head ref: $MERGE_GROUP_HEAD_REF."
exit 1
fi
fi

- name: Check release
if: github.event_name != 'push' && steps.is-release.outputs.IS_RELEASE == 'true'
uses: ./.github/actions/check-release
with:
pull-request: ${{ steps.pr-number.outputs.PR_NUMBER }}

is-release:
name: Determine whether this is a release merge commit
needs: lint-build-test
Expand Down Expand Up @@ -99,6 +153,7 @@ jobs:
runs-on: ubuntu-latest
needs:
- analyse-code
- check-release
- lint-build-test
outputs:
passed: ${{ steps.set-output.outputs.passed }}
Expand Down
Loading