|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Rebuild the Couchers Dev Tool native client on EAS when its fingerprint changes. |
| 3 | +# |
| 4 | +# The Dev Tool dev client only needs a fresh native build when the Expo |
| 5 | +# fingerprint (runtimeVersion) changes — a new native module, an app.config.js |
| 6 | +# change, or an Expo SDK bump. Pure JS/TS changes load over the air (see |
| 7 | +# docs/mobile-dev-tool-ota.md), so this no-ops on them. We record the last-built |
| 8 | +# fingerprint per platform in S3 and compare; on a change we build a fresh client |
| 9 | +# and update the marker only once that succeeds, so a failed build is retried. |
| 10 | +# |
| 11 | +# iOS: eas build --auto-submit -> TestFlight (Apple's tester channel). |
| 12 | +# Android: eas build -> a sideloadable APK we host ourselves. Google Play has no |
| 13 | +# TestFlight-equivalent for a dev-client APK (Play distributes AABs |
| 14 | +# through release tracks, not downloadable installers), so we publish |
| 15 | +# the APK to the dev-assets bucket at a stable URL devs bookmark. |
| 16 | +# |
| 17 | +# Usage: devtool-build.sh <ios|android> |
| 18 | +# Env: EXPO_TOKEN EAS auth (build + submit scope) |
| 19 | +# AWS_PREVIEW_BUCKET the couchers-dev-assets bucket |
| 20 | +# PREVIEW_DOMAIN the dev-assets CDN domain (preview.couchershq.org) |
| 21 | +# CI_COMMIT_SHORT_SHA tags the immutable APK filename |
| 22 | +# plus the AWS creds the aws CLI reads from the environment |
| 23 | +# Run from app/mobile (eas.json, the project, and node_modules must be present). |
| 24 | +set -euo pipefail |
| 25 | + |
| 26 | +PLATFORM="${1:?usage: devtool-build.sh <ios|android>}" |
| 27 | +case "$PLATFORM" in |
| 28 | + ios | android) ;; |
| 29 | + *) |
| 30 | + echo "platform must be ios or android (got $PLATFORM)" >&2 |
| 31 | + exit 1 |
| 32 | + ;; |
| 33 | +esac |
| 34 | + |
| 35 | +MARKER="s3://${AWS_PREVIEW_BUCKET}/devtool-builds/${PLATFORM}.fingerprint" |
| 36 | + |
| 37 | +# Read a single JSON field from stdin with node (no jq in the node:22 image). |
| 38 | +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"; } |
| 39 | + |
| 40 | +# Fingerprint of the working tree — the same value EAS stamps as runtimeVersion. |
| 41 | +# .fingerprintignore keeps this reproducible (google-services.json is an EAS-only |
| 42 | +# secret), so the CI-computed hash matches what the EAS build will compute. |
| 43 | +CURRENT="$(npx expo-updates fingerprint:generate --platform "$PLATFORM" 2>/dev/null | json_field '.hash')" |
| 44 | +echo "current $PLATFORM fingerprint: $CURRENT" |
| 45 | + |
| 46 | +PREVIOUS="$(aws s3 cp "$MARKER" - 2>/dev/null || true)" |
| 47 | +echo "last-built $PLATFORM fingerprint: ${PREVIOUS:-<none>}" |
| 48 | + |
| 49 | +if [ -n "$PREVIOUS" ] && [ "$PREVIOUS" = "$CURRENT" ]; then |
| 50 | + echo "Fingerprint unchanged for $PLATFORM — installed Dev Tool client still current, skipping build." |
| 51 | + exit 0 |
| 52 | +fi |
| 53 | + |
| 54 | +echo "Fingerprint changed for $PLATFORM — building a new Dev Tool client." |
| 55 | + |
| 56 | +if [ "$PLATFORM" = "ios" ]; then |
| 57 | + # Store distribution + auto-submit lands the build in TestFlight; devs update there. |
| 58 | + eas build --platform ios --profile devtool --auto-submit --non-interactive |
| 59 | +else |
| 60 | + # devtool-apk: internal-distribution APK dev client (extends devtool). Not |
| 61 | + # submitted anywhere — we download the artifact and host it ourselves. |
| 62 | + BUILD_JSON="$(eas build --platform android --profile devtool-apk --non-interactive --json)" |
| 63 | + APK_URL="$(printf '%s' "$BUILD_JSON" | json_field '[0].artifacts.applicationArchiveUrl')" |
| 64 | + [ -n "$APK_URL" ] || { |
| 65 | + echo "could not find the APK artifact URL in the eas build output" >&2 |
| 66 | + exit 1 |
| 67 | + } |
| 68 | + echo "APK artifact: $APK_URL" |
| 69 | + curl -fSL "$APK_URL" -o couchers-devtool.apk |
| 70 | + |
| 71 | + # Immutable per-commit APK (cache forever, no CDN invalidation — matching the OTA |
| 72 | + # infra), plus a stable landing page devs bookmark that links to the current one. |
| 73 | + PREFIX="devtool-builds/android" |
| 74 | + APK_NAME="couchers-devtool-${CI_COMMIT_SHORT_SHA}.apk" |
| 75 | + HOST="android--devtool-builds.${PREVIEW_DOMAIN}" |
| 76 | + aws s3 cp couchers-devtool.apk "s3://${AWS_PREVIEW_BUCKET}/${PREFIX}/${APK_NAME}" \ |
| 77 | + --content-type application/vnd.android.package-archive |
| 78 | + |
| 79 | + cat > index.html <<HTML |
| 80 | +<!doctype html> |
| 81 | +<html lang="en"> |
| 82 | +<head> |
| 83 | +<meta charset="utf-8"> |
| 84 | +<meta name="viewport" content="width=device-width, initial-scale=1"> |
| 85 | +<title>Couchers Dev Tool — Android</title> |
| 86 | +</head> |
| 87 | +<body style="font-family: sans-serif; max-width: 32rem; margin: 3rem auto; padding: 0 1rem"> |
| 88 | +<h1>Couchers Dev Tool (Android)</h1> |
| 89 | +<p><a href="./${APK_NAME}" style="font-size: 1.25rem">Download the latest APK</a></p> |
| 90 | +<p>Built from commit <code>${CI_COMMIT_SHORT_SHA}</code>. Enable "install from unknown |
| 91 | +sources" to sideload. Re-open this page to grab newer builds.</p> |
| 92 | +<p>iOS testers: the Dev Tool ships through TestFlight, not here.</p> |
| 93 | +</body> |
| 94 | +</html> |
| 95 | +HTML |
| 96 | + # Stable pointer — overwritten each build, so revalidate rather than cache. |
| 97 | + aws s3 cp index.html "s3://${AWS_PREVIEW_BUCKET}/${PREFIX}/index.html" \ |
| 98 | + --content-type "text/html; charset=utf-8" --cache-control "no-cache" |
| 99 | + echo "APK published: https://${HOST}/${APK_NAME}" |
| 100 | + echo "Stable page: https://${HOST}/index.html" |
| 101 | +fi |
| 102 | + |
| 103 | +# Record the new fingerprint only after a successful build (and publish), so a |
| 104 | +# failed build is retried on the next pipeline instead of being marked as done. |
| 105 | +printf '%s' "$CURRENT" | aws s3 cp - "$MARKER" |
| 106 | +echo "Recorded $PLATFORM fingerprint $CURRENT — Dev Tool client is up to date." |
0 commit comments