diff --git a/.github/workflows/pull-request-commit.yml b/.github/workflows/pull-request-commit.yml
index a06556ea15..4b3b9c42e7 100644
--- a/.github/workflows/pull-request-commit.yml
+++ b/.github/workflows/pull-request-commit.yml
@@ -19,7 +19,7 @@ concurrency:
permissions: {}
# denis release tag in bluesky-social/tango whose linux-amd64 binary the PR OTA
-# job downloads. Bump this one line to roll denis.
+# publish job downloads. Bump this one line to roll denis.
env:
DENIS_RELEASE_TAG: denis-v0.1.1
@@ -255,25 +255,24 @@ jobs:
# every dependabot PR. There is no OTA preview worth publishing for a
# dependency bump anyway.
#
- # Gated on a clean fingerprint-native run: an OTA can only carry JS, so once
- # the native surface changes the published bundle no longer represents the PR
- # and installing it on a store/TestFlight client is misleading at best. Those
- # PRs need a native build instead. A skipped or failed fingerprint job also
- # skips this one - without a verdict we can't say the OTA is representative.
- publish-pr-ota:
- name: Publish PR OTA to denis
- needs: fingerprint-native
+ # The bundle is built here rather than in publish-pr-ota so it runs in
+ # PARALLEL with fingerprint-native instead of after it: both are multi-minute
+ # jobs, and the bundle does not depend on the fingerprint verdict - only the
+ # decision to publish it does. This job deliberately carries no AWS or denis
+ # credentials; it only produces an artifact. When the fingerprint turns out to
+ # be dirty the bundle is simply discarded unpublished, which costs a runner but
+ # saves the fingerprint's wall clock on every clean PR.
+ build-pr-ota:
+ name: Build PR OTA bundle
runs-on: ubuntu-latest
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository &&
- github.event.pull_request.user.type != 'Bot' &&
- needs.fingerprint-native.outputs.includes-changes != 'true'
+ github.event.pull_request.user.type != 'Bot'
concurrency:
- group: pr-ota-${{ github.event.pull_request.number }}
+ group: pr-ota-build-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
- id-token: write
contents: read
outputs:
release-version: ${{ steps.env.outputs.release-version }}
@@ -311,6 +310,71 @@ jobs:
SENTRY_DIST=${{ steps.env.outputs.bundle-identifier }}
pnpm export
+ # Assembling here (rather than inside denisPublish.sh) keeps the handoff to
+ # the publish job to just the files denis uploads, instead of all of ./dist
+ # plus a Node toolchain to process it.
+ - name: 📦 Assemble bundle directory
+ run: node scripts/bundleUpdate.js
+
+ # Read from the global EAS counters while the EAS CLI is still set up, so
+ # the publish job needs neither EXPO_TOKEN nor node_modules. These are the
+ # numbers the install link advertises, so the publish must be pinned to the
+ # same ones.
+ - name: 🔢 Get native build numbers
+ id: build-info
+ run: bash scripts/setGitHubOutput.sh
+
+ # The name is keyed on the run rather than the attempt so that re-running
+ # only the failed jobs still finds the bundle this run already built;
+ # overwrite keeps a full re-run (which rebuilds it) from colliding with the
+ # previous attempt's upload.
+ - name: 🚀 Upload bundle
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: pr-ota-bundle-${{ github.run_id }}
+ path: bundleTempDir
+ retention-days: 1
+ if-no-files-found: error
+ overwrite: true
+
+ # Publish the bundle built above, once fingerprint-native has returned a clean
+ # verdict. An OTA can only carry JS, so once the native surface changes the
+ # published bundle no longer represents the PR and installing it on a
+ # store/TestFlight client is misleading at best. Those PRs need a native build
+ # instead. A skipped or failed fingerprint job also skips this one - without a
+ # verdict we can't say the OTA is representative.
+ #
+ # This is kept separate from build-pr-ota so the fingerprint gate holds back
+ # only the publish, which is quick, rather than the whole bundle build.
+ publish-pr-ota:
+ name: Publish PR OTA to denis
+ needs: [fingerprint-native, build-pr-ota]
+ runs-on: ubuntu-latest
+ if: >-
+ github.event_name == 'pull_request' &&
+ github.event.pull_request.head.repo.full_name == github.repository &&
+ github.event.pull_request.user.type != 'Bot' &&
+ needs.fingerprint-native.outputs.includes-changes != 'true'
+ concurrency:
+ group: pr-ota-publish-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+ permissions:
+ id-token: write
+ contents: read
+ steps:
+ # Needed for scripts/denisPublish.sh and the local composite actions; no
+ # dependency install, since denis is a downloaded binary.
+ - name: ⬇️ Checkout
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: ⬇️ Download bundle
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: pr-ota-bundle-${{ github.run_id }}
+ path: bundleTempDir
+
- name: ☁️ Configure AWS credentials (denis, PR-scoped)
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
with:
@@ -346,22 +410,23 @@ jobs:
app-id: ${{ vars.SYNC_INTERNAL_APP_ID }}
private-key: ${{ secrets.SYNC_INTERNAL_PK }}
- - name: 🔢 Get native build numbers
- id: build-info
- run: bash scripts/setGitHubOutput.sh
-
+ # The use-build-number wrapper is skipped: every value it would look up is
+ # already fixed by the build job, and calling it would mean installing
+ # dependencies again just to shell out to the EAS CLI.
- name: 🚀 Publish OTA to denis (S3)
- run: pnpm use-build-number bash scripts/denisPublish.sh
+ run: bash scripts/denisPublish.sh
env:
- RUNTIME_VERSION: ''
+ # bundleTempDir arrived as an artifact; ./dist is not in this job.
+ SKIP_BUNDLE_ASSEMBLY: '1'
CHANNEL_NAME: pull-request-${{ github.event.pull_request.number }}
# Pin the publish to the same values exposed in the install link.
- BSKY_IOS_BUILD_NUMBER: ${{ steps.build-info.outputs.BSKY_IOS_BUILD_NUMBER }}
- BSKY_ANDROID_VERSION_CODE: ${{ steps.build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
+ RUNTIME_VERSION: ${{ needs.build-pr-ota.outputs.release-version }}
+ BSKY_IOS_BUILD_NUMBER: ${{ needs.build-pr-ota.outputs.ios-build-number }}
+ BSKY_ANDROID_VERSION_CODE: ${{ needs.build-pr-ota.outputs.android-build-number }}
comment-pr-ota:
name: Comment PR OTA install link
- needs: publish-pr-ota
+ needs: [build-pr-ota, publish-pr-ota]
runs-on: ubuntu-latest
permissions:
pull-requests: write
@@ -373,6 +438,6 @@ jobs:
message: |
The OTA deployment for this PR was successful! You may now apply it by either scanning the QR code or opening the deep link below in your browser:
-
+
- `bluesky://intent/apply-ota?channel=pull-request-${{ github.event.pull_request.number }}&releaseVersion=${{ needs.publish-pr-ota.outputs.release-version }}&iosBuildNumber=${{ needs.publish-pr-ota.outputs.ios-build-number }}&androidBuildNumber=${{ needs.publish-pr-ota.outputs.android-build-number }}`
+ `bluesky://intent/apply-ota?channel=pull-request-${{ github.event.pull_request.number }}&releaseVersion=${{ needs.build-pr-ota.outputs.release-version }}&iosBuildNumber=${{ needs.build-pr-ota.outputs.ios-build-number }}&androidBuildNumber=${{ needs.build-pr-ota.outputs.android-build-number }}`
diff --git a/scripts/denisPublish.sh b/scripts/denisPublish.sh
index c6a30b77b9..48fd998fef 100755
--- a/scripts/denisPublish.sh
+++ b/scripts/denisPublish.sh
@@ -10,10 +10,21 @@ set -o nounset
# (configure-aws-credentials OIDC), and BSKY_IOS_BUILD_NUMBER /
# BSKY_ANDROID_VERSION_CODE from the use-build-number wrapper.
-rm -rf bundleTempDir
+# Set when bundleTempDir was assembled by a separate job and handed over as an
+# artifact, as the PR OTA flow does so the bundle can be built in parallel with
+# the fingerprint check. Without it, assemble the directory from ./dist here.
+if [ -n "${SKIP_BUNDLE_ASSEMBLY:-}" ]; then
+ echo "Using pre-assembled bundle directory..."
+ if [ ! -f bundleTempDir/metadata.json ]; then
+ echo "bundleTempDir/metadata.json is missing; nothing to publish" >&2
+ exit 1
+ fi
+else
+ rm -rf bundleTempDir
-echo "Assembling bundle directory..."
-node scripts/bundleUpdate.js
+ echo "Assembling bundle directory..."
+ node scripts/bundleUpdate.js
+fi
if [ -z "$RUNTIME_VERSION" ]; then
RUNTIME_VERSION=$(cat package.json | jq '.version' -r)