Skip to content

Commit 676eaf1

Browse files
chore(runway): cherry-pick ci: split push-eas-update into two parallel jobs (#30724)
- ci: split push-eas-update into two parallel jobs (#30399) ## **Description** Splits the `push-eas-update` job into two parallel jobs, one per platform (iOS and Android), running on separate runners. This avoids LavaMoat serializer contention and mitigates intermittent OTA deployment failures caused by a [known GitHub Actions runner issue](actions/runner#3819 (comment)). The shared EAS update steps have been extracted into a reusable workflow. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: N/A ## **Manual testing steps** | Run | Platform | Result | |-----|----------|--------| | [iOS dry-run](https://github.com/MetaMask/metamask-mobile/actions/runs/26117723683/job/76811308433) | iOS | ✅ Passed — cancelled before EAS upload | | [Android test](https://github.com/MetaMask/metamask-mobile/actions/runs/26119164078) | Android | ✅ Passed | | [Both platforms in parallel](https://github.com/MetaMask/metamask-mobile/actions/runs/26120507757) | iOS + Android | ✅ Both passed | ## **Before/After** ### **Before** Single `push-update` job — iOS bundled first, then Android sequentially on the same runner. ### **After** Two parallel jobs (`push-update-ios`, `push-update-android`) running simultaneously on isolated runners, visible as side-by-side nodes in the Actions run graph. Verified via dry-run dispatch targeting `exp` channel. [b4dffea](b4dffea) Co-authored-by: João Loureiro <175489935+joaoloureirop@users.noreply.github.com>
1 parent cd21803 commit 676eaf1

2 files changed

Lines changed: 257 additions & 177 deletions

File tree

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
name: EAS Update Platform
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
platform:
7+
description: 'Platform to update (ios or android)'
8+
type: string
9+
required: true
10+
commit_sha:
11+
description: 'Target commit SHA to build and push'
12+
type: string
13+
required: true
14+
artifact_name:
15+
description: 'node_modules artifact name to download'
16+
type: string
17+
required: true
18+
channel:
19+
description: 'OTA channel (exp, rc, production)'
20+
type: string
21+
required: true
22+
message:
23+
description: 'EAS update message'
24+
type: string
25+
required: true
26+
build_name:
27+
description: 'Build name from builds.yml (e.g. main-exp)'
28+
type: string
29+
required: true
30+
github_environment:
31+
description: 'GitHub environment name for secrets/approvals'
32+
type: string
33+
required: true
34+
secrets_json:
35+
description: 'JSON mapping of secret aliases from builds.yml'
36+
type: string
37+
required: true
38+
39+
jobs:
40+
push-update:
41+
name: Push EAS Update (${{ inputs.platform }})
42+
runs-on: ubuntu-latest
43+
environment: ${{ inputs.github_environment }}
44+
env:
45+
# OTA-specific vars only — build config + secrets come from builds.yml via steps below
46+
TARGET_COMMIT_HASH: ${{ inputs.commit_sha }}
47+
ARTIFACT_NAME: ${{ inputs.artifact_name }}
48+
EXPO_CHANNEL: ${{ vars.EXPO_CHANNEL }}
49+
UPDATE_MESSAGE: ${{ inputs.message }}
50+
TARGET_CHANNEL: ${{ inputs.channel }}
51+
OTA_PUSH_PLATFORM: ${{ inputs.platform }}
52+
GIT_BRANCH: ${{ github.ref_name }}
53+
steps:
54+
- name: Checkout repository
55+
uses: actions/checkout@v4
56+
with:
57+
ref: ${{ env.TARGET_COMMIT_HASH }}
58+
fetch-depth: 1
59+
60+
- name: Setup Node.js
61+
uses: actions/setup-node@v4
62+
with:
63+
node-version-file: '.nvmrc'
64+
65+
- name: Validate artifact compatibility
66+
uses: ./.github/actions/validate-artifact-compatibility
67+
with:
68+
artifact-name: ${{ env.ARTIFACT_NAME }}
69+
artifact-prefix: node-modules-eas-update-pr
70+
validation-context: artifact
71+
72+
- name: Download node_modules artifact
73+
uses: actions/download-artifact@v4
74+
with:
75+
name: ${{ env.ARTIFACT_NAME }}
76+
77+
- name: Restore executable permissions
78+
uses: ./.github/actions/restore-node-modules-permissions
79+
80+
- name: Verify downloaded artifacts
81+
run: |
82+
echo "✅ Verifying downloaded artifacts..."
83+
if [ ! -d "node_modules" ]; then
84+
echo "❌ node_modules directory not found"
85+
exit 1
86+
fi
87+
if [ ! -f "app/core/InpageBridgeWeb3.js" ]; then
88+
echo "❌ InpageBridgeWeb3.js not found in artifact"
89+
exit 1
90+
fi
91+
echo "📦 node_modules size: $(du -sh node_modules | cut -f1)"
92+
echo "✅ Artifacts verified"
93+
94+
- name: Apply build config from builds.yml
95+
run: |
96+
BUILD_NAME="${{ inputs.build_name }}"
97+
echo "📦 Applying build config for: $BUILD_NAME"
98+
eval "$(node scripts/apply-build-config.js "$BUILD_NAME" --export)"
99+
node scripts/apply-build-config.js "$BUILD_NAME" --export-github-env >> "$GITHUB_ENV"
100+
101+
- name: Set secrets from builds.yml mapping
102+
env:
103+
CONFIG_SECRETS: ${{ inputs.secrets_json }}
104+
ALL_SECRETS: ${{ toJSON(secrets) }}
105+
run: node scripts/set-secrets-from-config.js
106+
107+
- name: Determine signing secret name
108+
shell: bash
109+
env:
110+
TARGET: ${{ inputs.channel }}
111+
run: |
112+
case "$TARGET" in
113+
exp)
114+
SECRET_NAME="metamask-exp-expo-signer"
115+
;;
116+
rc)
117+
SECRET_NAME="metamask-rc-expo-signer"
118+
;;
119+
production)
120+
SECRET_NAME="metamask-prod-expo-signer"
121+
;;
122+
*)
123+
echo "❌ Unknown target: $TARGET"
124+
exit 1
125+
;;
126+
esac
127+
echo "AWS_SIGNING_CERT_SECRET_NAME=$SECRET_NAME" >> "$GITHUB_ENV"
128+
129+
- name: Configure AWS credentials
130+
uses: aws-actions/configure-aws-credentials@v4
131+
with:
132+
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
133+
aws-region: 'us-east-2'
134+
135+
- name: Fetch secret and export as environment variables
136+
shell: bash
137+
run: |
138+
echo "🔐 Fetching secret from Secrets Manager..."
139+
secret_json=$(aws secretsmanager get-secret-value \
140+
--region 'us-east-2' \
141+
--secret-id "${AWS_SIGNING_CERT_SECRET_NAME}" \
142+
--query SecretString \
143+
--output text)
144+
145+
keys=$(echo "$secret_json" | jq -r 'keys[]')
146+
for key in $keys; do
147+
value=$(echo "$secret_json" | jq -r --arg k "$key" '.[$k]')
148+
echo "::add-mask::$value"
149+
echo "$key=$(printf '%s' "$value")" >> "$GITHUB_ENV"
150+
echo "✅ Set secret for key: $key"
151+
done
152+
153+
- name: Display configuration
154+
run: |
155+
TARGET_RUNTIME_VERSION=$(node -p "require('./package.json').version")
156+
echo "🔧 Configuration:"
157+
echo " Platform: ${{ inputs.platform }}"
158+
echo " Build: ${{ inputs.build_name }}"
159+
echo " Channel: ${EXPO_CHANNEL:-<not set>}"
160+
echo " Channel (vars.EXPO_CHANNEL): ${{ vars.EXPO_CHANNEL }}"
161+
echo " Message: ${UPDATE_MESSAGE:-<not set>}"
162+
echo " Runtime Version (target): ${TARGET_RUNTIME_VERSION}"
163+
164+
- name: Build & Push EAS Update via build.sh
165+
env:
166+
SKIP_TRANSFORM_LINT: 'true'
167+
NODE_OPTIONS: '--max_old_space_size=8192'
168+
run: |
169+
echo "📦 Configuring EXPO key..."
170+
if [[ -z "$EXPO_KEY_PRIV_B64" ]]; then
171+
echo "⚠️ EXPO_KEY_PRIV_B64 is not set. Skipping keystore decoding."
172+
exit 1
173+
fi
174+
175+
# Decode the key
176+
EXPO_KEY_PRIV=$(echo "$EXPO_KEY_PRIV_B64" | base64 --decode)
177+
export EXPO_KEY_PRIV
178+
echo "✅ Expo key decoded and exported"
179+
180+
echo "🚀 Pushing EAS update for channel: ${TARGET_CHANNEL}"
181+
case "${TARGET_CHANNEL}" in
182+
exp)
183+
yarn run build:expo-update:main:exp
184+
;;
185+
rc)
186+
yarn run build:expo-update:main:rc
187+
;;
188+
production)
189+
yarn run build:expo-update:main:prod
190+
;;
191+
*)
192+
echo "❌ Unsupported TARGET_CHANNEL: ${TARGET_CHANNEL}" >&2
193+
exit 1
194+
;;
195+
esac
196+
197+
- name: Update summary
198+
if: success()
199+
run: |
200+
PLATFORM_LABEL=$(echo "${{ inputs.platform }}" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')
201+
{
202+
echo "### ✅ EAS Update Published Successfully (${PLATFORM_LABEL})"
203+
echo
204+
echo "**Channel:** \`${EXPO_CHANNEL:-<not set>}\`"
205+
echo "**Message:** ${UPDATE_MESSAGE:-<not set>}"
206+
echo
207+
echo "${PLATFORM_LABEL} users on the \`${EXPO_CHANNEL:-<not set>}\` channel will receive this update on their next app launch."
208+
} >> "$GITHUB_STEP_SUMMARY"
209+
210+
- name: Update summary on failure
211+
if: failure()
212+
run: |
213+
PLATFORM_LABEL=$(echo "${{ inputs.platform }}" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')
214+
{
215+
echo "### ❌ EAS Update Failed (${PLATFORM_LABEL})"
216+
echo
217+
echo "Check the logs above for error details."
218+
} >> "$GITHUB_STEP_SUMMARY"

0 commit comments

Comments
 (0)