diff --git a/.github/actions/check-release/action.yml b/.github/actions/check-release/action.yml new file mode 100644 index 00000000000..6718629d1f0 --- /dev/null +++ b/.github/actions/check-release/action.yml @@ -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" + 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 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a54afc89eca..fcdc910e89a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 }} @@ -62,6 +63,59 @@ jobs: needs: check-workflows uses: ./.github/workflows/lint-build-test.yml + check-release: + 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 + 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 }} + 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 @@ -99,6 +153,7 @@ jobs: runs-on: ubuntu-latest needs: - analyse-code + - check-release - lint-build-test outputs: passed: ${{ steps.set-output.outputs.passed }}