Move nightly note-gen, marker, and alerts to the orchestrator

The release notes, commit marker, and Slack alerts are nightly-specific, so
move them out of the build workflows and into nightly-build.yml:

- prepare job reads the previous nightly-build-commit artifact and generates
  the shared changelog once
- iOS/Android builds run in parallel, consuming the notes (iOS via a new
  releaseNotes input); both expose version/build outputs via workflow_call
- per-platform Slack jobs post the notes with that platform's version
- record job advances the marker only after both builds succeed, so a failed
  night's commits roll into the next successful nightly's notes

Also harden the TestFlight key cleanup with a trap ... EXIT so the App Store
Connect private key is removed even if fastlane exits non-zero under bash -e.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Eric Bailey
2026-06-17 17:35:19 -05:00
parent 04860bad9c
commit 09b2fa05b6
3 changed files with 171 additions and 99 deletions
@@ -16,6 +16,13 @@ on:
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
permissions:
@@ -31,6 +38,7 @@ jobs:
cancel-in-progress: false
outputs:
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
steps:
- name: Check for EXPO_TOKEN
+25 -97
View File
@@ -24,6 +24,18 @@ on:
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
permissions:
@@ -34,13 +46,12 @@ jobs:
if: github.repository == 'bluesky-social/social-app'
name: Build and Submit iOS
runs-on: macos-26-xlarge
# actions: read lets the release-notes step query past nightly-build-commit artifacts
permissions:
contents: read
actions: read
concurrency:
group: ios-build
cancel-in-progress: false
outputs:
package-version: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
build-number: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}
steps:
- name: Check for EXPO_TOKEN
run: >
@@ -182,56 +193,10 @@ jobs:
id: get-build-info
run: bash scripts/setGitHubOutput.sh
# Build the "What to Test" changelog from the commits since the last nightly build.
# The previous nightly's commit SHA is stored as a "nightly-build-commit" artifact
# (written at the end of this job), so the range only covers nightly-to-nightly changes
# rather than every TestFlight build. Falls back to the most recent commits if no prior
# nightly marker is found (e.g. the first run, or after the 90-day artifact retention).
- name: 📝 Generate release notes
id: notes
if: ${{ inputs.assignTestFlightGroup }}
env:
GH_TOKEN: ${{ github.token }}
run: |
# Deepen the shallow checkout so the range back to the previous nightly is reachable
git fetch --deepen=200 origin || true
# Find the most recent non-expired nightly-build-commit artifact and read its SHA
prev=""
url=$(gh api --paginate \
"repos/${GITHUB_REPOSITORY}/actions/artifacts?name=nightly-build-commit&per_page=100" \
--jq '[.artifacts[] | select(.expired == false)] | sort_by(.created_at) | last | .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
# TestFlight "What to Test" is capped at 4000 characters
notes=$(printf '%s' "$notes" | cut -c1-3900)
{
echo "notes<<NOTES_EOF"
echo "$notes"
echo "NOTES_EOF"
} >> "$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:
@@ -240,63 +205,26 @@ jobs:
ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }}
APP_VERSION: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
BUILD_NUMBER: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}
RELEASE_NOTES: ${{ steps.notes.outputs.notes }}
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:"$RELEASE_NOTES" \
"${changelog_args[@]}" \
groups:"QA Team"
rm -f asc_api_key.p8 asc_api_key.json
# Record this nightly's commit so the next nightly can diff against it.
- name: ✏️ Write nightly commit marker
if: ${{ inputs.assignTestFlightGroup }}
env:
GITHUB_SHA: ${{ github.sha }}
run: echo "$GITHUB_SHA" > nightly-build-commit.txt
- name: 🚀 Upload nightly commit marker
if: ${{ inputs.assignTestFlightGroup }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: nightly-build-commit
path: nightly-build-commit.txt
retention-days: 90
# Build the Slack payload with jq so the multi-line changelog is safely JSON-encoded.
- name: 📝 Build nightly Slack payload
id: nightly-slack
if: ${{ inputs.assignTestFlightGroup }}
env:
RELEASE_NOTES: ${{ steps.notes.outputs.notes }}
APP_VERSION: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
BUILD_NUMBER: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}
run: |
text="*Nightly iOS build available in TestFlight (QA Team)*
Version ${APP_VERSION} (${BUILD_NUMBER})
${RELEASE_NOTES}"
payload=$(jq -n --arg text "$text" '{text: $text}')
{
echo "payload<<PAYLOAD_EOF"
echo "$payload"
echo "PAYLOAD_EOF"
} >> "$GITHUB_OUTPUT"
- name: 🔔 Notify Slack of Nightly Build
if: ${{ inputs.assignTestFlightGroup }}
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
with:
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
webhook-type: incoming-webhook
payload: ${{ steps.nightly-slack.outputs.payload }}
- name: 🔔 Notify Slack of Production Build
if: ${{ inputs.profile == 'production' }}
+138 -2
View File
@@ -11,19 +11,155 @@ 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
prev=""
url=$(gh api --paginate \
"repos/${GITHUB_REPOSITORY}/actions/artifacts?name=nightly-build-commit&per_page=100" \
--jq '[.artifacts[] | select(.expired == false)] | sort_by(.created_at) | last | .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
# TestFlight "What to Test" is capped at 4000 characters
notes=$(printf '%s' "$notes" | cut -c1-3900)
{
echo "notes<<NOTES_EOF"
echo "$notes"
echo "NOTES_EOF"
} >> "$GITHUB_OUTPUT"
ios:
name: Nightly iOS Build
if: github.repository == 'bluesky-social/social-app'
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
if: github.repository == 'bluesky-social/social-app'
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