diff --git a/.github/workflows/build-submit-android.yml b/.github/workflows/build-submit-android.yml index a2261536e6..52bd28caf6 100644 --- a/.github/workflows/build-submit-android.yml +++ b/.github/workflows/build-submit-android.yml @@ -170,22 +170,6 @@ jobs: payload: | {"text": "Android ${{ inputs.profile || 'testflight-android' }} build submitted to Google Play!\n```Version Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.version-code }}```"} - # Record the commit only after a successful submit, so a failed submit doesn't - # advance the "most recent testflight" marker. - - name: ⬇️ Restore Cache - id: get-base-commit - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - if: ${{ inputs.profile == 'testflight-android' }} - with: - path: most-recent-testflight-commit.txt - key: most-recent-testflight-commit - - - name: ✏️ Write commit hash to cache - if: ${{ inputs.profile == 'testflight-android' }} - env: - GITHUB_SHA: ${{ github.sha }} - run: echo $GITHUB_SHA > most-recent-testflight-commit.txt - # Runs in parallel with submit: the QA APK shouldn't be blocked by a Play submission failure. universalApk: name: Build universal APK diff --git a/.github/workflows/build-submit-ios.yml b/.github/workflows/build-submit-ios.yml index 2085aabaa4..a1c6b45ec8 100644 --- a/.github/workflows/build-submit-ios.yml +++ b/.github/workflows/build-submit-ios.yml @@ -254,22 +254,6 @@ jobs: payload: | {"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.build-number }}```"} - # Record the commit only after a successful submit, so a failed submit doesn't advance - # the baseline used for the next testflight build's changelog. - - name: ⬇️ Restore Cache - id: get-base-commit - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - if: ${{ inputs.profile == 'testflight' }} - with: - path: most-recent-testflight-commit.txt - key: most-recent-testflight-commit - - - name: ✏️ Write commit hash to cache - env: - GITHUB_SHA: ${{ github.sha }} - if: ${{ inputs.profile == 'testflight' }} - run: echo $GITHUB_SHA > most-recent-testflight-commit.txt - distribute: name: Assign build to TestFlight group # fastlane and jq ship preinstalled on the macOS runner image, and this step mostly idles diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml index 9154d90040..7fc2925c34 100644 --- a/.github/workflows/bundle-deploy-eas-update.yml +++ b/.github/workflows/bundle-deploy-eas-update.yml @@ -33,9 +33,11 @@ jobs: name: Bundle and Deploy EAS Update runs-on: ubuntu-latest # id-token: write lets this job mint an OIDC token to assume the denis - # publish role; contents: read is still needed for the checkout. + # publish role; actions: read loads the fingerprint baseline artifact; + # contents: read is still needed for the checkout. permissions: id-token: write + actions: read contents: read concurrency: group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-deploy @@ -101,12 +103,58 @@ jobs: node-version-file: package.json cache: pnpm + - name: ⬇️ Load fingerprint baseline + id: baseline + if: ${{ (inputs.channel || 'testflight') == 'testflight' }} + env: + GH_TOKEN: ${{ github.token }} + REPOSITORY_ID: ${{ github.repository_id }} + run: | + url=$(gh api \ + "repos/${GITHUB_REPOSITORY}/actions/artifacts?name=testflight-native-fingerprint&per_page=100" \ + --jq "[.artifacts[] | select( + .expired == false and + .workflow_run.head_branch == \"main\" and + .workflow_run.head_repository_id == (\$ENV.REPOSITORY_ID | tonumber) + )] | max_by(.created_at) | .archive_download_url" \ + 2>/dev/null || true) + + if [ -n "$url" ] && [ "$url" != "null" ]; then + mkdir baseline-artifact + if curl -sSL -H "Authorization: Bearer $GH_TOKEN" -o baseline.zip "$url" \ + && unzip -q baseline.zip -d baseline-artifact; then + if jq -e '.sources | type == "array"' \ + baseline-artifact/native-fingerprint.json >/dev/null; then + echo "path=baseline-artifact/native-fingerprint.json" >> "$GITHUB_OUTPUT" + else + echo "::warning::Ignoring invalid fingerprint baseline artifact." + fi + else + echo "::warning::Could not download fingerprint baseline artifact." + fi + fi + - name: 📷 Check fingerprint and install dependencies id: fingerprint - uses: bluesky-social/github-actions/fingerprint-native@b5556913e4aef3964cfd5936d0add3fc0d809bdb # v0.2.0 + uses: bluesky-social/github-actions/fingerprint-native@abc6a46eb4badf243f55bfd7d6cec42722456300 # v0.3.0 with: profile: ${{ inputs.channel || 'testflight' }} previous-commit-tag: ${{ inputs.runtimeVersion }} + # The recordBaseline job uploads this marker after a successful deploy; + # on the native path, that requires both builds to succeed. A missing + # marker forces native builds so they can seed the baseline safely. + baseline-fingerprint-path: ${{ steps.baseline.outputs.path }} + + # Hand the full fingerprint to recordBaseline through a short-lived + # artifact. It is uploaded unconditionally but promoted to the persistent + # baseline only after both native builds succeed. + - name: 🚀 Upload native fingerprint + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: native-fingerprint-${{ github.run_id }} + path: ${{ steps.fingerprint.outputs.current-fingerprint-path }} + retention-days: 1 + if-no-files-found: error - name: 🔤 Compile translations uses: ./.github/actions/compile-i18n @@ -195,20 +243,6 @@ jobs: RUNTIME_VERSION: ${{ inputs.runtimeVersion }} CHANNEL_NAME: ${{ inputs.channel || 'testflight' }} - - name: ⬇️ Restore Cache - id: get-base-commit - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - if: ${{ !steps.fingerprint.outputs.includes-changes && - !steps.version.outputs.version-changed }} - with: - path: most-recent-testflight-commit.txt - key: most-recent-testflight-commit - - - name: ✏️ Write commit hash to cache - if: ${{ !steps.fingerprint.outputs.includes-changes && - !steps.version.outputs.version-changed }} - run: echo $GITHUB_SHA > most-recent-testflight-commit.txt - buildIfNecessaryIOS: name: Build and Submit iOS needs: [bundleDeploy] @@ -270,3 +304,48 @@ jobs: ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + + # Advance the fingerprint baseline only after BOTH native builds have shipped + # the new native surface. This replaces the old actions/cache baseline, which + # only advanced on cache eviction and so silently froze - freezing meant every + # fingerprint looked changed and OTA updates stopped deploying entirely. + # + # On the native-build path, this runs only after both builds succeed. A + # successful OTA deploy also records its fingerprint, refreshing the + # persistent marker's retention without changing the native baseline. + # + # The persistent artifact replaces the old actions/cache marker without + # requiring a PAT or mutable repository variable. Each successful deploy adds + # an immutable marker; the next run reads the newest non-expired one using the + # built-in GITHUB_TOKEN. + recordBaseline: + name: Record fingerprint baseline + runs-on: ubuntu-latest + needs: [bundleDeploy, buildIfNecessaryIOS, buildIfNecessaryAndroid] + if: ${{ always() && + (inputs.channel || 'testflight') == 'testflight' && + needs.bundleDeploy.result == 'success' && + (needs.bundleDeploy.outputs.changes-detected != 'true' || + (needs.buildIfNecessaryIOS.result == 'success' && + needs.buildIfNecessaryAndroid.result == 'success')) && + github.repository == 'bluesky-social/social-app' }} + permissions: + actions: read + steps: + - name: ⬇️ Download native fingerprint + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: native-fingerprint-${{ github.run_id }} + + - name: 🧐 Validate native fingerprint + run: > + jq -e '.sources | type == "array"' native-fingerprint.json >/dev/null || + (echo "::error::native fingerprint artifact was invalid; refusing to record it as the baseline." && exit 1) + + - name: 🚀 Record fingerprint baseline + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: testflight-native-fingerprint + path: native-fingerprint.json + retention-days: 90 + if-no-files-found: error