Repository navigation
455 lines (422 loc) · 20.1 KB
/
Copy pathmobile-eas-preview.yml
File metadata and controls
455 lines (422 loc) · 20.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
name: Mobile EAS Preview
on:
pull_request:
types: [opened, reopened, synchronize, labeled]
jobs:
preview:
name: EAS Preview (iOS)
# Fork PRs deploy one reviewed commit per label through mobile-eas-preview-fork.yml.
if: |
github.event.pull_request.head.repo.full_name == github.repository &&
contains(github.event.pull_request.labels.*.name, '🚀 Mobile Continuous Deployment') &&
(github.event.action != 'labeled' || github.event.label.name == '🚀 Mobile Continuous Deployment')
runs-on: blacksmith-8vcpu-ubuntu-2404
concurrency:
group: mobile-eas-preview-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
env:
APP_VARIANT: preview
NODE_OPTIONS: --max-old-space-size=8192
MOBILE_VERSION_POLICY: fingerprint
steps:
- id: expo-token
name: Check for EXPO_PREVIEW_TOKEN
env:
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
run: |
if [ -n "$EXPO_TOKEN" ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
echo "EXPO_PREVIEW_TOKEN is not available; skipping EAS preview."
fi
- name: Checkout
if: steps.expo-token.outputs.present == 'true'
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0
# No sparse-checkout here: it makes actions/checkout fetch with
# --filter=blob:none, and eas-cli archives the project via
# `git clone --depth 1 file://<workspace>`, which fails (exit 128)
# when the partial clone can't serve the unfetched blobs.
- name: Setup Vite+
if: steps.expo-token.outputs.present == 'true'
uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1.15.0
with:
node-version-file: package.json
cache: true
run-install: |
args:
- --filter=@t3tools/mobile...
- name: Expose pnpm
if: steps.expo-token.outputs.present == 'true'
run: |
pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")"
vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin"
echo "$vp_pnpm_bin" >> "$GITHUB_PATH"
"$vp_pnpm_bin/pnpm" --version
- name: Setup EAS
if: steps.expo-token.outputs.present == 'true'
uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8.2.1
with:
# eas-cli 22 renamed `project` to `app` in build JSON, and
# continuous-deploy-fingerprint still reads `build.project.ownerAccount`.
# Unpin once https://github.com/expo/expo-github-action/issues/358 is fixed.
eas-version: 21.8.0
token: ${{ secrets.EXPO_PREVIEW_TOKEN }}
# npm, not pnpm: this only installs eas-cli into the action's own
# tool dir, and pnpm 11 hard-fails that install on dtrace-provider's
# ignored build script (no allowBuilds config outside the repo).
packager: npm
- name: Pull preview environment variables
if: steps.expo-token.outputs.present == 'true'
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
run: |
eas env:pull preview --non-interactive
# EAS Update disables dotenv loading. Keep the downloaded native config
# in its process environment so Android build and OTA fingerprints match.
node --input-type=module <<'NODE'
import { appendFileSync, readFileSync } from "node:fs";
import { parseEnv } from "node:util";
const env = parseEnv(readFileSync(".env.local", "utf8"));
const googleServicesFile = env.T3CODE_ANDROID_GOOGLE_SERVICES_FILE;
if (googleServicesFile) {
appendFileSync(process.env.GITHUB_ENV, `T3CODE_ANDROID_GOOGLE_SERVICES_FILE=${googleServicesFile}\n`);
}
NODE
- name: Deploy with fingerprint check
if: steps.expo-token.outputs.present == 'true'
uses: expo/expo-github-action/continuous-deploy-fingerprint@5273c2136bd09c2a341fe3340d3e767ce5a13015 # v9.0.2
env:
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
# The Expo action otherwise suppresses the EAS command's failure output.
RUNNER_DEBUG: "1"
with:
profile: preview:dev
branch: pr-${{ github.event.pull_request.number }}
# Android is the android job below: its client builds on Blacksmith.
platform: ios
environment: preview
working-directory: apps/mobile
github-token: ${{ secrets.GITHUB_TOKEN }}
# Android publishes its own update and comment, because the Expo action can only
# find a client that EAS built. A fingerprint with no client is built by
# android-build with `eas build --local` on Blacksmith (about 4 minutes warm,
# against about 40 on EAS) and recorded as a mobile-android-preview-build/<fingerprint>
# tag whose message is the install link. Clients EAS built earlier still count.
android:
name: EAS Preview (Android)
if: |
github.event.pull_request.head.repo.full_name == github.repository &&
contains(github.event.pull_request.labels.*.name, '🚀 Mobile Continuous Deployment') &&
(github.event.action != 'labeled' || github.event.label.name == '🚀 Mobile Continuous Deployment')
runs-on: blacksmith-8vcpu-ubuntu-2404
concurrency:
group: mobile-eas-preview-android-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
outputs:
build: ${{ steps.client.outputs.build }}
fingerprint: ${{ steps.client.outputs.fingerprint }}
env:
APP_VARIANT: preview
NODE_OPTIONS: --max-old-space-size=8192
MOBILE_VERSION_POLICY: fingerprint
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
steps:
- id: expo-token
name: Check for EXPO_PREVIEW_TOKEN
run: |
if [ -n "$EXPO_TOKEN" ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
echo "EXPO_PREVIEW_TOKEN is not available; skipping the Android preview."
fi
- name: Checkout
if: steps.expo-token.outputs.present == 'true'
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0
# The steps below run the PR's dependency and build scripts; they get no token.
# checkout keeps it in a file under RUNNER_TEMP that .git/config includes, so both
# go. Asking checkout not to persist it fails here: that cleanup runs `git submodule
# foreach`, which stops at the vendored gitlinks under .repos (no .gitmodules entry).
- name: Drop checkout credentials
if: steps.expo-token.outputs.present == 'true'
run: |
for dir in . .trusted; do
[ -e "$dir/.git" ] || continue
git -C "$dir" config --local --get-regexp '^includeif\..*\.path$' | while read -r key file; do
rm -f "$file"
git -C "$dir" config --local --unset-all "$key"
done
git -C "$dir" config --local --name-only --get-regexp '^http\..*\.extraheader$' | while read -r key; do
git -C "$dir" config --local --unset-all "$key"
done
if git -C "$dir" config --local --get-regexp 'extraheader|includeif'; then
echo "::error::checkout credentials are still configured in $dir"
exit 1
fi
done
rm -f "$RUNNER_TEMP"/git-credentials-*.config
if ls "$RUNNER_TEMP"/git-credentials-*.config 2>/dev/null; then
echo "::error::a checkout credential file is still present"
exit 1
fi
- name: Setup Vite+
if: steps.expo-token.outputs.present == 'true'
uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1.15.0
with:
node-version-file: package.json
cache: true
run-install: |
args:
- --filter=@t3tools/mobile...
- name: Expose pnpm
if: steps.expo-token.outputs.present == 'true'
run: |
pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")"
vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin"
echo "$vp_pnpm_bin" >> "$GITHUB_PATH"
"$vp_pnpm_bin/pnpm" --version
- name: Setup EAS
if: steps.expo-token.outputs.present == 'true'
uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8.2.1
with:
eas-version: latest
token: ${{ secrets.EXPO_PREVIEW_TOKEN }}
packager: npm
- name: Pull preview environment variables
if: steps.expo-token.outputs.present == 'true'
working-directory: apps/mobile
run: |
eas env:pull preview --non-interactive
node --input-type=module <<'NODE'
import { appendFileSync, readFileSync } from "node:fs";
import { parseEnv } from "node:util";
const env = parseEnv(readFileSync(".env.local", "utf8"));
const googleServicesFile = env.T3CODE_ANDROID_GOOGLE_SERVICES_FILE;
if (googleServicesFile) {
appendFileSync(process.env.GITHUB_ENV, `T3CODE_ANDROID_GOOGLE_SERVICES_FILE=${googleServicesFile}\n`);
}
NODE
# The same command the Expo action runs, so this is the runtime version the update gets.
- id: client
name: Find the client for this fingerprint
if: steps.expo-token.outputs.present == 'true'
working-directory: apps/mobile
shell: bash
run: |
hash="$(eas env:exec --non-interactive preview "npx expo-updates fingerprint:generate --platform android" | sed -n '/^{/,$p' | jq -er '.hash | select(type == "string" and length > 0)')"
echo "fingerprint=$hash" >> "$GITHUB_OUTPUT"
tag="mobile-android-preview-build/$hash"
link=""
if git fetch --quiet origin "refs/tags/$tag:refs/tags/$tag" 2>/dev/null; then
link="$(git tag -l --format='%(contents:subject)' "$tag")"
else
# A client EAS built before this job existed; expired ones are gone.
link="$(eas build:list --platform android --build-profile preview:dev --runtime-version "$hash" --status finished --limit 5 --json --non-interactive \
| sed -n '/^\[/,$p' \
| jq -r --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '[.[] | select((.expirationDate // "9999") > $now)][0] | select(. != null) | "https://expo.dev/accounts/jayleaton/projects/t3-code-personal/builds/\(.id)"')"
fi
echo "link=$link" >> "$GITHUB_OUTPUT"
if [ -z "$link" ]; then echo "build=true" >> "$GITHUB_OUTPUT"; fi
echo "android fingerprint \`$hash\`: ${link:-no client yet, building on Blacksmith}" >> "$GITHUB_STEP_SUMMARY"
- id: update
name: Publish the update
if: steps.expo-token.outputs.present == 'true'
working-directory: apps/mobile
shell: bash
run: |
group="$(eas update --auto --branch "pr-$PR" --platform android --environment preview --non-interactive --json | sed -n '/^\[/,$p' | jq -er '.[0].group')"
echo "group=$group" >> "$GITHUB_OUTPUT"
- name: Comment
if: steps.expo-token.outputs.present == 'true'
env:
FINGERPRINT: ${{ steps.client.outputs.fingerprint }}
CLIENT: ${{ steps.client.outputs.link }}
GROUP: ${{ steps.update.outputs.group }}
run: node .github/scripts/mobile-preview-comment.cjs
android-build:
name: Android preview client
needs: android
if: needs.android.outputs.build == 'true'
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 60
concurrency:
# One build per fingerprint; a second PR with the same one finds the tag.
group: mobile-android-preview-build-${{ needs.android.outputs.fingerprint }}
cancel-in-progress: false
permissions:
contents: write # pushes the build record tag
pull-requests: write
env:
APP_VARIANT: preview
NODE_OPTIONS: --max-old-space-size=8192
MOBILE_VERSION_POLICY: fingerprint
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
FINGERPRINT: ${{ needs.android.outputs.fingerprint }}
BUILD_TAG: mobile-android-preview-build/${{ needs.android.outputs.fingerprint }}
# Phones only: emulators use the development client from the native-client cache.
ORG_GRADLE_PROJECT_reactNativeArchitectures: armeabi-v7a,arm64-v8a
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0
- id: record
name: Check build record
run: |
if git fetch --quiet origin "refs/tags/$BUILD_TAG:refs/tags/$BUILD_TAG" 2>/dev/null; then
echo "built=true" >> "$GITHUB_OUTPUT"
echo "link=$(git tag -l --format='%(contents:subject)' "$BUILD_TAG")" >> "$GITHUB_OUTPUT"
fi
# The steps below run the PR's dependency and build scripts; they get no token.
# checkout keeps it in a file under RUNNER_TEMP that .git/config includes, so both
# go. Asking checkout not to persist it fails here: that cleanup runs `git submodule
# foreach`, which stops at the vendored gitlinks under .repos (no .gitmodules entry).
- name: Drop checkout credentials
run: |
for dir in . .trusted; do
[ -e "$dir/.git" ] || continue
git -C "$dir" config --local --get-regexp '^includeif\..*\.path$' | while read -r key file; do
rm -f "$file"
git -C "$dir" config --local --unset-all "$key"
done
git -C "$dir" config --local --name-only --get-regexp '^http\..*\.extraheader$' | while read -r key; do
git -C "$dir" config --local --unset-all "$key"
done
if git -C "$dir" config --local --get-regexp 'extraheader|includeif'; then
echo "::error::checkout credentials are still configured in $dir"
exit 1
fi
done
rm -f "$RUNNER_TEMP"/git-credentials-*.config
if ls "$RUNNER_TEMP"/git-credentials-*.config 2>/dev/null; then
echo "::error::a checkout credential file is still present"
exit 1
fi
- name: Prepare build directories
if: steps.record.outputs.built != 'true'
run: |
cache="$HOME/.cache/t3-android-preview"
{
echo "GRADLE_USER_HOME=$cache/gradle"
echo "CCACHE_DIR=$cache/ccache"
echo "CCACHE_MAXSIZE=5G"
# Gradle installs the NDK fresh each run, so key on the compiler's version, not its mtime.
echo "CCACHE_COMPILERCHECK=%compiler% -v"
echo "pnpm_config_store_dir=$cache/pnpm-store"
# eas build --local copies the project here. A fixed path keeps
# ccache keys stable, and the same disk as the store lets pnpm hardlink.
echo "EAS_LOCAL_BUILD_WORKINGDIR=$cache/eas-build"
echo "CCACHE_BASEDIR=$cache/eas-build"
echo "CMAKE_C_COMPILER_LAUNCHER=ccache"
echo "CMAKE_CXX_COMPILER_LAUNCHER=ccache"
} >> "$GITHUB_ENV"
- name: Mount build cache
if: steps.record.outputs.built != 'true'
uses: useblacksmith/stickydisk@3f6be1451e3cad893b778bc93495c80452a5da26 # v1.7.2
with:
key: ${{ github.repository }}-android-preview-build
path: ~/.cache/t3-android-preview
- name: Configure Gradle and ccache
if: steps.record.outputs.built != 'true'
run: |
mkdir -p "$GRADLE_USER_HOME" "$CCACHE_DIR"
rm -rf "$EAS_LOCAL_BUILD_WORKINGDIR"
# No daemon: nothing should hold the sticky disk open when it is committed.
printf 'org.gradle.caching=true\norg.gradle.daemon=false\n' > "$GRADLE_USER_HOME/gradle.properties"
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update -qq && sudo apt-get install -y -qq ccache
ccache --zero-stats
- name: Setup Node
if: steps.record.outputs.built != 'true'
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version-file: package.json
- name: Enable corepack
if: steps.record.outputs.built != 'true'
run: |
npm install --global corepack@latest
corepack enable
pnpm --version
- name: Install mobile dependencies
if: steps.record.outputs.built != 'true'
run: pnpm install --frozen-lockfile --prefer-offline --filter=@t3tools/mobile...
- name: Setup Java
if: steps.record.outputs.built != 'true'
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: temurin
java-version: 17
- name: Setup EAS
if: steps.record.outputs.built != 'true'
uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8.2.1
with:
eas-version: latest
token: ${{ secrets.EXPO_PREVIEW_TOKEN }}
packager: npm
- name: Pull preview environment variables
if: steps.record.outputs.built != 'true'
working-directory: apps/mobile
run: |
eas env:pull preview --non-interactive
node --input-type=module <<'NODE'
import { appendFileSync, readFileSync } from "node:fs";
import { parseEnv } from "node:util";
const env = parseEnv(readFileSync(".env.local", "utf8"));
const googleServicesFile = env.T3CODE_ANDROID_GOOGLE_SERVICES_FILE;
if (googleServicesFile) {
appendFileSync(process.env.GITHUB_ENV, `T3CODE_ANDROID_GOOGLE_SERVICES_FILE=${googleServicesFile}\n`);
}
NODE
# `eas upload` hosts the APK on EAS and returns the page testers install from.
- id: build
name: Build and upload
if: steps.record.outputs.built != 'true'
working-directory: apps/mobile
shell: bash
run: |
eas build --local --platform android --profile preview:dev --non-interactive --output "$RUNNER_TEMP/t3code-preview.apk"
eas upload --platform android --build-path "$RUNNER_TEMP/t3code-preview.apk" --fingerprint "$FINGERPRINT" --non-interactive --json | tee "$RUNNER_TEMP/upload.json"
link="$(sed -n '/^{/,$p' "$RUNNER_TEMP/upload.json" | jq -r '.url // empty')"
if [ -z "$link" ]; then
echo "::error::eas upload returned no install link"
exit 1
fi
echo "link=$link" >> "$GITHUB_OUTPUT"
{
echo "### Android preview client for \`$FINGERPRINT\`: $link"
echo '```'
ccache --show-stats
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
# The tag is the record later runs look up; its message is the install link.
- name: Record build
if: steps.record.outputs.built != 'true'
env:
LINK: ${{ steps.build.outputs.link }}
GH_TOKEN: ${{ github.token }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$BUILD_TAG" -m "$LINK"
# Only this step has the token; the checkout's copy was removed before the build.
git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "refs/tags/$BUILD_TAG"
- name: Comment
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
CLIENT: ${{ steps.build.outputs.link || steps.record.outputs.link }}
run: node .github/scripts/mobile-preview-comment.cjs