Add nightly iOS/Android build workflow (#10940)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,19 @@ on:
|
|||||||
options:
|
options:
|
||||||
- testflight-android
|
- testflight-android
|
||||||
- production
|
- production
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
profile:
|
||||||
|
type: string
|
||||||
|
description: Build profile to use
|
||||||
|
required: true
|
||||||
|
outputs:
|
||||||
|
package-version:
|
||||||
|
description: Version from package.json
|
||||||
|
value: ${{ jobs.build.outputs.package-version }}
|
||||||
|
version-code:
|
||||||
|
description: Android version code
|
||||||
|
value: ${{ jobs.build.outputs.version-code }}
|
||||||
|
|
||||||
# Deploys happen via EAS using EXPO_TOKEN; the GITHUB_TOKEN only checks out code
|
# Deploys happen via EAS using EXPO_TOKEN; the GITHUB_TOKEN only checks out code
|
||||||
permissions:
|
permissions:
|
||||||
@@ -25,6 +38,7 @@ jobs:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
outputs:
|
outputs:
|
||||||
package-version: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
|
package-version: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
|
||||||
|
version-code: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
|
||||||
apk-artifact-name: build-${{ steps.timestamp.outputs.time }}.apk
|
apk-artifact-name: build-${{ steps.timestamp.outputs.time }}.apk
|
||||||
steps:
|
steps:
|
||||||
- name: Check for EXPO_TOKEN
|
- name: Check for EXPO_TOKEN
|
||||||
|
|||||||
@@ -10,6 +10,32 @@ on:
|
|||||||
options:
|
options:
|
||||||
- testflight
|
- testflight
|
||||||
- production
|
- production
|
||||||
|
assignTestFlightGroup:
|
||||||
|
type: boolean
|
||||||
|
description: Assign the build to the "QA Team" TestFlight group after submitting
|
||||||
|
default: false
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
profile:
|
||||||
|
type: string
|
||||||
|
description: Build profile to use
|
||||||
|
required: true
|
||||||
|
assignTestFlightGroup:
|
||||||
|
type: boolean
|
||||||
|
description: Assign the build to the "QA Team" TestFlight group after submitting
|
||||||
|
default: false
|
||||||
|
releaseNotes:
|
||||||
|
type: string
|
||||||
|
description: Notes to set as the TestFlight "What to Test" changelog
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
outputs:
|
||||||
|
package-version:
|
||||||
|
description: Version from package.json
|
||||||
|
value: ${{ jobs.build.outputs.package-version }}
|
||||||
|
build-number:
|
||||||
|
description: iOS build number
|
||||||
|
value: ${{ jobs.build.outputs.build-number }}
|
||||||
|
|
||||||
# Deploys happen via EAS using EXPO_TOKEN; the GITHUB_TOKEN only checks out code
|
# Deploys happen via EAS using EXPO_TOKEN; the GITHUB_TOKEN only checks out code
|
||||||
permissions:
|
permissions:
|
||||||
@@ -23,6 +49,9 @@ jobs:
|
|||||||
concurrency:
|
concurrency:
|
||||||
group: ios-build
|
group: ios-build
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
outputs:
|
||||||
|
package-version: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
|
||||||
|
build-number: ${{ steps.ipa-build-number.outputs.build-number }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check for EXPO_TOKEN
|
- name: Check for EXPO_TOKEN
|
||||||
run: >
|
run: >
|
||||||
@@ -164,6 +193,59 @@ jobs:
|
|||||||
id: get-build-info
|
id: get-build-info
|
||||||
run: bash scripts/setGitHubOutput.sh
|
run: bash scripts/setGitHubOutput.sh
|
||||||
|
|
||||||
|
# Read the build number straight from the IPA's CFBundleVersion. This is the value
|
||||||
|
# baked in at build time by use-build-number-with-bump (remote counter + 1) and the
|
||||||
|
# number that actually lands in App Store Connect. `eas build:version:get` reads the
|
||||||
|
# remote counter, which a --local build does not advance, so it can be off by one —
|
||||||
|
# using it here would make distribute_only poll for a nonexistent build.
|
||||||
|
- name: 🔢 Read build number from IPA
|
||||||
|
id: ipa-build-number
|
||||||
|
run: |
|
||||||
|
plist_dir="$(mktemp -d)"
|
||||||
|
unzip -o -q "$BUILD_DIR/Bluesky.ipa" 'Payload/*.app/Info.plist' -d "$plist_dir"
|
||||||
|
plist="$(find "$plist_dir" -name Info.plist -print -quit)"
|
||||||
|
build_number="$(/usr/libexec/PlistBuddy -c 'Print CFBundleVersion' "$plist")"
|
||||||
|
rm -rf "$plist_dir"
|
||||||
|
if [ -z "$build_number" ]; then
|
||||||
|
echo "ERROR: could not read CFBundleVersion from IPA"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "IPA build number: $build_number"
|
||||||
|
echo "build-number=$build_number" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# eas submit only uploads to App Store Connect; it can't assign a build to a
|
||||||
|
# TestFlight group. fastlane's distribute_only mode skips the upload and assigns the
|
||||||
|
# already-submitted build to the group, polling until Apple finishes processing it.
|
||||||
|
# The "What to Test" changelog is supplied by the caller (e.g. the nightly workflow).
|
||||||
|
- name: 🧪 Assign build to TestFlight group
|
||||||
|
if: ${{ inputs.assignTestFlightGroup }}
|
||||||
|
env:
|
||||||
|
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||||
|
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
|
||||||
|
ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }}
|
||||||
|
APP_VERSION: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
|
||||||
|
BUILD_NUMBER: ${{ steps.ipa-build-number.outputs.build-number }}
|
||||||
|
RELEASE_NOTES: ${{ inputs.releaseNotes }}
|
||||||
|
run: |
|
||||||
|
# Ensure the API key material is removed even if fastlane exits non-zero
|
||||||
|
# (the step runs under `bash -e`, which would otherwise abort before cleanup).
|
||||||
|
trap 'rm -f asc_api_key.p8 asc_api_key.json' EXIT
|
||||||
|
echo "$ASC_KEY_P8_BASE64" | base64 --decode > asc_api_key.p8
|
||||||
|
printf '{"key_id":"%s","issuer_id":"%s","key_filepath":"%s","in_house":false}' \
|
||||||
|
"$ASC_KEY_ID" "$ASC_ISSUER_ID" "$PWD/asc_api_key.p8" > asc_api_key.json
|
||||||
|
changelog_args=()
|
||||||
|
if [ -n "$RELEASE_NOTES" ]; then
|
||||||
|
changelog_args=(changelog:"$RELEASE_NOTES")
|
||||||
|
fi
|
||||||
|
fastlane run upload_to_testflight \
|
||||||
|
api_key_path:"$PWD/asc_api_key.json" \
|
||||||
|
distribute_only:true \
|
||||||
|
app_identifier:"xyz.blueskyweb.app" \
|
||||||
|
app_version:"$APP_VERSION" \
|
||||||
|
build_number:"$BUILD_NUMBER" \
|
||||||
|
"${changelog_args[@]}" \
|
||||||
|
groups:"QA Team"
|
||||||
|
|
||||||
- name: 🔔 Notify Slack of Production Build
|
- name: 🔔 Notify Slack of Production Build
|
||||||
if: ${{ inputs.profile == 'production' }}
|
if: ${{ inputs.profile == 'production' }}
|
||||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
||||||
@@ -172,7 +254,7 @@ jobs:
|
|||||||
webhook-type: incoming-webhook
|
webhook-type: incoming-webhook
|
||||||
payload-templated: true
|
payload-templated: true
|
||||||
payload: |
|
payload: |
|
||||||
{"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}```"}
|
{"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.ipa-build-number.outputs.build-number }}```"}
|
||||||
|
|
||||||
- name: ⬇️ Restore Cache
|
- name: ⬇️ Restore Cache
|
||||||
id: get-base-commit
|
id: get-base-commit
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
---
|
||||||
|
name: Nightly Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "10 2 * * *" # run at 2:10 AM UTC, after the nightly i18n job
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Deploys happen via EAS using EXPO_TOKEN; the GITHUB_TOKEN only checks out code
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Generate the changelog once, shared by both platforms. The range covers commits since
|
||||||
|
# the previous nightly, whose commit SHA is stored as a "nightly-build-commit" artifact
|
||||||
|
# (advanced by the record job below, only after both builds succeed).
|
||||||
|
prepare:
|
||||||
|
name: Prepare release notes
|
||||||
|
if: github.repository == 'bluesky-social/social-app'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
outputs:
|
||||||
|
notes: ${{ steps.notes.outputs.notes }}
|
||||||
|
steps:
|
||||||
|
- name: ⬇️ Checkout
|
||||||
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: 📝 Generate release notes
|
||||||
|
id: notes
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
# Find the most recent non-expired nightly-build-commit artifact and read its SHA
|
||||||
|
# The artifacts API returns results newest-first, so the most recent marker is on
|
||||||
|
# page 1 — no --paginate needed (which would run the jq aggregation per page and
|
||||||
|
# could emit multiple URLs). Take the first non-expired match.
|
||||||
|
prev=""
|
||||||
|
url=$(gh api \
|
||||||
|
"repos/${GITHUB_REPOSITORY}/actions/artifacts?name=nightly-build-commit&per_page=100" \
|
||||||
|
--jq 'first(.artifacts[] | select(.expired == false)) | .archive_download_url' \
|
||||||
|
2>/dev/null || true)
|
||||||
|
if [ -n "$url" ] && [ "$url" != "null" ]; then
|
||||||
|
if curl -sSL -H "Authorization: Bearer $GH_TOKEN" -o marker.zip "$url" \
|
||||||
|
&& unzip -o -q marker.zip; then
|
||||||
|
prev=$(cat nightly-build-commit.txt 2>/dev/null | tr -d '[:space:]')
|
||||||
|
fi
|
||||||
|
rm -f marker.zip nightly-build-commit.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$prev" ] && git cat-file -e "${prev}^{commit}" 2>/dev/null; then
|
||||||
|
echo "Generating notes since previous nightly: $prev"
|
||||||
|
range="${prev}..HEAD"
|
||||||
|
else
|
||||||
|
echo "No reachable previous nightly commit; falling back to last 30 commits."
|
||||||
|
range="HEAD~30..HEAD"
|
||||||
|
fi
|
||||||
|
notes=$(git log --no-merges --pretty=format:'- %s' "$range" 2>/dev/null | head -n 50)
|
||||||
|
if [ -z "$notes" ]; then
|
||||||
|
notes="Nightly build — no new commits since the last nightly."
|
||||||
|
fi
|
||||||
|
# Cap the whole changelog (TestFlight "What to Test" is limited to 4000 characters).
|
||||||
|
# head -c caps the combined stream; cut -c would only cap each line independently.
|
||||||
|
notes=$(printf '%s' "$notes" | head -c 3900)
|
||||||
|
{
|
||||||
|
echo "notes<<NOTES_EOF"
|
||||||
|
echo "$notes"
|
||||||
|
echo "NOTES_EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
ios:
|
||||||
|
name: Nightly iOS Build
|
||||||
|
needs: [prepare]
|
||||||
|
uses: ./.github/workflows/build-submit-ios.yml
|
||||||
|
with:
|
||||||
|
profile: testflight
|
||||||
|
assignTestFlightGroup: true
|
||||||
|
releaseNotes: ${{ needs.prepare.outputs.notes }}
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
android:
|
||||||
|
name: Nightly Android Build
|
||||||
|
needs: [prepare]
|
||||||
|
uses: ./.github/workflows/build-submit-android.yml
|
||||||
|
with:
|
||||||
|
profile: testflight-android
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
notify-ios:
|
||||||
|
name: Notify Slack of iOS nightly
|
||||||
|
needs: [prepare, ios]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 📝 Build Slack payload
|
||||||
|
id: payload
|
||||||
|
env:
|
||||||
|
NOTES: ${{ needs.prepare.outputs.notes }}
|
||||||
|
VERSION: ${{ needs.ios.outputs.package-version }}
|
||||||
|
BUILD_NUMBER: ${{ needs.ios.outputs.build-number }}
|
||||||
|
run: |
|
||||||
|
text="*Nightly iOS build available in TestFlight (QA Team)*
|
||||||
|
Version ${VERSION} (${BUILD_NUMBER})
|
||||||
|
|
||||||
|
${NOTES}"
|
||||||
|
payload=$(jq -n --arg text "$text" '{text: $text}')
|
||||||
|
{
|
||||||
|
echo "payload<<PAYLOAD_EOF"
|
||||||
|
echo "$payload"
|
||||||
|
echo "PAYLOAD_EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: 🔔 Notify Slack
|
||||||
|
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
|
||||||
|
webhook-type: incoming-webhook
|
||||||
|
payload: ${{ steps.payload.outputs.payload }}
|
||||||
|
|
||||||
|
notify-android:
|
||||||
|
name: Notify Slack of Android nightly
|
||||||
|
needs: [prepare, android]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 📝 Build Slack payload
|
||||||
|
id: payload
|
||||||
|
env:
|
||||||
|
NOTES: ${{ needs.prepare.outputs.notes }}
|
||||||
|
VERSION: ${{ needs.android.outputs.package-version }}
|
||||||
|
VERSION_CODE: ${{ needs.android.outputs.version-code }}
|
||||||
|
run: |
|
||||||
|
text="*Nightly Android build available (Internal track)*
|
||||||
|
Version ${VERSION} (${VERSION_CODE})
|
||||||
|
|
||||||
|
${NOTES}"
|
||||||
|
payload=$(jq -n --arg text "$text" '{text: $text}')
|
||||||
|
{
|
||||||
|
echo "payload<<PAYLOAD_EOF"
|
||||||
|
echo "$payload"
|
||||||
|
echo "PAYLOAD_EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: 🔔 Notify Slack
|
||||||
|
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
|
||||||
|
webhook-type: incoming-webhook
|
||||||
|
payload: ${{ steps.payload.outputs.payload }}
|
||||||
|
|
||||||
|
# Advance the nightly marker only after both builds succeed, so a failed night's commits
|
||||||
|
# roll into the next successful nightly's notes rather than being silently dropped.
|
||||||
|
record:
|
||||||
|
name: Record nightly commit
|
||||||
|
needs: [ios, android]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: ✏️ Write nightly commit marker
|
||||||
|
env:
|
||||||
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
|
run: echo "$GITHUB_SHA" > nightly-build-commit.txt
|
||||||
|
|
||||||
|
- name: 🚀 Upload nightly commit marker
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: nightly-build-commit
|
||||||
|
path: nightly-build-commit.txt
|
||||||
|
retention-days: 90
|
||||||
Reference in New Issue
Block a user