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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ app/core/DeeplinkManager @MetaMask/mobile-pla
scripts/build.sh @MetaMask/mobile-platform
fingerprint.config.js @MetaMask/mobile-platform
builds.yml @MetaMask/mobile-platform
.github/workflows/create-build-branch.yml @MetaMask/mobile-platform
.github/workflows/push-eas-update.yml @MetaMask/mobile-admins
.github/workflows/upload-to-testflight.yml @MetaMask/mobile-admins
scripts/update-expo-channel.js @MetaMask/mobile-admins
Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/create-build-branch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Create Build Branch

# Reusable workflow that creates an ephemeral build/<source>-<timestamp> branch
# from a source ref. This avoids pushing version-bump commits directly to
# protected branches (e.g. main).
#
# The caller is responsible for deleting the branch after use.

on:
workflow_call:
inputs:
source_branch:
description: 'Branch, tag, or SHA to branch from'
required: true
type: string
outputs:
build_branch:
description: 'Name of the created ephemeral build branch'
value: ${{ jobs.create.outputs.branch_name }}

jobs:
create:
name: Create build branch
runs-on: ubuntu-latest
outputs:
branch_name: ${{ steps.create-branch.outputs.branch_name }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.source_branch }}
token: ${{ secrets.PR_TOKEN || github.token }}
- name: Create temporary build branch
id: create-branch
env:
SOURCE_BRANCH: ${{ inputs.source_branch }}
run: |
TIMESTAMP=$(date +%s%3N)
SANITIZED=$(echo "$SOURCE_BRANCH" | tr '/' '-')
BRANCH_NAME="build/${SANITIZED}-${TIMESTAMP}"
git checkout -b "$BRANCH_NAME"
git push origin "$BRANCH_NAME"
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
echo "✅ Created build branch: $BRANCH_NAME"
252 changes: 73 additions & 179 deletions .github/workflows/nightly-build.yml
Original file line number Diff line number Diff line change
@@ -1,211 +1,105 @@
name: Nightly Build

# Triggered by every push to chore/temp-nightly (which nightly-temp-branch-sync.yml
# force-pushes daily at 4 AM UTC to match main).
# Temporarily now this is pointing to test/temp-nightly instead of chore/temp-nightly.
# Runs on a schedule (4 AM UTC) like the old nightly-temp-branch-sync.
# Each build creates its own ephemeral branch via create-build-branch.yml,
# so the persistent chore/temp-nightly branch is no longer needed.
#
# [skip ci] commits (e.g. version bumps pushed via update-latest-build-version.yml)
# are automatically skipped by GitHub Actions, so this workflow will NOT
# double-trigger on those commits.
# iOS builds are handled end-to-end by upload-to-testflight.yml (build + upload).
# Android builds use create-build-branch.yml + build.yml (build artifacts only).
#
# Version strategy: exp and rc builds share the same bundle ID (MetaMask) so
# TestFlight requires unique CFBundleVersion per upload. We call the external
# version generator once (→ version N for exp), then locally increment to N+1
# for the rc build. Both builds run in parallel after their respective bumps.
# rc depends on exp within each platform stream to ensure the external version
# service produces sequential numbers (N for exp, N+1 for rc).
# iOS and Android streams run in parallel; their version numbers will differ
# but remain unique, which is all TestFlight / Play Store require.

on:
push:
branches:
- test/temp-nightly
schedule:
# NOTE: Scheduled workflows ALWAYS run from the default branch (main)
- cron: '0 4 * * *'
workflow_dispatch:

# contents: write required by build.yml update-build-version job (version bump commit push)
permissions:
contents: write
id-token: write

jobs:
bump-version-exp:
name: Bump build version (exp)
uses: ./.github/workflows/update-latest-build-version.yml
permissions:
contents: write
id-token: write
# ── iOS exp: build + TestFlight upload ─────────────────────────────────
ios-exp:
name: Nightly iOS exp
uses: ./.github/workflows/upload-to-testflight.yml
with:
base-branch: ${{ github.ref_name }}
source_branch: main
environment: exp
testflight_group: 'MetaMask BETA & Release Candidates'
secrets: inherit

bump-version-rc:
name: Bump build version (rc)
needs: [bump-version-exp]
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
commit-hash: ${{ steps.bump.outputs.commit-hash }}
build-version: ${{ steps.bump.outputs.build-version }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.bump-version-exp.outputs.commit-hash }}
fetch-depth: 0
token: ${{ secrets.PR_TOKEN || github.token }}
# ── iOS rc: build + TestFlight upload (after exp for sequential versions) ─
ios-rc:
name: Nightly iOS rc
needs: [ios-exp]
uses: ./.github/workflows/upload-to-testflight.yml
with:
source_branch: main
environment: rc
testflight_group: 'MetaMask BETA & Release Candidates'
secrets: inherit

- name: Increment version for RC build
id: bump
env:
EXP_VERSION: ${{ needs.bump-version-exp.outputs.build-version }}
HEAD_REF: ${{ github.ref_name }}
run: |
RC_VERSION=$((EXP_VERSION + 1))
echo "Exp version: $EXP_VERSION → RC version: $RC_VERSION"
./scripts/set-build-version.sh "$RC_VERSION"
git config user.name metamaskbot
git config user.email metamaskbot@users.noreply.github.com
git add bitrise.yml package.json ios/MetaMask.xcodeproj/project.pbxproj android/app/build.gradle
git commit -m "[skip ci] Bump version number to ${RC_VERSION} (nightly rc)"
git push origin HEAD:"$HEAD_REF" --force-with-lease
echo "commit-hash=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "build-version=$RC_VERSION" >> "$GITHUB_OUTPUT"
# ── Android exp: ephemeral branch + build ──────────────────────────────
android-exp-branch:
uses: ./.github/workflows/create-build-branch.yml
with:
source_branch: main
secrets: inherit

build-exp:
name: Nightly exp build (main-exp)
needs: [bump-version-exp]
android-exp:
name: Nightly Android exp
needs: [android-exp-branch]
uses: ./.github/workflows/build.yml
with:
build_name: main-exp
platform: both
skip_version_bump: true
source_branch: ${{ needs.bump-version-exp.outputs.commit-hash }}
platform: android
skip_version_bump: false
source_branch: ${{ needs.android-exp-branch.outputs.build_branch }}
secrets: inherit

build-rc:
name: Nightly RC build (main-rc)
needs: [bump-version-rc]
# ── Android rc: ephemeral branch + build (after exp for sequential versions) ─
android-rc-branch:
needs: [android-exp]
uses: ./.github/workflows/create-build-branch.yml
with:
source_branch: main
secrets: inherit

android-rc:
name: Nightly Android rc
needs: [android-rc-branch]
uses: ./.github/workflows/build.yml
with:
build_name: main-rc
platform: both
skip_version_bump: true
source_branch: ${{ needs.bump-version-rc.outputs.commit-hash }}
platform: android
skip_version_bump: false
source_branch: ${{ needs.android-rc-branch.outputs.build_branch }}
secrets: inherit

upload-exp-testflight:
name: Upload exp to TestFlight
needs: [build-exp]
runs-on: ghcr.io/cirruslabs/macos-runner:sequoia-xl
environment: apple
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Ruby (iOS)
uses: ruby/setup-ruby@44511735964dcb71245e7e55f72539531f7bc0eb #v1
with:
ruby-version: '3.2.9'
working-directory: ios
bundler-cache: true

- name: Download iOS IPA artifact
uses: actions/download-artifact@v4
with:
name: ios-ipa-main-exp

- name: Find IPA path
id: ipa
run: |
IPA=$(find . -name '*.ipa' -type f | head -1)
if [ -z "$IPA" ]; then
echo "::error::No .ipa file found in artifact"
exit 1
fi
case "$IPA" in /*) ABS="$IPA" ;; *) ABS="$PWD/$IPA" ;; esac
echo "path=$ABS" >> "$GITHUB_OUTPUT"

- name: Setup App Store Connect API Key
run: |
bash scripts/setup-app-store-connect-api-key.sh \
"$APP_STORE_CONNECT_API_KEY_ISSUER_ID" \
"$APP_STORE_CONNECT_API_KEY_KEY_ID" \
"$APP_STORE_CONNECT_API_KEY_KEY_CONTENT"
env:
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }}
APP_STORE_CONNECT_API_KEY_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_CONTENT }}

- name: Upload to TestFlight
run: |
bash scripts/upload-to-testflight.sh \
"github_actions_main-exp" \
"${{ github.ref_name }}" \
"${{ steps.ipa.outputs.path }}" \
"MetaMask BETA & Release Candidates"

- name: Cleanup API Key
if: always()
run: |
rm -f ios/AuthKey.p8
echo "🧹 Cleaned up API key file"

upload-rc-testflight:
name: Upload RC to TestFlight
needs: [build-rc]
runs-on: ghcr.io/cirruslabs/macos-runner:sequoia-xl
environment: apple
# ── Cleanup Android ephemeral branches ─────────────────────────────────
# iOS branches are cleaned up by upload-to-testflight.yml internally.
cleanup:
name: Cleanup Android build branches
needs: [android-exp-branch, android-rc-branch, android-exp, android-rc]
if: always()
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Ruby (iOS)
uses: ruby/setup-ruby@44511735964dcb71245e7e55f72539531f7bc0eb #v1
with:
ruby-version: '3.2.9'
working-directory: ios
bundler-cache: true

- name: Download iOS IPA artifact
uses: actions/download-artifact@v4
- uses: actions/checkout@v4
with:
name: ios-ipa-main-rc

- name: Find IPA path
id: ipa
run: |
IPA=$(find . -name '*.ipa' -type f | head -1)
if [ -z "$IPA" ]; then
echo "::error::No .ipa file found in artifact"
exit 1
fi
case "$IPA" in /*) ABS="$IPA" ;; *) ABS="$PWD/$IPA" ;; esac
echo "path=$ABS" >> "$GITHUB_OUTPUT"

- name: Setup App Store Connect API Key
run: |
bash scripts/setup-app-store-connect-api-key.sh \
"$APP_STORE_CONNECT_API_KEY_ISSUER_ID" \
"$APP_STORE_CONNECT_API_KEY_KEY_ID" \
"$APP_STORE_CONNECT_API_KEY_KEY_CONTENT"
token: ${{ secrets.PR_TOKEN || github.token }}
- name: Delete ephemeral branches
env:
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }}
APP_STORE_CONNECT_API_KEY_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_CONTENT }}

- name: Upload to TestFlight
run: |
# Group arg is required as positional placeholder for the 5th arg (distribute_external=false).
# With distribute_external=false the build is uploaded but NOT distributed to external testers.
bash scripts/upload-to-testflight.sh \
"github_actions_main-rc" \
"${{ github.ref_name }}" \
"${{ steps.ipa.outputs.path }}" \
"MetaMask BETA & Release Candidates"

- name: Cleanup API Key
if: always()
EXP_BRANCH: ${{ needs.android-exp-branch.outputs.build_branch }}
RC_BRANCH: ${{ needs.android-rc-branch.outputs.build_branch }}
run: |
rm -f ios/AuthKey.p8
echo "🧹 Cleaned up API key file"
for branch in "$EXP_BRANCH" "$RC_BRANCH"; do
if [ -n "$branch" ]; then
git push origin --delete "$branch" || true
echo "🧹 Deleted: $branch"
fi
done
Loading
Loading