diff --git a/.github/workflows/build-submit-android.yml b/.github/workflows/build-submit-android.yml index 88c48dff9f..4788adc663 100644 --- a/.github/workflows/build-submit-android.yml +++ b/.github/workflows/build-submit-android.yml @@ -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 diff --git a/.github/workflows/build-submit-ios.yml b/.github/workflows/build-submit-ios.yml index 19511a07cb..350957b897 100644 --- a/.github/workflows/build-submit-ios.yml +++ b/.github/workflows/build-submit-ios.yml @@ -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<> "$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<> "$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' }} diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 37009bf526..ddfebeb2b5 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -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<> "$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<> "$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<> "$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