fix(TMCU-1045): Remove useElevatedSurface shim from Core UX bottom sheets #159340
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: ci | |
| run-name: >- | |
| ${{ inputs.pr_number && format('ci [shadow PR #{0} @ {1}]', inputs.pr_number, inputs.head_sha) || '' }} | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| types: | |
| - opened | |
| - reopened | |
| - synchronize | |
| merge_group: | |
| schedule: | |
| # Run the full suite "overnight," once every hour from 2:00am UTC until 6:00am UTC. | |
| # This helps to identy the flaky and failed tests on main branch | |
| - cron: '0 2-6 * * *' | |
| workflow_call: | |
| inputs: | |
| runner_provider: | |
| type: string | |
| required: false | |
| default: current | |
| pr_number: | |
| description: 'PR number (shadow correlation, optional)' | |
| type: string | |
| required: false | |
| head_sha: | |
| description: 'PR head SHA (shadow correlation, optional)' | |
| type: string | |
| required: false | |
| workflow_dispatch: | |
| inputs: | |
| runner_provider: | |
| description: Runner provider for this manual trial run | |
| required: true | |
| type: choice | |
| options: | |
| - current | |
| - namespace | |
| - bitrise | |
| default: current | |
| pr_number: | |
| description: 'PR number (shadow correlation, optional)' | |
| required: false | |
| type: string | |
| head_sha: | |
| description: 'PR head SHA (shadow correlation, optional)' | |
| required: false | |
| type: string | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.sha || github.ref }} | |
| cancel-in-progress: ${{ !(contains(github.ref, 'refs/heads/main') || contains(github.ref, 'refs/heads/stable')) }} | |
| jobs: | |
| get_requirements: | |
| name: Get workflow and job requirements | |
| uses: ./.github/workflows/get-requirements.yml | |
| check-diff: | |
| name: Check diff | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ios-build' || 'macos-latest' }} | |
| if: ${{ needs.get_requirements.outputs.skip_everything != 'true' }} | |
| needs: | |
| - get_requirements | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: yarn | |
| - uses: ruby/setup-ruby@44511735964dcb71245e7e55f72539531f7bc0eb #v1 | |
| with: | |
| ruby-version: '3.2.9' | |
| env: | |
| BUNDLE_GEMFILE: ios/Gemfile | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Restore .metamask folder | |
| id: restore-metamask | |
| uses: actions/cache@v4 | |
| with: | |
| path: .metamask | |
| key: .metamask-${{ hashFiles('package.json', 'yarn.lock') }} | |
| - name: Install Foundry if cache missed | |
| if: steps.restore-metamask.outputs.cache-hit != 'true' | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install:foundryup | |
| - name: Clean state and following up dependencies installation with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 20 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn setup:github-ci | |
| - name: Require clean working directory | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Working tree dirty at end of job" | |
| exit 1 | |
| else | |
| echo "No changes detected" | |
| fi | |
| native-build-fingerprint: | |
| name: Compute native build fingerprint | |
| runs-on: ubuntu-latest | |
| if: ${{ needs.get_requirements.outputs.skip_everything != 'true' }} | |
| permissions: | |
| contents: read | |
| statuses: write | |
| needs: | |
| - get_requirements | |
| outputs: | |
| fingerprint: ${{ steps.publish.outputs.fingerprint }} | |
| steps: | |
| # Use the default ref (refs/pull/N/merge on pull_request, github.sha otherwise) so the | |
| # fingerprint is computed against the SAME working tree that `build-android-e2e.yml`, | |
| # `build-ios-e2e.yml`, and `run-e2e-workflow.yml` check out and build/test against. | |
| # Posting on `pull_request.head.sha` while fingerprinting the PR head tree would let | |
| # two PRs with identical heads but different merges collide on the cache key — and the | |
| # native build then runs against a different (merge) tree than the one we fingerprinted. | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: yarn | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Compute native build fingerprint and post build-source-hash | |
| id: publish | |
| uses: ./.github/actions/post-build-source-hash | |
| with: | |
| github-token: ${{ github.token }} | |
| post-status: ${{ inputs.runner_provider != 'namespace' && inputs.runner_provider != 'bitrise' }} | |
| # Commit statuses must be posted on a real commit SHA (not the merge ref). | |
| # `find-reusable-build` looks up the status via `run.head_sha` from | |
| # `listWorkflowRuns`, which GitHub reports as the PR head SHA for pull_request | |
| # events and as the pushed/scheduled commit SHA otherwise — so this is the same | |
| # SHA the lookup queries against. | |
| target-sha: ${{ github.event.pull_request.head.sha || github.sha }} | |
| dedupe: | |
| name: Dedupe | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ needs.get_requirements.outputs.skip_everything != 'true' && inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' }} | |
| needs: | |
| - get_requirements | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| - name: Configure Namespace cache | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1 | |
| with: | |
| path: | | |
| ~/.cache/yarn | |
| .metamask | |
| node_modules | |
| .yarn/cache | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: ${{ inputs.runner_provider != 'namespace' && 'yarn' || '' }} | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --node | |
| - name: Deduplicate dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn deduplicate | |
| - name: Print error if duplicates found | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Duplicate dependencies detected; run 'yarn deduplicate' to remove them" | |
| exit 1 | |
| fi | |
| git-safe-dependencies: | |
| name: Run `@lavamoat/git-safe-dependencies` | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ needs.get_requirements.outputs.skip_everything != 'true' && inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' }} | |
| needs: | |
| - get_requirements | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| - name: Configure Namespace cache | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1 | |
| with: | |
| path: | | |
| ~/.cache/yarn | |
| .metamask | |
| node_modules | |
| .yarn/cache | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: ${{ inputs.runner_provider != 'namespace' && 'yarn' || '' }} | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --node | |
| - name: Run @lavamoat/git-safe-dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn git-safe-dependencies | |
| scripts: | |
| name: Run `${{ matrix.scripts }}` | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ needs.get_requirements.outputs.skip_everything != 'true' && inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' }} | |
| needs: | |
| - get_requirements | |
| strategy: | |
| matrix: | |
| scripts: | |
| - lint | |
| - lint:tsc | |
| - format:check | |
| - audit:ci | |
| - test:depcheck | |
| - test:tgz-check | |
| - constraints | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| with: | |
| fetch-depth: 2 | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| with: | |
| fetch-depth: 2 | |
| - name: Configure Namespace cache | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1 | |
| with: | |
| path: | | |
| ~/.cache/yarn | |
| .metamask | |
| node_modules | |
| .yarn/cache | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: ${{ inputs.runner_provider != 'namespace' && 'yarn' || '' }} | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --node | |
| - run: yarn ${{ matrix['scripts'] }} | |
| - name: Require clean working directory | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Working tree dirty at end of job" | |
| exit 1 | |
| else | |
| echo "No changes detected" | |
| fi | |
| js-bundle-size-check: | |
| name: JS bundle size check | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ needs.get_requirements.outputs.skip_everything != 'true' && inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' }} | |
| needs: | |
| - get_requirements | |
| permissions: | |
| contents: read | |
| statuses: write | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| with: | |
| fetch-depth: 2 | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| with: | |
| fetch-depth: 2 | |
| - name: Pin bundle size baseline SHA | |
| if: github.event_name == 'pull_request' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| BASE_REF='${{ github.event.pull_request.base.ref }}' | |
| git fetch origin "${BASE_REF}" --depth=1 | |
| if ! MERGE_BASE_SHA="$(git merge-base "origin/${BASE_REF}" HEAD)"; then | |
| echo "::warning::Could not resolve merge-base between HEAD and origin/${BASE_REF}; bundle size delta check will be skipped." | |
| echo "MERGE_BASE_SHA=" >> "$GITHUB_ENV" | |
| else | |
| echo "Merge-base pinned (origin/${BASE_REF}, HEAD): ${MERGE_BASE_SHA}" | |
| echo "MERGE_BASE_SHA=${MERGE_BASE_SHA}" >> "$GITHUB_ENV" | |
| fi | |
| echo "BUNDLE_SIZE_BASE_REF=${BASE_REF}" >> "$GITHUB_ENV" | |
| - name: Configure Namespace cache | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1 | |
| with: | |
| path: | | |
| ~/.cache/yarn | |
| .metamask | |
| node_modules | |
| .yarn/cache | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: ${{ inputs.runner_provider != 'namespace' && 'yarn' || '' }} | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --no-build-android | |
| - name: Generate iOS bundle | |
| run: yarn gen-bundle:ios | |
| env: | |
| NODE_OPTIONS: --max_old_space_size=12288 | |
| - name: Check bundle size | |
| if: github.event_name == 'pull_request' | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Baseline: ci/ios-js-bundle-size on merge-base pinned at checkout (before bundle build). | |
| append_job_summary() { | |
| if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then | |
| cat >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| } | |
| FILE_PATH='ios/main.jsbundle' | |
| CURRENT_MB="$(stat -c %s "$FILE_PATH" | awk '{print $1/1024/1024}')" | |
| echo "Current iOS JS bundle: ${CURRENT_MB} MB" | |
| BASELINE_MB="" | |
| BASE_REF="${BUNDLE_SIZE_BASE_REF}" | |
| MERGE_BASE_SHA="${MERGE_BASE_SHA:-}" | |
| REPO_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" | |
| if [[ -z "${MERGE_BASE_SHA}" ]]; then | |
| echo "::warning::Merge-base was not pinned at checkout; skipping iOS JS bundle delta check." | |
| append_job_summary <<EOF | |
| ### iOS JS bundle size (PR) | |
| **Result:** skipped — merge-base was not available from the checkout step. | |
| | | MiB | | |
| |---|--:| | |
| | Current bundle | ${CURRENT_MB} | | |
| Baseline would be read from the latest successful \`ci/ios-js-bundle-size\` status on the merge-base of HEAD and \`${BASE_REF}\`. | |
| EOF | |
| exit 0 | |
| fi | |
| echo "Merge-base (origin/${BASE_REF}, HEAD): ${MERGE_BASE_SHA}" | |
| if ! STATUSES_JSON="$( | |
| curl -fsS \ | |
| -H "Authorization: Bearer ${GITHUB_TOKEN}" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/commits/${MERGE_BASE_SHA}/status" | |
| )"; then | |
| echo "::warning::Failed to fetch commit statuses for merge-base ${MERGE_BASE_SHA}; skipping iOS JS bundle delta check." | |
| append_job_summary <<EOF | |
| ### iOS JS bundle size (PR) | |
| **Result:** skipped — could not load [combined status](${REPO_URL}/commit/${MERGE_BASE_SHA}) for merge-base \`${MERGE_BASE_SHA}\` (\`${BASE_REF}\`). | |
| | | MiB | | |
| |---|--:| | |
| | Current bundle | ${CURRENT_MB} | | |
| Baseline would be read from the latest successful \`ci/ios-js-bundle-size\` status description on that commit (MiB from awk, then an optional trailing semicolon, matching the main-branch post step). | |
| EOF | |
| exit 0 | |
| fi | |
| DESC="$( | |
| echo "$STATUSES_JSON" | jq -r ' | |
| (.statuses // []) | |
| | map(select(.context == "ci/ios-js-bundle-size" and .state == "success")) | |
| | sort_by(.updated_at // .created_at) | |
| | if length > 0 then .[-1].description else "" end | |
| ' | |
| )" | |
| # Status description is "${FILE_SIZE_MB};" from the Post iOS JS bundle size step (awk MiB + ';'). | |
| # Regex in a variable: ';' in an unquoted [[ =~ ... ]] pattern is parsed as a command separator (shellcheck SC1072). | |
| _bundle_desc_re='^[[:space:]]*([0-9]+(\.[0-9]*)?)[[:space:]]*;?[[:space:]]*$' | |
| if [[ "$DESC" =~ $_bundle_desc_re ]]; then | |
| BASELINE_MB="${BASH_REMATCH[1]}" | |
| echo "Baseline from merge-base status: ${BASELINE_MB} MB" | |
| else | |
| echo "::warning::No successful ci/ios-js-bundle-size status (or unrecognized description) on merge-base ${MERGE_BASE_SHA}; skipping iOS JS bundle delta check." | |
| append_job_summary <<EOF | |
| ### iOS JS bundle size (PR) | |
| **Result:** skipped — no parseable \`ci/ios-js-bundle-size\` success status on merge-base [\`${MERGE_BASE_SHA}\`](${REPO_URL}/commit/${MERGE_BASE_SHA}) (\`${BASE_REF}\`). | |
| | | MiB | | |
| |---|--:| | |
| | Current bundle | ${CURRENT_MB} | | |
| | Raw description | \`${DESC:-<empty>}\` | | |
| EOF | |
| exit 0 | |
| fi | |
| if [[ -z "${BASELINE_MB:-}" ]]; then | |
| echo "::warning::Baseline is undefined; skipping iOS JS bundle delta check." | |
| append_job_summary <<EOF | |
| ### iOS JS bundle size (PR) | |
| **Result:** skipped — baseline MiB was empty after parsing. | |
| | | MiB | | |
| |---|--:| | |
| | Current bundle | ${CURRENT_MB} | | |
| EOF | |
| exit 0 | |
| fi | |
| DELTA_MB="$(echo "${CURRENT_MB} - ${BASELINE_MB}" | bc -l)" | |
| echo "Delta (current - baseline): ${DELTA_MB} MB" | |
| echo "::notice::iOS JS bundle delta vs baseline: ${DELTA_MB} MB (current ${CURRENT_MB} MB, baseline ${BASELINE_MB} MB)" | |
| if (( $(echo "${DELTA_MB} > 1" | bc -l) )); then | |
| echo "::error::JS bundle exceeds baseline by more than 1 MiB (delta ${DELTA_MB} MiB; current ${CURRENT_MB} MiB, baseline ${BASELINE_MB} MiB)." | |
| append_job_summary <<EOF | |
| ### iOS JS bundle size (PR) | |
| **Result:** failed — delta is more than 1 MiB above the status baseline on merge-base [\`${MERGE_BASE_SHA}\`](${REPO_URL}/commit/${MERGE_BASE_SHA}) (\`${BASE_REF}\`). | |
| | | MiB | | |
| |---|--:| | |
| | Merge-base (\`${BASE_REF}\` × HEAD) | [\`${MERGE_BASE_SHA}\`](${REPO_URL}/commit/${MERGE_BASE_SHA}) | | |
| | Baseline (from status) | ${BASELINE_MB} | | |
| | Current bundle | ${CURRENT_MB} | | |
| | Delta (current - baseline) | ${DELTA_MB} | | |
| EOF | |
| exit 1 | |
| fi | |
| echo "JS bundle size check passed (delta is one or less)." | |
| append_job_summary <<EOF | |
| ### iOS JS bundle size (PR) | |
| **Result:** passed | |
| | | MiB | | |
| |---|--:| | |
| | Merge-base (\`${BASE_REF}\` × HEAD) | [\`${MERGE_BASE_SHA}\`](${REPO_URL}/commit/${MERGE_BASE_SHA}) | | |
| | Baseline (from \`ci/ios-js-bundle-size\` status) | ${BASELINE_MB} | | |
| | Current bundle | ${CURRENT_MB} | | |
| | Delta (current - baseline) | ${DELTA_MB} | | |
| Fail threshold: more than 1 MiB above baseline. | |
| EOF | |
| - name: Post iOS JS bundle size to commit status | |
| if: ${{ github.ref == 'refs/heads/main' && inputs.runner_provider != 'namespace' && inputs.runner_provider != 'bitrise' }} | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| FILE_PATH='ios/main.jsbundle' | |
| # Match scripts/js-bundle-stats.sh: GNU stat byte size, MiB via awk | |
| FILE_SIZE_MB="$(stat -c %s "$FILE_PATH" | awk '{print $1/1024/1024}')" | |
| echo "File size of $FILE_PATH: $FILE_SIZE_MB MB" | |
| COMMIT_SHA='${{ github.event.pull_request.head.sha || github.sha }}' | |
| TARGET_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" | |
| BODY="$(jq -n \ | |
| --arg state success \ | |
| --arg context 'ci/ios-js-bundle-size' \ | |
| --arg desc "${FILE_SIZE_MB};" \ | |
| --arg target_url "$TARGET_URL" \ | |
| '{ | |
| state: $state, | |
| context: $context, | |
| description: ($desc | if length > 140 then .[0:140] else . end), | |
| target_url: $target_url | |
| }')" | |
| curl -fsS -X POST \ | |
| -H "Authorization: Bearer ${GITHUB_TOKEN}" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/statuses/${COMMIT_SHA}" \ | |
| -d "$BODY" | |
| - name: Upload iOS bundle (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3 | |
| with: | |
| name: ios-bundle | |
| path: ios/main.jsbundle | |
| retention-days: 7 | |
| - name: Upload iOS bundle (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ios-bundle | |
| path: ios/main.jsbundle | |
| retention-days: 7 | |
| ship-js-bundle-size-check: | |
| name: Ship JS bundle size check | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| needs: [js-bundle-size-check] | |
| # Skip on shadow runs (Namespace/Bitrise): cron against main would otherwise | |
| # push duplicate entries to the external mobile_bundlesize_stats repo. | |
| if: ${{ github.ref == 'refs/heads/main' && inputs.runner_provider != 'namespace' && inputs.runner_provider != 'bitrise' }} | |
| permissions: | |
| id-token: write | |
| contents: read | |
| actions: read | |
| steps: | |
| - name: Get token | |
| id: get-token | |
| uses: MetaMask/github-tools/.github/actions/get-token@v1 | |
| with: | |
| token-exchange-url: ${{ vars.TOKEN_EXCHANGE_URL }} | |
| target-repository: MetaMask/mobile_bundlesize_stats | |
| permissions: | | |
| contents: write | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| - name: Download iOS bundle (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/download-artifact@7cbad919e4b0e09f17e9d6311a444ff002992b5b # v2.0.1 | |
| with: | |
| name: ios-bundle | |
| path: ios | |
| - name: Download iOS bundle (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ios-bundle | |
| path: ios | |
| - name: Verify iOS bundle artifact layout | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| ls -la ios/ | |
| if [[ ! -f ios/main.jsbundle ]]; then | |
| echo "::error::Expected ios/main.jsbundle to be a regular file (artifact layout may be wrong)." | |
| exit 1 | |
| fi | |
| - name: Report iOS JS bundle size and commit (main) | |
| run: | | |
| SHORT_SHA="$(git rev-parse --short HEAD)" | |
| FILE_PATH='ios/main.jsbundle' | |
| MB="$(stat -c %s "$FILE_PATH" | awk '{print $1/1024/1024}')" | |
| echo "commit_short=$SHORT_SHA" | |
| echo "::notice::iOS JS bundle (main): ${MB} MiB @ ${SHORT_SHA}" | |
| - name: Push bundle size to mobile_bundlesize_stats repo | |
| run: ./scripts/push-bundle-size.sh | |
| env: | |
| GITHUB_ACTOR: metamaskbot | |
| GITHUB_TOKEN: ${{ steps.get-token.outputs.token }} | |
| check-workflows: | |
| name: Check workflows | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ needs.get_requirements.outputs.skip_everything != 'true' && inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' }} | |
| needs: | |
| - get_requirements | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| - name: Download actionlint | |
| id: download-actionlint | |
| run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/62dc61a45fc95efe8c800af7a557ab0b9165d63b/scripts/download-actionlint.bash) 1.7.1 | |
| shell: bash | |
| - name: Check workflow files | |
| run: ${{ steps.download-actionlint.outputs.executable }} -color -config-file .github/actionlint.yaml | |
| shell: bash | |
| # Warms the Namespace cache volume before test shards run. On non-Namespace | |
| # runners uploads a ci-js-deps artifact so consumers can skip their own install. | |
| prepare-ci-js-deps: | |
| name: Prepare CI JS dependencies | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ needs.get_requirements.outputs.skip_everything != 'true' && inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' }} | |
| needs: | |
| - get_requirements | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: ./.github/actions/setup-ci-js-deps | |
| with: | |
| runner_provider: ${{ inputs.runner_provider }} | |
| # TEMP: artifact fallback for non-Namespace runners. | |
| # Remove these two steps once Namespace passes the trial and becomes the default. | |
| - name: Pack CI JS deps | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| run: tar -czf ci-js-deps.tar.gz node_modules app/util/termsOfUse/termsOfUseContent.ts | |
| - name: Upload CI JS deps artifact | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ci-js-deps | |
| path: ci-js-deps.tar.gz | |
| retention-days: 1 | |
| compression-level: 0 | |
| if-no-files-found: error | |
| unit-tests: | |
| name: Unit tests (${{ matrix.shard }}) | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ !cancelled() && needs.get_requirements.result == 'success' && needs.get_requirements.outputs.skip_everything != 'true' && inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' }} | |
| needs: | |
| - get_requirements | |
| - prepare-ci-js-deps | |
| strategy: | |
| matrix: | |
| shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| # TEMP: artifact fallback for non-Namespace runners. | |
| # Remove these two steps once Namespace passes the trial and becomes the default. | |
| - name: Download CI JS deps artifact | |
| id: download-ci-js-deps | |
| if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' }} | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ci-js-deps | |
| path: . | |
| - name: Extract CI JS deps | |
| if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' && steps.download-ci-js-deps.outcome == 'success' }} | |
| run: tar -xzf ci-js-deps.tar.gz && rm ci-js-deps.tar.gz | |
| - uses: ./.github/actions/setup-ci-js-deps | |
| with: | |
| runner_provider: ${{ inputs.runner_provider }} | |
| - name: Prepare results directory | |
| run: mkdir -p tests/results | |
| # The "10" in this command is the total number of shards. It must be kept | |
| # in sync with the length of matrix.shard | |
| # Namespace Linux: --maxWorkers=33% (~3 workers on 8 vCPU) + 12GiB heap. | |
| - run: yarn test:unit --shard=${{ matrix.shard }}/10${{ inputs.runner_provider == 'namespace' && ' --maxWorkers=33%' || '' }} --workerIdleMemoryLimit=2GB --forceExit --silent --coverageReporters=json --json --outputFile=tests/results/unit-test-results-${{ matrix.shard }}.json | |
| env: | |
| NODE_OPTIONS: --max_old_space_size=12288 | |
| - name: Rename coverage report and extract test count for this shard | |
| shell: bash | |
| run: | | |
| mv ./tests/coverage/coverage-final.json ./tests/coverage/coverage-unit-${{ matrix.shard }}.json | |
| cp tests/results/unit-test-results-${{ matrix.shard }}.json ./tests/coverage/jest-results.json | |
| count=$(jq '(.numPassedTests // 0) + (.numFailedTests // 0)' tests/results/unit-test-results-${{ matrix.shard }}.json) | |
| echo "{\"count\": $count}" > ./tests/coverage/count.json | |
| - name: Upload coverage unit shard (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3 | |
| with: | |
| name: coverage-unit-${{ matrix.shard }} | |
| path: ./tests/coverage/ | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload coverage unit shard (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-unit-${{ matrix.shard }} | |
| path: ./tests/coverage/ | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Require clean working directory | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Working tree dirty at end of job" | |
| exit 1 | |
| else | |
| echo "No changes detected" | |
| fi | |
| # We need to merge both unit and component view tests into a single coverage report so the PR coverage | |
| # threshold calculation is accurate. | |
| merge-unit-and-component-view-tests: | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| needs: [prepare-ci-js-deps, unit-tests, component-view-tests] | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ github.event_name != 'merge_group' && inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' }} | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| # TEMP: artifact fallback for non-Namespace runners. | |
| # Remove these two steps once Namespace passes the trial and becomes the default. | |
| - name: Download CI JS deps artifact | |
| id: download-ci-js-deps | |
| if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' }} | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ci-js-deps | |
| path: . | |
| - name: Extract CI JS deps | |
| if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' && steps.download-ci-js-deps.outcome == 'success' }} | |
| run: tar -xzf ci-js-deps.tar.gz && rm ci-js-deps.tar.gz | |
| - uses: ./.github/actions/setup-ci-js-deps | |
| with: | |
| runner_provider: ${{ inputs.runner_provider }} | |
| - name: Download coverage shards (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/download-artifact@7cbad919e4b0e09f17e9d6311a444ff002992b5b # v2.0.1 | |
| with: | |
| pattern: coverage-* | |
| path: tests/coverage/ | |
| - name: Download coverage shards (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: coverage-* | |
| path: tests/coverage/ | |
| - name: Aggregate test counts and gather coverage reports | |
| shell: bash | |
| run: | | |
| unit_total=0 | |
| for file in ./tests/coverage/coverage-unit-*/count.json; do | |
| [ -f "$file" ] || continue | |
| count=$(jq '.count // 0' "$file") | |
| unit_total=$((unit_total + count)) | |
| done | |
| echo "{\"unit_test_number\": $unit_total}" > unit-test-stats.json | |
| echo "Unit test count: $unit_total" | |
| cv_total=0 | |
| for file in ./tests/coverage/coverage-cv-*/count.json; do | |
| [ -f "$file" ] || continue | |
| count=$(jq '.count // 0' "$file") | |
| cv_total=$((cv_total + count)) | |
| done | |
| echo "{\"component_view_test_number\": $cv_total}" > cv-test-stats.json | |
| echo "CV test count: $cv_total" | |
| mkdir -p tests/coverage-cv-merged | |
| for file in ./tests/coverage/coverage-cv-*/coverage-cv-*.json; do | |
| [ -f "$file" ] && cp "$file" ./tests/coverage-cv-merged/ | |
| done | |
| mkdir -p tests/coverage-unit-merged | |
| for file in ./tests/coverage/coverage-unit-*/coverage-unit-*.json; do | |
| [ -f "$file" ] && cp "$file" ./tests/coverage-unit-merged/ | |
| done | |
| find ./tests/coverage/coverage-* -name 'coverage-*.json' -exec mv {} ./tests/coverage/ \; | |
| - run: yarn test:merge-coverage | |
| - run: yarn test:validate-coverage | |
| - name: Upload lcov.info (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3 | |
| with: | |
| name: lcov.info | |
| path: ./tests/merged-coverage/lcov.info | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload lcov.info (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: lcov.info | |
| path: ./tests/merged-coverage/lcov.info | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload cv-test-stats (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3 | |
| with: | |
| name: cv-test-stats | |
| path: ./cv-test-stats.json | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload cv-test-stats (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cv-test-stats | |
| path: ./cv-test-stats.json | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload unit-test-stats (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3 | |
| with: | |
| name: unit-test-stats | |
| path: ./unit-test-stats.json | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload unit-test-stats (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: unit-test-stats | |
| path: ./unit-test-stats.json | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Generate CV test coverage report | |
| run: yarn nyc report --temp-dir ./tests/coverage-cv-merged --report-dir ./tests/coverage-cv-lcov --reporter html --reporter json-summary | |
| - name: Upload cv-test-coverage-html (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3 | |
| with: | |
| name: cv-test-coverage-html | |
| path: ./tests/coverage-cv-lcov/ | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload cv-test-coverage-html (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cv-test-coverage-html | |
| path: ./tests/coverage-cv-lcov/ | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload cv-test-coverage-summary (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3 | |
| with: | |
| name: cv-test-coverage-summary | |
| path: ./tests/coverage-cv-lcov/coverage-summary.json | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload cv-test-coverage-summary (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cv-test-coverage-summary | |
| path: ./tests/coverage-cv-lcov/coverage-summary.json | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Generate unit test coverage summary | |
| run: yarn nyc report --temp-dir ./tests/coverage-unit-merged --report-dir ./tests/coverage-unit-lcov --reporter json-summary | |
| - name: Upload unit-test-coverage-summary (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3 | |
| with: | |
| name: unit-test-coverage-summary | |
| path: ./tests/coverage-unit-lcov/coverage-summary.json | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload unit-test-coverage-summary (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: unit-test-coverage-summary | |
| path: ./tests/coverage-unit-lcov/coverage-summary.json | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Require clean working directory | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Working tree dirty at end of job" | |
| exit 1 | |
| else | |
| echo "No changes detected" | |
| fi | |
| component-view-tests: | |
| name: Component view tests | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ !cancelled() && needs.get_requirements.result == 'success' && needs.get_requirements.outputs.skip_everything != 'true' && inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' }} | |
| needs: | |
| - get_requirements | |
| - prepare-ci-js-deps | |
| strategy: | |
| matrix: | |
| shard: [1, 2] | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| # TEMP: artifact fallback for non-Namespace runners. | |
| # Remove these two steps once Namespace passes the trial and becomes the default. | |
| - name: Download CI JS deps artifact | |
| id: download-ci-js-deps | |
| if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' }} | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ci-js-deps | |
| path: . | |
| - name: Extract CI JS deps | |
| if: ${{ inputs.runner_provider != 'namespace' && needs.prepare-ci-js-deps.result == 'success' && steps.download-ci-js-deps.outcome == 'success' }} | |
| run: tar -xzf ci-js-deps.tar.gz && rm ci-js-deps.tar.gz | |
| - uses: ./.github/actions/setup-ci-js-deps | |
| with: | |
| runner_provider: ${{ inputs.runner_provider }} | |
| - name: Prepare results directory | |
| run: mkdir -p tests/results | |
| - run: | | |
| yarn test:view:ci \ | |
| --shard=${{ matrix.shard }}/2 \ | |
| --json \ | |
| --outputFile=tests/results/cv-test-results-${{ matrix.shard }}.json | |
| env: | |
| NODE_OPTIONS: ${{ inputs.runner_provider == 'namespace' && '--max-old-space-size=12288' || '--max-old-space-size=20480' }} | |
| - name: Rename coverage report and extract test count for this shard | |
| shell: bash | |
| run: | | |
| mv ./tests/coverage/coverage-final.json ./tests/coverage/coverage-cv-${{ matrix.shard }}.json | |
| cp tests/results/cv-test-results-${{ matrix.shard }}.json ./tests/coverage/jest-results.json | |
| count=$(jq '(.numPassedTests // 0) + (.numFailedTests // 0)' tests/results/cv-test-results-${{ matrix.shard }}.json) | |
| echo "{\"count\": $count}" > ./tests/coverage/count.json | |
| - name: Upload coverage CV shard (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3 | |
| with: | |
| name: coverage-cv-${{ matrix.shard }} | |
| path: ./tests/coverage/ | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload coverage CV shard (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-cv-${{ matrix.shard }} | |
| path: ./tests/coverage/ | |
| if-no-files-found: error | |
| retention-days: 7 | |
| smart-e2e-selection: | |
| name: 'Smart E2E Selection' | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| if: ${{ needs.get_requirements.outputs.run_smart_e2e_selection == 'true' }} | |
| needs: | |
| - get_requirements | |
| continue-on-error: true | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| outputs: | |
| ai_e2e_test_tags: ${{ steps.e2e-selection.outputs.ai_e2e_test_tags }} | |
| ai_confidence: ${{ steps.e2e-selection.outputs.ai_confidence }} | |
| ai_performance_test_tags: ${{ steps.e2e-selection.outputs.ai_performance_test_tags }} | |
| ai_performance_test_reasoning: ${{ steps.e2e-selection.outputs.ai_performance_test_reasoning }} | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| with: | |
| sparse-checkout: | | |
| .github/actions/smart-e2e-selection | |
| sparse-checkout-cone-mode: false | |
| fetch-depth: 1 | |
| - name: Checkout for action definition | |
| uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| with: | |
| sparse-checkout: | | |
| .github/actions/smart-e2e-selection | |
| sparse-checkout-cone-mode: false | |
| fetch-depth: 1 | |
| - name: Run Smart E2E Selection | |
| id: e2e-selection | |
| uses: ./.github/actions/smart-e2e-selection | |
| with: | |
| claude-api-key: ${{ secrets.E2E_CLAUDE_API_KEY }} | |
| openai-api-key: ${{ secrets.E2E_OPENAI_API_KEY }} | |
| google-api-key: ${{ secrets.E2E_GEMINI_API_KEY }} | |
| github-token: ${{ github.token }} | |
| pr-number: ${{ github.event.pull_request.number }} | |
| repository: ${{ github.repository }} | |
| post-comment: ${{ inputs.runner_provider != 'namespace' && inputs.runner_provider != 'bitrise' }} | |
| base-ref: ${{ github.event.pull_request.base.ref }} | |
| run-performance-tests-pr: | |
| name: 'Run Performance Tests (PR)' | |
| # Run when: | |
| # - run-performance-tests label is on the PR (forced run, all tests), OR | |
| # - smart-e2e-selection ran and AI selected performance tags. | |
| # ai_performance_test_tags == '[]' → AI ran and found no perf-relevant changes: skip. | |
| # ai_performance_test_tags == '' → conservative fallback (AI failed) or run-performance-tests label: run all tests. | |
| # ai_performance_test_tags == '[…]' → specific tags selected: run those tests. | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: >- | |
| ${{ | |
| inputs.runner_provider != 'bitrise' && | |
| inputs.runner_provider != 'namespace' && | |
| github.event_name == 'pull_request' && | |
| !cancelled() && | |
| ( | |
| needs.get_requirements.outputs.run_performance == 'true' || | |
| ( | |
| needs.smart-e2e-selection.result == 'success' && | |
| needs.smart-e2e-selection.outputs.ai_performance_test_tags != '[]' | |
| ) | |
| ) | |
| }} | |
| needs: [get_requirements, smart-e2e-selection] | |
| uses: ./.github/workflows/run-performance-e2e.yml | |
| with: | |
| performance_tags: ${{ needs.get_requirements.outputs.run_performance != 'true' && needs.smart-e2e-selection.outputs.ai_performance_test_tags || '' }} | |
| performance_tags_reasoning: ${{ needs.get_requirements.outputs.run_performance == 'true' && 'Forced run via run-performance-tests label on PR' || needs.smart-e2e-selection.outputs.ai_performance_test_reasoning }} | |
| build_variant: 'e2e' | |
| branch_name: ${{ github.head_ref }} | |
| pr_number: ${{ github.event.pull_request.number }} | |
| secrets: inherit | |
| permissions: | |
| contents: write | |
| id-token: write | |
| actions: write | |
| pull-requests: write | |
| build-android-apks: | |
| name: 'Build Android APKs' | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: >- | |
| ${{ | |
| inputs.runner_provider != 'bitrise' && | |
| inputs.runner_provider != 'namespace' && | |
| !cancelled() && | |
| needs.get_requirements.outputs.android_e2e_needed == 'true' && | |
| !(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 85 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags == '[]') | |
| }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| actions: read | |
| statuses: read | |
| pull-requests: read | |
| needs: [get_requirements, smart-e2e-selection, native-build-fingerprint] | |
| uses: ./.github/workflows/build-android-e2e.yml | |
| with: | |
| build_type: 'main' | |
| metamask_environment: 'e2e' | |
| source-fingerprint: ${{ needs.native-build-fingerprint.outputs.fingerprint }} | |
| runner_provider: ${{ inputs.runner_provider }} | |
| secrets: inherit | |
| prepare-e2e-timings: | |
| name: 'Prepare E2E Timings' | |
| # Run in parallel with the build jobs — it is a lightweight API call (~30s). | |
| # Fetches qa-stats timings once (merge-base preferred, latest-main fallback) | |
| # and uploads them as a run-scoped artifact so every shard and every re-run | |
| # uses the exact same frozen snapshot, keeping the bin-pack stable. | |
| runs-on: ubuntu-latest | |
| if: >- | |
| ${{ | |
| !cancelled() && | |
| (needs.get_requirements.outputs.android_e2e_needed == 'true' || needs.get_requirements.outputs.ios_e2e_needed == 'true') && | |
| !(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 85 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags == '[]') | |
| }} | |
| needs: [get_requirements, smart-e2e-selection] | |
| outputs: | |
| available: ${{ steps.fetch.outputs.available }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: '.nvmrc' | |
| - name: Fetch and freeze E2E timings | |
| id: fetch | |
| continue-on-error: true | |
| run: node .github/scripts/e2e-freeze-timings.mjs | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPOSITORY: ${{ github.repository }} | |
| PR_NUMBER: ${{ github.event.pull_request.number || '' }} | |
| OUTPUT_PATH: ./e2e-timings.json | |
| - name: Upload frozen timings artifact | |
| # continue-on-error so the job does not fail if the artifact already | |
| # exists for this run (i.e. on re-runs). First-write wins — the artifact | |
| # uploaded by the earliest attempt is always the one shards will use. | |
| if: ${{ steps.fetch.outputs.available == 'true' }} | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-timings-${{ github.run_id }} | |
| path: ./e2e-timings.json | |
| # Needed so re-runs of old PRs can still download the frozen snapshot | |
| # and keep the same bin-pack, avoiding failed tests landing on a different | |
| # shard that was not re-run, which would make them appear as passed. | |
| # See: https://github.com/MetaMask/metamask-mobile/pull/31217 | |
| retention-days: 30 | |
| e2e-smoke-tests-android: | |
| name: 'Android E2E Smoke Tests' | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: >- | |
| ${{ | |
| inputs.runner_provider != 'bitrise' && | |
| inputs.runner_provider != 'namespace' && | |
| !cancelled() && | |
| needs.build-android-apks.result == 'success' && | |
| (needs.prepare-e2e-timings.result == 'success' || needs.prepare-e2e-timings.result == 'failure' || needs.prepare-e2e-timings.result == 'skipped') | |
| }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [get_requirements, build-android-apks, smart-e2e-selection, prepare-e2e-timings] | |
| uses: ./.github/workflows/run-e2e-smoke-tests-android.yml | |
| with: | |
| changed_files: ${{ needs.get_requirements.outputs.changed_files }} | |
| selected_tags: >- | |
| ${{ | |
| (fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 85 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) || | |
| '["ALL"]' | |
| }} | |
| runner_provider: ${{ inputs.runner_provider }} | |
| secrets: inherit | |
| build-ios-apps: | |
| name: 'Build iOS Apps' | |
| if: >- | |
| ${{ | |
| !cancelled() && | |
| needs.get_requirements.outputs.ios_e2e_needed == 'true' && | |
| !(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 85 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags == '[]') | |
| }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| actions: read | |
| statuses: read | |
| pull-requests: read | |
| needs: [get_requirements, smart-e2e-selection, native-build-fingerprint] | |
| uses: ./.github/workflows/build-ios-e2e.yml | |
| with: | |
| source-fingerprint: ${{ needs.native-build-fingerprint.outputs.fingerprint }} | |
| runner_provider: ${{ inputs.runner_provider }} | |
| secrets: inherit | |
| ios-tests-ready: | |
| name: 'iOS Tests Ready' | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| if: ${{ !cancelled() && needs.build-ios-apps.result == 'success' }} | |
| needs: [build-ios-apps] | |
| steps: | |
| - name: iOS build complete | |
| run: echo "Dummy step to better visualize the Android and iOS E2E tests in Github workflow graph" | |
| e2e-smoke-tests-ios: | |
| name: 'iOS E2E Smoke Tests' | |
| if: >- | |
| ${{ | |
| !cancelled() && | |
| needs.ios-tests-ready.result == 'success' && | |
| (needs.prepare-e2e-timings.result == 'success' || needs.prepare-e2e-timings.result == 'failure' || needs.prepare-e2e-timings.result == 'skipped') | |
| }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [get_requirements, ios-tests-ready, smart-e2e-selection, prepare-e2e-timings] | |
| uses: ./.github/workflows/run-e2e-smoke-tests-ios.yml | |
| with: | |
| changed_files: ${{ needs.get_requirements.outputs.changed_files }} | |
| selected_tags: >- | |
| ${{ | |
| (fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 85 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) || | |
| '["ALL"]' | |
| }} | |
| runner_provider: ${{ inputs.runner_provider }} | |
| secrets: inherit | |
| appium-smoke-tests-android: | |
| name: 'Appium Smoke Tests (Android)' | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: >- | |
| ${{ | |
| inputs.runner_provider != 'bitrise' && | |
| inputs.runner_provider != 'namespace' && | |
| !cancelled() && | |
| needs.build-android-apks.result == 'success' | |
| }} | |
| permissions: | |
| contents: read | |
| checks: write | |
| id-token: write | |
| needs: [build-android-apks, smart-e2e-selection] | |
| uses: ./.github/workflows/run-appium-smoke-tests-android.yml | |
| with: | |
| build_type: 'main' | |
| metamask_environment: 'e2e' | |
| # Appium Android on Cirrus/GitHub-hosted runners until Namespace parity is ready. | |
| runner_provider: current | |
| selected_tags: >- | |
| ${{ | |
| (fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 85 && | |
| needs.smart-e2e-selection.outputs.ai_e2e_test_tags) || | |
| '["ALL"]' | |
| }} | |
| secrets: inherit | |
| appium-smoke-tests-ios: | |
| name: 'Appium Smoke Tests (iOS)' | |
| # Main push/schedule: always run when the iOS build succeeded. | |
| # PRs: skipped by default; add run-appium-ios-tests, or change page-objects/selectors/locators/framework | |
| # (same smart-selected tags as other E2E jobs). | |
| if: >- | |
| ${{ | |
| !cancelled() && | |
| needs.ios-tests-ready.result == 'success' && | |
| ( | |
| ( | |
| github.ref == 'refs/heads/main' && | |
| (github.event_name == 'push' || github.event_name == 'schedule') | |
| ) || | |
| ( | |
| github.event_name == 'pull_request' && | |
| needs.get_requirements.outputs.run_appium_ios == 'true' | |
| ) | |
| ) | |
| }} | |
| permissions: | |
| contents: read | |
| checks: write | |
| id-token: write | |
| needs: [get_requirements, ios-tests-ready, smart-e2e-selection] | |
| uses: ./.github/workflows/run-appium-smoke-tests-ios.yml | |
| with: | |
| build_type: 'main' | |
| metamask_environment: 'e2e' | |
| runner_provider: current | |
| selected_tags: >- | |
| ${{ | |
| (fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 85 && | |
| needs.smart-e2e-selection.outputs.ai_e2e_test_tags) || | |
| '["ALL"]' | |
| }} | |
| secrets: inherit | |
| # Fixture validation — ensures committed E2E fixtures match the live app state schema | |
| validate-e2e-fixtures: | |
| name: 'Validate E2E Fixtures' | |
| if: ${{ needs.ios-tests-ready.result == 'success' && github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [get_requirements, ios-tests-ready, smart-e2e-selection] | |
| uses: ./.github/workflows/run-e2e-workflow.yml | |
| with: | |
| test-suite-name: validate-e2e-fixtures | |
| platform: ios | |
| test_suite_tag: 'FixtureValidation' | |
| split_number: 1 | |
| total_splits: 1 | |
| build_type: 'main' | |
| runner_provider: ${{ inputs.runner_provider }} | |
| secrets: inherit | |
| report-fixture-validation: | |
| name: 'Report Fixture Validation' | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' && !cancelled() && needs.validate-e2e-fixtures.result != 'skipped' }} | |
| needs: [validate-e2e-fixtures] | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| - name: Download fixture validation results (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| continue-on-error: true | |
| uses: namespace-actions/download-artifact@7cbad919e4b0e09f17e9d6311a444ff002992b5b # v2.0.1 | |
| with: | |
| name: test-e2e-main-validate-e2e-fixtures-junit-results | |
| path: fixture-results/ | |
| - name: Download fixture validation results (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: test-e2e-main-validate-e2e-fixtures-junit-results | |
| path: fixture-results/ | |
| - name: Report results | |
| if: ${{ inputs.runner_provider != 'namespace' && inputs.runner_provider != 'bitrise' }} | |
| env: | |
| RESULTS_PATH: fixture-results | |
| VALIDATION_RESULT: ${{ needs.validate-e2e-fixtures.result }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: node .github/scripts/e2e-report-fixture-validation.mjs | |
| sonar-cloud: | |
| name: SonarCloud analysis | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| needs: merge-unit-and-component-view-tests | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' && github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork }} | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| with: | |
| fetch-depth: 0 # SonarCloud needs a full checkout to perform necessary analysis | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| with: | |
| fetch-depth: 0 # SonarCloud needs a full checkout to perform necessary analysis | |
| - name: Configure Namespace cache | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1 | |
| with: | |
| path: | | |
| ~/.cache/yarn | |
| .metamask | |
| node_modules | |
| .yarn/cache | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: '.nvmrc' | |
| - name: Download lcov.info (Namespace) | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| uses: namespace-actions/download-artifact@7cbad919e4b0e09f17e9d6311a444ff002992b5b # v2.0.1 | |
| with: | |
| name: lcov.info | |
| path: coverage/ | |
| - name: Download lcov.info (current) | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: lcov.info | |
| path: coverage/ | |
| - name: Upload coverage reports to Codecov | |
| if: ${{ always() }} | |
| continue-on-error: true | |
| uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 | |
| - name: SonarCloud Scan | |
| if: ${{ env.HAVE_SONAR_TOKEN == 'true' }} | |
| continue-on-error: true | |
| # This is SonarSource/sonarqube-scan-action@v7.0.0 | |
| uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 | |
| env: | |
| HAVE_SONAR_TOKEN: ${{ secrets.SONAR_TOKEN != '' }} | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| - name: Require clean working directory | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Working tree dirty at end of job" | |
| exit 1 | |
| else | |
| echo "No changes detected" | |
| fi | |
| # Revert git update-index --no-assume-unchanged for each entry | |
| echo "Reverting assume unchanged for the following paths:" | |
| for path in "${EXCLUDES[@]}"; do | |
| echo "$path" | |
| git update-index --no-assume-unchanged "$path" | |
| done | |
| sonar-cloud-quality-gate-status: | |
| name: SonarCloud quality gate status | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| needs: sonar-cloud | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' && github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork }} | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| - name: SonarCloud Quality Gate Status | |
| id: sonar-status | |
| env: | |
| REPO: ${{ github.repository }} | |
| ISSUE_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Skip step if event is not a PR | |
| if [[ "${{ github.event_name }}" != "pull_request" ]]; then | |
| echo "This job only runs for pull requests." | |
| exit 0 | |
| fi | |
| # Bypass step if skip-sonar-cloud label is found | |
| LABEL=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/$REPO/issues/$ISSUE_NUMBER/labels" | \ | |
| jq -r '.[] | select(.name=="skip-sonar-cloud") | .name') | |
| if [[ "$LABEL" == "skip-sonar-cloud" ]]; then | |
| echo "skip-sonar-cloud label found. Skipping SonarCloud Quality Gate check." | |
| else | |
| PROJECT_KEY="MetaMask_metamask-mobile" | |
| PR_NUMBER="${{ github.event.pull_request.number }}" | |
| SONAR_TOKEN="${{ secrets.SONAR_TOKEN }}" | |
| if [ -z "$PR_NUMBER" ]; then | |
| echo "No pull request number found. Failing the check." | |
| exit 1 | |
| fi | |
| # Poll until SonarCloud has finished ingesting the analysis (status OK or ERROR), | |
| # retrying on NONE/empty which means ingestion is still in progress. | |
| # Retries up to 12 times, 15s apart (~2m45s total), then fails the check on timeout. | |
| MAX_ATTEMPTS=12 | |
| SLEEP_INTERVAL=15 | |
| for i in $(seq 1 $MAX_ATTEMPTS); do | |
| RESPONSE=$(curl -s -u "$SONAR_TOKEN:" \ | |
| "https://sonarcloud.io/api/qualitygates/project_status?projectKey=$PROJECT_KEY&pullRequest=$PR_NUMBER") | |
| echo "SonarCloud API Response (attempt $i/$MAX_ATTEMPTS): $RESPONSE" | |
| STATUS=$(echo "$RESPONSE" | jq -r '.projectStatus.status') | |
| if [[ "$STATUS" == "OK" ]]; then | |
| echo "Quality Gate passed." | |
| exit 0 | |
| elif [[ "$STATUS" == "ERROR" ]]; then | |
| echo "Quality Gate failed." | |
| exit 1 | |
| fi | |
| echo "Quality Gate status not yet settled (got: '$STATUS'). Retrying in ${SLEEP_INTERVAL}s..." | |
| if [[ $i -lt $MAX_ATTEMPTS ]]; then | |
| sleep "$SLEEP_INTERVAL" | |
| fi | |
| done | |
| echo "Could not determine Quality Gate status after $MAX_ATTEMPTS attempts." | |
| exit 1 | |
| fi | |
| # TEMP: delete the ci-js-deps artifact once all consumers have finished so it | |
| # does not sit in GitHub artifact storage for the full retention-days: 1 | |
| # window. Every push re-creates this artifact from scratch, so there is never | |
| # a reason to keep it after the run's consumers are done. | |
| # Remove this job together with the upload/download steps once Namespace | |
| # becomes the default runner. | |
| cleanup-ci-js-deps: | |
| name: Delete CI JS deps artifact | |
| runs-on: ubuntu-latest | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ always() && inputs.runner_provider != 'namespace' && inputs.runner_provider != 'bitrise' }} | |
| needs: | |
| - unit-tests | |
| - component-view-tests | |
| - merge-unit-and-component-view-tests | |
| permissions: | |
| actions: write | |
| steps: | |
| - name: Delete CI JS deps artifact | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: context.runId, | |
| }); | |
| const target = artifacts.data.artifacts.find(a => a.name === 'ci-js-deps'); | |
| if (target) { | |
| await github.rest.actions.deleteArtifact({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| artifact_id: target.id, | |
| }); | |
| core.info(`Deleted artifact ci-js-deps (id=${target.id})`); | |
| } else { | |
| core.info('Artifact ci-js-deps not found (already deleted or never created)'); | |
| } | |
| check-all-jobs-pass: | |
| name: Check all jobs pass | |
| # Run the aggregate gate even when optional dependencies are skipped. | |
| # The composite action decides which skipped jobs are acceptable. | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ always() && !cancelled() && inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' }} | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| needs: | |
| - get_requirements | |
| - check-diff | |
| - dedupe | |
| - scripts | |
| - unit-tests | |
| - component-view-tests | |
| - check-workflows | |
| - prepare-ci-js-deps | |
| - js-bundle-size-check | |
| - sonar-cloud-quality-gate-status | |
| - build-android-apks | |
| - build-ios-apps | |
| - e2e-smoke-tests-android | |
| - e2e-smoke-tests-ios | |
| - appium-smoke-tests-android | |
| - appium-smoke-tests-ios | |
| steps: | |
| - uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1 | |
| if: ${{ inputs.runner_provider == 'namespace' }} | |
| with: | |
| fetch-depth: 1 | |
| sparse-checkout: | | |
| .github/actions/ci-status-gate | |
| - uses: actions/checkout@v6 | |
| if: ${{ inputs.runner_provider != 'namespace' }} | |
| with: | |
| fetch-depth: 1 | |
| sparse-checkout: | | |
| .github/actions/ci-status-gate | |
| - name: Evaluate CI status | |
| uses: ./.github/actions/ci-status-gate | |
| with: | |
| needs-json: ${{ toJSON(needs) }} | |
| requirement-context-json: ${{ toJSON(needs.get_requirements.outputs) }} | |
| e2e-job-regex: '^(build-android-apks|build-ios-apps|e2e-smoke-tests-android|e2e-smoke-tests-ios|appium-smoke-tests-android|appium-smoke-tests-ios)$' | |
| event-name: ${{ github.event_name }} | |
| is-fork: ${{ github.event.pull_request.head.repo.fork == true }} | |
| log-merge-group-failure: | |
| name: Log merge group failure | |
| runs-on: ${{ inputs.runner_provider == 'namespace' && 'namespace-profile-metamask-ci-linux' || 'ubuntu-latest' }} | |
| # Only run this job if the merge group event fails, skip on forks | |
| # BITRISE/NAMESPACE-SHADOW: skip for iOS-only shadow benchmark (INFRA-3679) — revert to enable full pipeline | |
| if: ${{ inputs.runner_provider != 'bitrise' && inputs.runner_provider != 'namespace' && github.event_name == 'merge_group' && failure() }} | |
| needs: | |
| - check-all-jobs-pass | |
| steps: | |
| - name: Log merge group failure to Google Sheets | |
| uses: MetaMask/github-tools/.github/actions/log-merge-group-failure@v1 | |
| with: | |
| google-application-credentials: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} | |
| google-service-account: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }} | |
| spreadsheet-id: ${{ secrets.GOOGLE_MERGE_QUEUE_SPREADSHEET_ID }} | |
| sheet-name: ${{ secrets.GOOGLE_MERGE_QUEUE_SHEET_NAME }} |