Skip to content

Commit 6dafba0

Browse files
committed
Auto-build and submit the production app on fingerprint change
1 parent 6f421d3 commit 6dafba0

2 files changed

Lines changed: 105 additions & 0 deletions

File tree

app/.gitlab-ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,47 @@ deploy:mobile-ota-staging:
10551055
# TEMP: run on this branch to validate the pipeline; revert before merge.
10561056
- if: $CI_COMMIT_BRANCH == "mobile/v1.1.20"
10571057

1058+
# Rebuild and submit the *production* store app when its fingerprint changes — the
1059+
# native counterpart to the OTA publish below. A signed OTA only applies to an
1060+
# installed build whose runtimeVersion (Expo fingerprint) matches, so a native dep
1061+
# / app.config.js / Expo SDK change (the ~5% case OTA can't serve) needs a fresh
1062+
# store build carrying the new fingerprint. scripts/production-build.sh builds on
1063+
# EAS and auto-submits:
1064+
# iOS -> App Store Connect (lands in TestFlight + as an available build; it
1065+
# is NOT submitted for App Store review and no release notes change —
1066+
# do that by hand in App Store Connect).
1067+
# Android -> the Play *internal* testing track (submit profile `production`,
1068+
# track: internal); promote to public production by hand in the Play
1069+
# Console.
1070+
# Neither path makes anything public on its own. It no-ops when the freshly
1071+
# computed fingerprint matches the last-built app, recorded per platform under
1072+
# s3://$AWS_PREVIEW_BUCKET/production-builds/. Requires the EXPO_TOKEN CI variable.
1073+
build:production-native:
1074+
needs: ["protos"]
1075+
stage: build
1076+
image: node:22
1077+
inherit:
1078+
default: false
1079+
variables:
1080+
# Compute the fingerprint as the production variant so it matches the EAS build
1081+
# (app.config.js branches on APP_VARIANT) and the OTA publish job's fingerprint.
1082+
APP_VARIANT: production
1083+
before_script:
1084+
- apt-get update -qq && apt-get install -y --no-install-recommends awscli curl
1085+
script:
1086+
- cd app/mobile
1087+
- npm ci
1088+
- npm run build:protos
1089+
- npm install -g eas-cli
1090+
# Separate lines (not a loop) so an iOS failure fails the job instead of being
1091+
# masked by a later Android success.
1092+
- bash scripts/production-build.sh ios
1093+
- bash scripts/production-build.sh android
1094+
rules:
1095+
- if: ($BUILD_MOBILE == "true") && ($CI_COMMIT_BRANCH == $RELEASE_BRANCH)
1096+
# TEMP: run on this branch to validate the pipeline; revert before merge.
1097+
- if: $CI_COMMIT_BRANCH == "mobile/v1.1.20"
1098+
10581099
# ─── Production native OTA publish (decoupled from the backend/web deploy) ───
10591100
#
10601101
# Mirrors the web static release flow (build a bundle -> upload a tarball to the
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env bash
2+
# Rebuild and submit the production store app on EAS when its fingerprint changes.
3+
#
4+
# A signed OTA only applies to an installed build whose runtimeVersion (the Expo
5+
# fingerprint) matches it. So when a native change moves the production
6+
# fingerprint — a new native module, an app.config.js change, or an Expo SDK bump
7+
# — we need a fresh store build carrying it before an OTA on that fingerprint can
8+
# land; pure JS/TS changes ship over the air. We record the last-built fingerprint
9+
# per platform in S3 and compare; on a change we build and auto-submit, recording
10+
# the marker only once that succeeds so a failed build is retried.
11+
#
12+
# iOS: eas build --auto-submit -> App Store Connect. The build lands in
13+
# TestFlight and as an available build; it is NOT submitted for App
14+
# Store review and no release notes are changed — do that by hand.
15+
# Android: eas build --auto-submit -> the Play *internal* testing track (the
16+
# `production` submit profile sets track: internal). Promote to the
17+
# public production track by hand in the Play Console.
18+
#
19+
# Neither platform makes anything public on its own.
20+
#
21+
# Usage: production-build.sh <ios|android>
22+
# Env: EXPO_TOKEN EAS auth (build + submit scope)
23+
# AWS_PREVIEW_BUCKET the couchers-dev-assets bucket (holds the markers)
24+
# plus the AWS creds the aws CLI reads from the environment
25+
# Run from app/mobile (eas.json, the project, and node_modules must be present).
26+
set -euo pipefail
27+
28+
PLATFORM="${1:?usage: production-build.sh <ios|android>}"
29+
case "$PLATFORM" in
30+
ios | android) ;;
31+
*)
32+
echo "platform must be ios or android (got $PLATFORM)" >&2
33+
exit 1
34+
;;
35+
esac
36+
37+
MARKER="s3://${AWS_PREVIEW_BUCKET}/production-builds/${PLATFORM}.fingerprint"
38+
39+
# Read a single JSON field from stdin with node (no jq in the node:22 image).
40+
json_field() { node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const j=JSON.parse(s);process.stdout.write(String(eval("j"+process.argv[1])??""))})' "$1"; }
41+
42+
# Fingerprint of the working tree — the same value EAS stamps as runtimeVersion
43+
# and the OTA publish job bakes into the manifest. APP_VARIANT=production (set by
44+
# the job) selects the production bundle id / scheme / signed updates config.
45+
CURRENT="$(npx expo-updates fingerprint:generate --platform "$PLATFORM" 2>/dev/null | json_field '.hash')"
46+
echo "current $PLATFORM fingerprint: $CURRENT"
47+
48+
PREVIOUS="$(aws s3 cp "$MARKER" - 2>/dev/null || true)"
49+
echo "last-built $PLATFORM fingerprint: ${PREVIOUS:-<none>}"
50+
51+
if [ -n "$PREVIOUS" ] && [ "$PREVIOUS" = "$CURRENT" ]; then
52+
echo "Fingerprint unchanged for $PLATFORM — the live production build still matches, skipping."
53+
exit 0
54+
fi
55+
56+
echo "Fingerprint changed for $PLATFORM — building and submitting a new production app."
57+
# --auto-submit uses the submit profile named like the build profile (production):
58+
# iOS -> App Store Connect, Android -> the internal track.
59+
eas build --platform "$PLATFORM" --profile production --auto-submit --non-interactive
60+
61+
# Record the new fingerprint only after a successful build+submit, so a failure is
62+
# retried on the next pipeline instead of being marked done.
63+
printf '%s' "$CURRENT" | aws s3 cp - "$MARKER"
64+
echo "Recorded $PLATFORM fingerprint $CURRENT — production build is up to date."

0 commit comments

Comments
 (0)