Skip to content

Commit e3c70b2

Browse files
KillerXclaude
andcommitted
Add kids CI pipeline and scope builds with change_in()
Mirrors the bccm Prep/Build/Deploy + production-promotion pipelines for the kids flavor, and gates every block (bccm + kids) with change_in() so a PR that only touches one app's tree no longer rebuilds the other. lib/, pubspec.yaml, submodules/, Makefile, and .semaphore/ are deliberately shared triggers — kids consumes lib/ directly via `brunstadtv_app: path: ../`. Also parameterizes scripts/refresh-ios-signing-secrets.sh with a bccm|kids flavor argument so it can refresh either Semaphore signing-cert secret. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ee4915d commit e3c70b2

7 files changed

Lines changed: 758 additions & 15 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version: v1.0
2+
name: Promote kids Android to Production
3+
agent:
4+
machine:
5+
type: f1-standard-4
6+
os_image: ubuntu2404
7+
blocks:
8+
- name: Promote AAB beta to production
9+
task:
10+
jobs:
11+
- name: Promote on Play Store
12+
commands:
13+
- checkout
14+
- sem-version ruby 3.2
15+
- cp /home/semaphore/google-play-service-account.json kids/android/fastlane/google-play-service-account.json
16+
- artifact pull workflow kids_build_number
17+
- export BUILD_NUMBER=$(cat kids_build_number)
18+
- test -n "$BUILD_NUMBER" && test "$BUILD_NUMBER" -gt 0
19+
- cd kids/android
20+
- bundle install
21+
- bundle exec fastlane promote_to_production
22+
secrets:
23+
- name: android-play-store-api-linux
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
version: v1.0
2+
name: Promote kids iOS to App Store
3+
agent:
4+
machine:
5+
type: a2-standard-4
6+
os_image: macos-xcode26
7+
blocks:
8+
- name: Submit TestFlight build to App Store
9+
task:
10+
jobs:
11+
- name: Promote on App Store Connect
12+
commands:
13+
- checkout
14+
- sem-version ruby 3.2
15+
- export APP_STORE_CONNECT_KEY_ID="$BCCMEDIA_APP_STORE_CONNECT_KEY_ID"
16+
- export APP_STORE_CONNECT_ISSUER_ID="$BCCMEDIA_APP_STORE_CONNECT_ISSUER_ID"
17+
- export APP_STORE_CONNECT_KEY_CONTENT_BASE64="$BCCMEDIA_APP_STORE_CONNECT_KEY_CONTENT_BASE64"
18+
- artifact pull workflow kids_ios_build_number
19+
- export BUILD_NUMBER=$(cat kids_ios_build_number)
20+
- test -n "$BUILD_NUMBER" && test "$BUILD_NUMBER" -gt 0
21+
- export APP_VERSION=$(grep '^version:' kids/pubspec.yaml | awk '{print $2}' | cut -d+ -f1)
22+
- test -n "$APP_VERSION"
23+
- cd kids/ios
24+
- bundle install
25+
- bundle exec fastlane promote_to_app_store
26+
secrets:
27+
- name: bccmedia-testflight-api

.semaphore/semaphore.yml

Lines changed: 245 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,16 @@ global_job_config:
88
env_vars:
99
- name: FLUTTER_VERSION
1010
value: 3.32.0
11+
# Each block is gated with change_in() so a PR that only touches kids/ skips
12+
# the bccm builds and vice versa. lib/, pubspec.yaml, submodules/, Makefile,
13+
# and .semaphore/ are shared infra — a change there triggers BOTH apps. kids
14+
# also depends on lib/ directly because kids/pubspec.yaml uses
15+
# `brunstadtv_app: path: ../`.
1116
blocks:
1217
- name: Prep bccm version
1318
dependencies: []
19+
run:
20+
when: "branch = 'master' AND change_in(['/lib/', '/android/', '/ios/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
1421
task:
1522
jobs:
1623
- name: Compute bccm versionCode
@@ -27,11 +34,11 @@ blocks:
2734
- artifact push workflow /tmp/bccm_build_number -d bccm_build_number
2835
secrets:
2936
- name: android-play-store-api-linux
30-
run:
31-
when: branch = 'master'
3237
- name: Build bccm Android
3338
dependencies:
3439
- Prep bccm version
40+
run:
41+
when: "change_in(['/lib/', '/android/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
3542
task:
3643
prologue:
3744
commands:
@@ -88,7 +95,7 @@ blocks:
8895
dependencies:
8996
- Build bccm Android
9097
run:
91-
when: branch = 'master'
98+
when: "branch = 'master' AND change_in(['/lib/', '/android/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
9299
task:
93100
jobs:
94101
- name: Upload to Play (internal + beta)
@@ -108,6 +115,8 @@ blocks:
108115
- name: android-play-store-api-linux
109116
- name: Prep bccm iOS version
110117
dependencies: []
118+
run:
119+
when: "change_in(['/lib/', '/ios/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
111120
task:
112121
jobs:
113122
- name: Compute bccm iOS build number
@@ -126,6 +135,8 @@ blocks:
126135
- name: Build bccm iOS
127136
dependencies:
128137
- Prep bccm iOS version
138+
run:
139+
when: "change_in(['/lib/', '/ios/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
129140
task:
130141
agent:
131142
machine:
@@ -215,7 +226,7 @@ blocks:
215226
dependencies:
216227
- Build bccm iOS
217228
run:
218-
when: branch = 'master'
229+
when: "branch = 'master' AND change_in(['/lib/', '/ios/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
219230
task:
220231
agent:
221232
machine:
@@ -234,8 +245,238 @@ blocks:
234245
- bundle exec fastlane testflight
235246
secrets:
236247
- name: bccmedia-testflight-api
248+
- name: Prep kids version
249+
dependencies: []
250+
run:
251+
when: "branch = 'master' AND change_in(['/kids/', '/lib/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
252+
task:
253+
jobs:
254+
- name: Compute kids versionCode
255+
commands:
256+
- checkout
257+
- sem-version ruby 3.2
258+
- cp /home/semaphore/google-play-service-account.json kids/android/fastlane/google-play-service-account.json
259+
- cd kids/android
260+
- bundle install
261+
- bundle exec fastlane next_version_code
262+
- export BUILD_NUMBER=$(cat fastlane/.next_version_code)
263+
- test -n "$BUILD_NUMBER" && test "$BUILD_NUMBER" -gt 0
264+
- echo "$BUILD_NUMBER" > /tmp/kids_build_number
265+
- artifact push workflow /tmp/kids_build_number -d kids_build_number
266+
secrets:
267+
- name: android-play-store-api-linux
268+
- name: Build kids Android
269+
dependencies:
270+
- Prep kids version
271+
run:
272+
when: "change_in(['/kids/', '/lib/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
273+
task:
274+
prologue:
275+
commands:
276+
- checkout
277+
- git submodule update --init --recursive
278+
- echo "$BCCMEDIA_ENV_DART_BASE64" | base64 -d > lib/env/.env.dart
279+
- sem-version java 17
280+
- cache restore flutter-sdk-linux-$FLUTTER_VERSION
281+
- |
282+
if [ ! -d "$HOME/flutter" ]; then
283+
wget -q https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz -O /tmp/flutter.tar.xz
284+
tar xf /tmp/flutter.tar.xz -C $HOME
285+
fi
286+
- cache store flutter-sdk-linux-$FLUTTER_VERSION $HOME/flutter
287+
- 'export PATH="$HOME/flutter/bin:$PATH"'
288+
- flutter --version
289+
- export ANDROID_HOME="$HOME/android-sdk"
290+
- cache restore android-sdk
291+
- |
292+
if [ ! -d "$ANDROID_HOME/platforms/android-36" ]; then
293+
mkdir -p "$ANDROID_HOME/cmdline-tools"
294+
curl -sSL -o /tmp/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
295+
unzip -q /tmp/cmdline-tools.zip -d "$ANDROID_HOME/cmdline-tools"
296+
mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest"
297+
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses > /dev/null
298+
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" "platform-tools" "platforms;android-36" "build-tools;36.0.0"
299+
fi
300+
- cache store android-sdk $ANDROID_HOME
301+
- 'export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"'
302+
jobs:
303+
- name: Build AAB
304+
commands:
305+
- artifact pull workflow kids_build_number
306+
- export BUILD_NUMBER=$(cat kids_build_number)
307+
- echo "Building with versionCode=$BUILD_NUMBER"
308+
- keystore_path="$(pwd)/kids/android/app/upload-keystore.jks"
309+
- echo "$KEYSTORE_FILE" | base64 -d > "$keystore_path"
310+
- |
311+
cat > kids/android/key.properties << EOF
312+
storePassword=$KEYSTORE_PASSWORD
313+
keyPassword=$KEY_PASSWORD
314+
keyAlias=$KEY_ALIAS
315+
storeFile=$keystore_path
316+
EOF
317+
- 'cache restore pub-linux-kids-$SEMAPHORE_GIT_BRANCH-$(checksum kids/pubspec.yaml),pub-linux-kids-$(checksum kids/pubspec.yaml),pub-linux-kids'
318+
- make pubgetall
319+
- cache store pub-linux-kids-$SEMAPHORE_GIT_BRANCH-$(checksum kids/pubspec.yaml) $HOME/.pub-cache
320+
- cd kids
321+
- flutter build appbundle --release -t lib/main.dart --build-number=$BUILD_NUMBER
322+
- artifact push workflow build/app/outputs/bundle/release/app-release.aab -d kids.aab
323+
secrets:
324+
- name: kids-android-keystore
325+
- name: bccmedia-env-dart
326+
- name: Deploy kids Android
327+
dependencies:
328+
- Build kids Android
329+
run:
330+
when: "branch = 'master' AND change_in(['/kids/', '/lib/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
331+
task:
332+
jobs:
333+
- name: Upload to Play (internal + beta)
334+
commands:
335+
- checkout
336+
- sem-version ruby 3.2
337+
- cp /home/semaphore/google-play-service-account.json kids/android/fastlane/google-play-service-account.json
338+
- mkdir -p kids/build/app/outputs/bundle/release
339+
- artifact pull workflow kids.aab
340+
- mv kids.aab kids/build/app/outputs/bundle/release/app-release.aab
341+
- artifact pull workflow kids_build_number
342+
- export BUILD_NUMBER=$(cat kids_build_number)
343+
- cd kids/android
344+
- bundle install
345+
- bundle exec fastlane upload
346+
secrets:
347+
- name: android-play-store-api-linux
348+
- name: Prep kids iOS version
349+
dependencies: []
350+
run:
351+
when: "change_in(['/kids/', '/lib/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
352+
task:
353+
jobs:
354+
- name: Compute kids iOS build number
355+
commands:
356+
- checkout
357+
- sem-version ruby 3.2
358+
# The shared App Store Connect API key is exposed by the secret under
359+
# BCCMEDIA_-prefixed env vars. Kids fastlane reads the unprefixed names,
360+
# so re-export them here (and in the build/deploy/promote jobs).
361+
- export APP_STORE_CONNECT_KEY_ID="$BCCMEDIA_APP_STORE_CONNECT_KEY_ID"
362+
- export APP_STORE_CONNECT_ISSUER_ID="$BCCMEDIA_APP_STORE_CONNECT_ISSUER_ID"
363+
- export APP_STORE_CONNECT_KEY_CONTENT_BASE64="$BCCMEDIA_APP_STORE_CONNECT_KEY_CONTENT_BASE64"
364+
- cd kids/ios
365+
- bundle install
366+
- bundle exec fastlane next_build_number
367+
- export BUILD_NUMBER=$(cat fastlane/.next_build_number)
368+
- test -n "$BUILD_NUMBER" && test "$BUILD_NUMBER" -gt 0
369+
- echo "$BUILD_NUMBER" > /tmp/kids_ios_build_number
370+
- artifact push workflow /tmp/kids_ios_build_number -d kids_ios_build_number
371+
secrets:
372+
- name: bccmedia-testflight-api
373+
- name: Build kids iOS
374+
dependencies:
375+
- Prep kids iOS version
376+
run:
377+
when: "change_in(['/kids/', '/lib/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
378+
task:
379+
agent:
380+
machine:
381+
type: a2-standard-4
382+
os_image: macos-xcode26
383+
prologue:
384+
commands:
385+
- checkout
386+
- git submodule update --init --recursive
387+
- echo "$BCCMEDIA_ENV_DART_BASE64" | base64 -d > lib/env/.env.dart
388+
- cache restore flutter-sdk-macos-$FLUTTER_VERSION
389+
- |
390+
if [ ! -d "$HOME/flutter" ]; then
391+
curl -sSL -o /tmp/flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_${FLUTTER_VERSION}-stable.tar.xz
392+
tar xf /tmp/flutter.tar.xz -C $HOME
393+
fi
394+
- cache store flutter-sdk-macos-$FLUTTER_VERSION $HOME/flutter
395+
- 'export PATH="$HOME/flutter/bin:$PATH"'
396+
- flutter --version
397+
jobs:
398+
- name: Build IPA
399+
commands:
400+
- artifact pull workflow kids_ios_build_number
401+
- export BUILD_NUMBER=$(cat kids_ios_build_number)
402+
- echo "Building with iOS build number=$BUILD_NUMBER"
403+
- echo "$CERTIFICATE_P12_BASE64" | base64 -d > /tmp/certificate.p12
404+
- security create-keychain -p "" build.keychain
405+
- security import /tmp/certificate.p12 -k ~/Library/Keychains/build.keychain-db -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
406+
- curl -fsSL https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer -o /tmp/AppleWWDRCAG3.cer
407+
- security import /tmp/AppleWWDRCAG3.cer -k ~/Library/Keychains/build.keychain-db -T /usr/bin/codesign || true
408+
- security list-keychains -s ~/Library/Keychains/build.keychain-db
409+
- security default-keychain -s ~/Library/Keychains/build.keychain-db
410+
- security unlock-keychain -p "" ~/Library/Keychains/build.keychain-db
411+
- security set-keychain-settings ~/Library/Keychains/build.keychain-db
412+
- 'security set-key-partition-list -S apple-tool:,apple: -s -k "" ~/Library/Keychains/build.keychain-db'
413+
- 'export CODE_SIGN_IDENTITY=$(security find-identity -v -p codesigning | grep -o ''"[^"]*"'' | head -1 | tr -d ''"'')'
414+
- 'echo "Signing identity: $CODE_SIGN_IDENTITY"'
415+
- |
416+
if [ -z "$CODE_SIGN_IDENTITY" ]; then
417+
echo "ERROR: no valid code-signing identity in build.keychain."
418+
security find-identity -p codesigning ~/Library/Keychains/build.keychain-db || true
419+
exit 1
420+
fi
421+
- mkdir -p "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles" "$HOME/Library/MobileDevice/Provisioning Profiles"
422+
- echo "$PROVISIONING_PROFILE_BASE64" | base64 -d > "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/profile.mobileprovision"
423+
- cp "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/profile.mobileprovision" "$HOME/Library/MobileDevice/Provisioning Profiles/profile.mobileprovision"
424+
- export PROVISIONING_PROFILE_UUID=$(security cms -D -i "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/profile.mobileprovision" | grep -A 1 "UUID" | grep -o '<string>.*</string>' | sed 's/<string>\(.*\)<\/string>/\1/')
425+
- 'echo "Provisioning profile UUID: $PROVISIONING_PROFILE_UUID"'
426+
- echo "CODE_SIGN_STYLE = Manual" >> kids/ios/Flutter/Release.xcconfig
427+
- echo "PROVISIONING_PROFILE = $PROVISIONING_PROFILE_UUID" >> kids/ios/Flutter/Release.xcconfig
428+
- echo "CODE_SIGN_IDENTITY = $CODE_SIGN_IDENTITY" >> kids/ios/Flutter/Release.xcconfig
429+
- 'cache restore pub-macos-kids-$SEMAPHORE_GIT_BRANCH-$(checksum kids/pubspec.yaml),pub-macos-kids-$(checksum kids/pubspec.yaml),pub-macos-kids'
430+
- make pubgetall
431+
- cache store pub-macos-kids-$SEMAPHORE_GIT_BRANCH-$(checksum kids/pubspec.yaml) $HOME/.pub-cache
432+
- 'cache restore cocoapods-kids-$SEMAPHORE_GIT_BRANCH-$(checksum kids/ios/Podfile.lock),cocoapods-kids-$(checksum kids/ios/Podfile.lock),cocoapods-kids'
433+
- 'export DART_VM_OPTIONS="--old_gen_heap_size=6144"'
434+
- cd kids
435+
- flutter build ipa --release -t lib/main.dart --build-number=$BUILD_NUMBER --export-options-plist=ios/ExportOptions.plist
436+
- cd ..
437+
- cache store cocoapods-kids-$SEMAPHORE_GIT_BRANCH-$(checksum kids/ios/Podfile.lock) kids/ios/Pods
438+
- |
439+
shopt -s nullglob
440+
ipas=(kids/build/ios/ipa/*.ipa)
441+
echo "Built IPA(s): ${ipas[*]:-<none>}"
442+
if [ ${#ipas[@]} -eq 0 ]; then echo "ERROR: no .ipa produced by flutter build ipa"; exit 1; fi
443+
[ "${ipas[0]}" = kids/build/ios/ipa/kids.ipa ] || mv "${ipas[0]}" kids/build/ios/ipa/kids.ipa
444+
- artifact push workflow kids/build/ios/ipa/kids.ipa -d kids.ipa
445+
secrets:
446+
- name: kids-ios-signing-certs
447+
- name: bccmedia-env-dart
448+
- name: Deploy kids iOS
449+
dependencies:
450+
- Build kids iOS
451+
run:
452+
when: "branch = 'master' AND change_in(['/kids/', '/lib/', '/pubspec.yaml', '/pubspec.lock', '/submodules/', '/Makefile', '/.semaphore/'], {default_branch: 'master', pipeline_file: 'ignore'})"
453+
task:
454+
agent:
455+
machine:
456+
type: a2-standard-4
457+
os_image: macos-xcode26
458+
jobs:
459+
- name: Upload to TestFlight (internal + Beta)
460+
commands:
461+
- checkout
462+
- sem-version ruby 3.2
463+
- export APP_STORE_CONNECT_KEY_ID="$BCCMEDIA_APP_STORE_CONNECT_KEY_ID"
464+
- export APP_STORE_CONNECT_ISSUER_ID="$BCCMEDIA_APP_STORE_CONNECT_ISSUER_ID"
465+
- export APP_STORE_CONNECT_KEY_CONTENT_BASE64="$BCCMEDIA_APP_STORE_CONNECT_KEY_CONTENT_BASE64"
466+
- mkdir -p kids/build/ios/ipa
467+
- artifact pull workflow kids.ipa
468+
- mv kids.ipa kids/build/ios/ipa/kids.ipa
469+
- cd kids/ios
470+
- bundle install
471+
- bundle exec fastlane testflight
472+
secrets:
473+
- name: bccmedia-testflight-api
237474
promotions:
238475
- name: Promote Android to Production
239476
pipeline_file: promote-android-prod.yml
240477
- name: Promote iOS to App Store
241478
pipeline_file: promote-ios-prod.yml
479+
- name: Promote kids Android to Production
480+
pipeline_file: promote-kids-android-prod.yml
481+
- name: Promote kids iOS to App Store
482+
pipeline_file: promote-kids-ios-prod.yml

0 commit comments

Comments
 (0)