Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 83 additions & 143 deletions .github/workflows/exact-package-candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ name: Exact package candidate
on:
push:
branches: [main]
pull_request:
branches: [main]
merge_group:
types: [checks_requested]
schedule:
- cron: '17 3 * * *'
workflow_dispatch:
Expand All @@ -23,8 +19,9 @@ on:

concurrency:
# A nightly or manually dispatched matrix must never cancel the push run that
# authorizes a release for the same main commit.
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
# authorizes a release for the same main commit, and consecutive pushes must
# not cancel each other's runs: every main SHA stays release-eligible.
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true

jobs:
Expand All @@ -47,7 +44,7 @@ jobs:

- name: Inspect metadata, contents, entrypoints, and type surfaces
env:
CANDIDATE_CHANNEL: ${{ inputs.channel || (github.event_name == 'schedule' && 'nightly') || (github.event_name == 'push' && 'release') || 'pull-request' }}
CANDIDATE_CHANNEL: ${{ inputs.channel || (github.event_name == 'schedule' && 'nightly') || 'release' }}
run: |
node scripts/verify-package-artifact.mjs \
artifacts/candidate/react-native-reorderable.tgz \
Expand Down Expand Up @@ -94,118 +91,6 @@ jobs:
- name: Verify Legend List alone
run: node scripts/verify-legend-list-package.mjs artifacts/candidate/react-native-reorderable.tgz

quality:
name: Required quality and performance gates
needs: candidate
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

- name: Setup
uses: ./.github/actions/setup

- name: Download inspected candidate
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: release-candidate
path: artifacts/candidate

- name: Verify source and packed API stay aligned
run: |
yarn lint
yarn typecheck
yarn test --maxWorkers=2 --coverage
yarn test:release
yarn docs:api:check
node scripts/verify-package-artifact.mjs artifacts/candidate/react-native-reorderable.tgz

- name: Enforce production geometry budgets
run: |
mkdir -p "$RUNNER_TEMP/candidate-package"
tar -xzf artifacts/candidate/react-native-reorderable.tgz -C "$RUNNER_TEMP/candidate-package"
test ! -e "$GITHUB_WORKSPACE/lib"
cp -R "$RUNNER_TEMP/candidate-package/package/lib" "$GITHUB_WORKSPACE/lib"
node benchmarks/issue34-geometry.mjs
node benchmarks/issue35-section-geometry.mjs

render-regressions:
name: Candidate Reassure render gates
needs: candidate
runs-on: ubuntu-latest
steps:
- name: Checkout candidate with baseline history
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0

- name: Setup candidate
uses: ./.github/actions/setup

- name: Download inspected candidate
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: release-candidate
path: artifacts/candidate

- name: Resolve baseline commit
id: baseline
env:
EVENT_NAME: ${{ github.event_name }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PUSH_BEFORE_SHA: ${{ github.event.before }}
run: |
if [[ "$EVENT_NAME" == "pull_request" ]]; then
echo "sha=$PR_BASE_SHA" >> "$GITHUB_OUTPUT"
elif [[ "$EVENT_NAME" == "push" ]]; then
echo "sha=$PUSH_BEFORE_SHA" >> "$GITHUB_OUTPUT"
else
baseline_sha="$(git merge-base HEAD origin/main)"
if [[ "$baseline_sha" == "$(git rev-parse HEAD)" ]]; then
baseline_sha="$(git rev-parse HEAD^)"
fi
echo "sha=$baseline_sha" >> "$GITHUB_OUTPUT"
fi

- name: Measure baseline in an isolated worktree
env:
BASELINE_SHA: ${{ steps.baseline.outputs.sha }}
BASELINE_ROOT: ${{ runner.temp }}/reassure-baseline
run: |
git worktree add --detach "$BASELINE_ROOT" "$BASELINE_SHA"
cp "$GITHUB_WORKSPACE"/src/__perf__/*.perf-test.tsx "$BASELINE_ROOT/src/__perf__/"
cd "$BASELINE_ROOT"
corepack enable
yarn install --immutable
yarn performance:reassure:baseline
mkdir -p "$GITHUB_WORKSPACE/.reassure"
cp .reassure/baseline.perf "$GITHUB_WORKSPACE/.reassure/baseline.perf"

- name: Replace candidate source with the source carried by the tarball
run: |
mkdir -p "$RUNNER_TEMP/candidate-package"
tar -xzf artifacts/candidate/react-native-reorderable.tgz -C "$RUNNER_TEMP/candidate-package"
mv "$GITHUB_WORKSPACE/src" "$RUNNER_TEMP/repository-source"
cp -R "$RUNNER_TEMP/candidate-package/package/src" "$GITHUB_WORKSPACE/src"
mkdir -p "$GITHUB_WORKSPACE/src/__perf__"
cp "$RUNNER_TEMP/repository-source"/__perf__/*.perf-test.tsx "$GITHUB_WORKSPACE/src/__perf__/"

- name: Measure and enforce candidate regressions
run: |
yarn performance:reassure
yarn performance:reassure:verify

- name: Upload Reassure report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: candidate-reassure-performance-report
if-no-files-found: error
retention-days: 30
path: |
.reassure/output.json
.reassure/output.md

clean-native-consumers:
name: ${{ matrix.kind }} / RN ${{ matrix.versions.reactNative }} / ${{ matrix.platform }}
needs: candidate
Expand Down Expand Up @@ -386,8 +271,7 @@ jobs:
build

minimum-android-runtime:
name: Nightly minimum Android API 24
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
name: Minimum Android API 24 runtime floor
needs: candidate
runs-on: ubuntu-latest
timeout-minutes: 120
Expand Down Expand Up @@ -445,8 +329,7 @@ jobs:
adb shell am start -W -n com.reorderableconsumer/.MainActivity

minimum-ios-runtime:
name: Nightly iOS 15.1 deployment target on hosted iOS 18.5
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
name: Minimum iOS 15.1 deployment target on hosted iOS 18.5
needs: candidate
runs-on: macos-15
timeout-minutes: 120
Expand Down Expand Up @@ -522,6 +405,60 @@ jobs:
"$SIMULATOR_ID" \
org.reactjs.native.example.ReorderableConsumer

peer-canary:
name: Nightly peer-version canary (non-gating)
# Drift canary only: deliberately absent from approve-candidate's needs so
# a breaking peer release alerts on the nightly run without blocking any
# push-to-main candidate or publication.
if: github.event_name == 'schedule'
needs: candidate
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc

- name: Download inspected candidate
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: release-candidate
path: artifacts/candidate

- name: Install the candidate against the latest peer releases
env:
FIXTURE: ${{ runner.temp }}/ReorderableConsumer
run: |
REACT="$(npm view react version)"
REACT_NATIVE="$(npm view react-native version)"
GESTURE_HANDLER="$(npm view react-native-gesture-handler version)"
REANIMATED="$(npm view react-native-reanimated version)"
WORKLETS="$(npm view react-native-worklets version)"
echo "Latest peers: react=$REACT react-native=$REACT_NATIVE" \
"gesture-handler=$GESTURE_HANDLER reanimated=$REANIMATED worklets=$WORKLETS"
# Unpinned on purpose: this canary exists to surface drift against
# whatever the ecosystem ships today, so the CLI floats with it.
npx --yes @react-native-community/cli@latest init ReorderableConsumer \
--version "$REACT_NATIVE" --directory "$FIXTURE" --skip-install --pm npm
node scripts/configure-clean-consumer.mjs \
"$FIXTURE" \
"$GITHUB_WORKSPACE/artifacts/candidate/react-native-reorderable.tgz" \
bare \
"{\"react\":\"$REACT\",\"reactNative\":\"$REACT_NATIVE\",\"reactNativeGestureHandler\":\"$GESTURE_HANDLER\",\"reactNativeReanimated\":\"$REANIMATED\",\"reactNativeWorklets\":\"$WORKLETS\"}"
npm install --prefix "$FIXTURE" --legacy-peer-deps --no-audit --no-fund

- name: Prove TypeScript, Babel, and autolinking against latest peers
working-directory: ${{ runner.temp }}/ReorderableConsumer
run: |
npx tsc --noEmit
npx react-native config | node "$GITHUB_WORKSPACE/scripts/verify-consumer-autolinking.mjs" \
--platform android
node -e "const output=require('@babel/core').transformFileSync('worklet-smoke.ts',{configFile:require('node:path').resolve('babel.config.js')}).code;if(!output.includes('__workletHash'))process.exit(1)"

contract:
name: ${{ matrix.configuration }} / RN 0.85
needs: candidate
Expand Down Expand Up @@ -566,18 +503,32 @@ jobs:
- name: Verify designated React Native baseline
run: node -e "if(require('./package.json').devDependencies['react-native']!=='0.85.0')process.exit(1)"

- name: Cache cocoapods
if: runner.os == 'macOS'
id: cocoapods-cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: example/ios/Pods
key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-cocoapods-

- name: Verify Apple runtime and prepare native app
if: runner.os == 'macOS'
env:
COCOAPODS_CACHE_HIT: ${{ steps.cocoapods-cache.outputs.cache-hit }}
run: |
xcrun simctl list runtimes | grep -F '${{ matrix.runtime }}'
brew tap wix/brew
brew trust --formula wix/brew/applesimutils
brew install wix/brew/applesimutils
(
cd example
bundle install
bundle exec pod install --project-directory=ios
)
if [[ "$COCOAPODS_CACHE_HIT" != 'true' ]]; then
(
cd example
bundle install
bundle exec pod install --project-directory=ios
)
fi
# build-framework-cache must run before detox build: without a
# prebuilt framework cache, Detox compiles it lazily mid-build and
# the Apple contract build fails on hosted runners.
Expand Down Expand Up @@ -655,23 +606,14 @@ jobs:
always() &&
needs.candidate.result == 'success' &&
needs.clean-package-consumers.result == 'success' &&
needs.quality.result == 'success' &&
needs.render-regressions.result == 'success' &&
needs.clean-native-consumers.result == 'success' &&
needs.minimum-android-runtime.result == 'success' &&
needs.minimum-ios-runtime.result == 'success' &&
needs.contract.result == 'success' &&
needs.parity.result == 'success' &&
(
(github.event_name != 'schedule' && github.event_name != 'workflow_dispatch' &&
needs.minimum-android-runtime.result == 'skipped' &&
needs.minimum-ios-runtime.result == 'skipped') ||
(needs.minimum-android-runtime.result == 'success' &&
needs.minimum-ios-runtime.result == 'success')
)
needs.parity.result == 'success'
needs:
- candidate
- clean-package-consumers
- quality
- render-regressions
- clean-native-consumers
- minimum-android-runtime
- minimum-ios-runtime
Expand All @@ -698,9 +640,7 @@ jobs:
- name: Recheck immutable candidate identity
env:
EXPECTED_SHA: ${{ needs.candidate.outputs.sha256 }}
run: |
test "$(sha256sum artifacts/candidate/react-native-reorderable.tgz | cut -d' ' -f1)" = "$EXPECTED_SHA"
test "$(node -p "require('./artifacts/candidate/manifest.json').sha256")" = "$EXPECTED_SHA"
run: test "$(sha256sum artifacts/candidate/react-native-reorderable.tgz | cut -d' ' -f1)" = "$EXPECTED_SHA"

- name: Expose the verified bytes to release automation
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
Expand Down
2 changes: 1 addition & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ The release workflow intentionally has no npm token. Its publish job receives a

Every user-visible package change carries a file from `yarn changeset`. After a fully green push to `main`, Changesets creates or updates `changeset-release/main`. This pull request is the on-demand release boundary: leave it open while accumulating changes and merge it when the release should happen.

Merging the release pull request causes the exact-package workflow to build one tarball and run that same tarball through package inspection, clean consumers, the supported React Native compatibility matrix, performance checks, and the four-engine device contract. Publication starts only after that workflow succeeds; the `main` ruleset's required pull-request checks are what guarantee the commit already passed the fast set. Immediately before npm, the publisher rechecks the tarball bytes, SHA-256, source commit, package name, and version against its manifest.
Merging the release pull request causes the exact-package workflow to build one tarball and run that same tarball through package inspection, clean consumers, the supported React Native compatibility matrix, the minimum Android and iOS runtime floors, and the four-engine device contract. Publication starts only after that workflow succeeds; the `main` ruleset's required pull-request checks are what guarantee the commit already passed the fast set. Immediately before npm, the publisher rechecks the tarball bytes, SHA-256, source commit, package name, and version against its manifest.

Changesets then creates the package tag and GitHub release. The same release workflow builds and deploys the documentation from that tagged commit. Stable versions use the npm `latest` tag.

Expand Down
Loading