diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml deleted file mode 100644 index 1eda5545a47e..000000000000 --- a/.github/workflows/nightly.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: Nightly - -on: - workflow_dispatch: - # nightly build @ 2:15 AM UTC - schedule: - - cron: "15 2 * * *" - -permissions: - contents: read - -jobs: - set_release_type: - runs-on: ubuntu-latest - if: github.repository == 'react/react-native' - outputs: - RELEASE_TYPE: ${{ steps.set_release_type.outputs.RELEASE_TYPE }} - env: - EVENT_NAME: ${{ github.event_name }} - REF: ${{ github.ref }} - steps: - - id: set_release_type - run: | - echo "Setting release type to nightly" - echo "RELEASE_TYPE=nightly" >> $GITHUB_OUTPUT - - prebuild_apple_dependencies: - if: github.repository == 'react/react-native' - uses: ./.github/workflows/prebuild-ios-dependencies.yml - secrets: inherit - - prebuild_react_native_core: - uses: ./.github/workflows/prebuild-ios-core.yml - with: - use-hermes-prebuilt: true - version-type: nightly - secrets: inherit - needs: [prebuild_apple_dependencies] - - build_android: - runs-on: ubuntu-latest - if: github.repository == 'react/react-native' - needs: [set_release_type] - container: - image: reactnativecommunity/react-native-android:latest - env: - TERM: "dumb" - # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers - # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 - LC_ALL: C.UTF8 - GRADLE_OPTS: "-Dorg.gradle.daemon=false" - ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} - ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} - ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }} - ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }} - REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Build Android - uses: ./.github/actions/build-android - with: - release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }} - gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - - # Delegate the actual npm publish to the shared reusable workflow so - # every `npm publish` in this repo originates from one workflow file — - # required because npm Trusted Publishing only accepts one - # (org, repo, workflow_filename) per package. - build_npm_package: - needs: - [ - set_release_type, - build_android, - prebuild_apple_dependencies, - prebuild_react_native_core, - ] - # The top-level `permissions: contents: read` is the ceiling for - # GITHUB_TOKEN in every job here, including reusable-workflow calls. - # Re-grant `id-token: write` at the job level so publish-npm.yml's - # `publish-react-native` job can mint the OIDC token that npm - # Trusted Publishing exchanges for a publish token. - permissions: - contents: read - id-token: write - uses: ./.github/workflows/publish-npm.yml - secrets: inherit - with: - mode: react-native - release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }} diff --git a/.github/workflows/publish-bumped-packages.yml b/.github/workflows/publish-bumped-packages.yml deleted file mode 100644 index 198f1d77749d..000000000000 --- a/.github/workflows/publish-bumped-packages.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Publish Bumped Packages - -on: - push: - branches: - - "main" - - "*-stable" - -jobs: - # Delegate to the shared reusable workflow so every `npm publish` in - # this repo originates from one workflow file — required because npm - # Trusted Publishing only accepts one (org, repo, workflow_filename) - # per package. - publish_bumped_packages: - if: github.repository == 'react/react-native' - uses: ./.github/workflows/publish-npm.yml - secrets: inherit - with: - mode: monorepo-packages diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 62ac1e1db1b1..e44ee8661817 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -1,47 +1,139 @@ -# Reusable workflow that performs every `npm publish` in this repo. +# Single top-level workflow for every npm publish in this repo. # -# Why this exists: npmjs.com Trusted Publishing accepts only ONE -# (org, repo, workflow_filename, environment) tuple per package. If -# `react-native` were published from `publish-release.yml` AND -# `nightly.yml` directly, we'd need two Trusted Publisher entries per -# package — npm rejects that. By moving every `npm publish` into this -# single reusable workflow file, the OIDC `job_workflow_ref` claim -# always resolves to `publish-npm.yml` regardless of which top-level -# workflow triggered the run, so each package needs exactly one -# Trusted Publisher entry pointing here. +# Why: npmjs.com Trusted Publishing matches the `workflow_ref` OIDC claim, +# which is always the TOP-LEVEL workflow filename. npm allows only ONE +# trusted publisher per package, so every `npm publish` must originate +# from the same top-level file. By consolidating all publish triggers +# here, the OIDC claim is always `publish-npm.yml`. # -# See https://docs.npmjs.com/trusted-publishers and -# https://docs.github.com/en/actions/sharing-automations/reusing-workflows . -name: Publish to npm (reusable) +# This replaces the previous separate entry points: +# - publish-release.yml (tag push) → mode=release +# - nightly.yml (cron/dispatch) → mode=nightly +# - publish-bumped-packages.yml (branch push) → mode=bumped-packages +# +# See https://docs.npmjs.com/trusted-publishers +name: Publish to npm on: - workflow_call: - inputs: - mode: - description: | - 'react-native' runs the full Android/iOS-prebuilt + JS build - and publishes via scripts/releases-ci/publish-npm.js (which - publishes `react-native` and, in nightly mode, every - @react-native/* package). 'monorepo-packages' runs only the - JS build and publishes via - scripts/releases-ci/publish-updated-packages.js (delta-based, - gated on a #publish-packages-to-npm commit message). - type: string - required: true - release-type: - description: "For mode=react-native: release | nightly | dry-run." - type: string - required: false - default: "dry-run" - skip-apple-prebuilts: - description: "For mode=react-native: skip downloading prebuilt Apple artifacts." - type: boolean - required: false - default: false + push: + tags: + - "v0.*.*" # This should match v0.X.Y + - "v0.*.*-rc.*" # This should match v0.X.Y-RC.0 + branches: + - "main" + - "*-stable" + workflow_dispatch: + # nightly build @ 2:15 AM UTC + schedule: + - cron: "15 2 * * *" + +permissions: + contents: read jobs: - publish-react-native: - if: inputs.mode == 'react-native' + # ─── Determine what kind of publish this is ────────────────────── + determine_mode: + runs-on: ubuntu-latest + if: github.repository == 'react/react-native' + outputs: + mode: ${{ steps.mode.outputs.mode }} + release-type: ${{ steps.mode.outputs.release-type }} + steps: + - id: mode + run: | + if [[ "${{ github.ref_type }}" == "tag" ]]; then + echo "mode=release" >> $GITHUB_OUTPUT + echo "release-type=release" >> $GITHUB_OUTPUT + elif [[ "${{ github.event_name }}" == "schedule" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "mode=nightly" >> $GITHUB_OUTPUT + echo "release-type=nightly" >> $GITHUB_OUTPUT + elif [[ "${{ github.event_name }}" == "push" ]]; then + echo "mode=bumped-packages" >> $GITHUB_OUTPUT + echo "release-type=" >> $GITHUB_OUTPUT + fi + - run: | + echo "Mode: ${{ steps.mode.outputs.mode }}" + echo "Release type: ${{ steps.mode.outputs.release-type }}" + + # ─── Release-only: extract Hermes version for draft release ────── + set_hermes_version: + runs-on: ubuntu-latest + if: github.ref_type == 'tag' + outputs: + HERMES_VERSION: ${{ steps.set_hermes_version.outputs.HERMES_VERSION }} + steps: + - name: Checkout + uses: actions/checkout@v6 + - id: set_hermes_version + run: | + hermes_version=$(grep -oE 'HERMES_VERSION_NAME=([0-9]+\.[0-9]+\.[0-9]+)' packages/react-native/sdks/hermes-engine/version.properties | cut -d'=' -f2) + echo "HERMES_VERSION=$hermes_version" >> $GITHUB_OUTPUT + echo "HERMES_VERSION=$hermes_version" + + # ─── Apple prebuilds (release + nightly) ───────────────────────── + prebuild_apple_dependencies: + needs: [determine_mode] + if: needs.determine_mode.outputs.mode == 'release' || needs.determine_mode.outputs.mode == 'nightly' + uses: ./.github/workflows/prebuild-ios-dependencies.yml + secrets: inherit + + prebuild_react_native_core: + needs: [determine_mode, prebuild_apple_dependencies] + if: needs.determine_mode.outputs.mode == 'release' || needs.determine_mode.outputs.mode == 'nightly' + uses: ./.github/workflows/prebuild-ios-core.yml + secrets: inherit + with: + use-hermes-prebuilt: ${{ needs.determine_mode.outputs.mode == 'nightly' }} + version-type: ${{ needs.determine_mode.outputs.mode == 'nightly' && 'nightly' || '' }} + + # ─── Android build (nightly only — releases handle this in the + # build-npm-package action's Gradle step) ───────────────────── + build_android: + needs: [determine_mode] + if: needs.determine_mode.outputs.mode == 'nightly' + runs-on: ubuntu-latest + container: + image: reactnativecommunity/react-native-android:latest + env: + TERM: "dumb" + # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers + # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 + LC_ALL: C.UTF8 + GRADLE_OPTS: "-Dorg.gradle.daemon=false" + ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} + ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }} + REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Build Android + uses: ./.github/actions/build-android + with: + release-type: nightly + gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} + + # ─── Build + Publish: react-native + all @react-native/* packages + # (release and nightly modes) ───────────────────────────────── + publish_react_native: + needs: + [ + determine_mode, + build_android, + prebuild_apple_dependencies, + prebuild_react_native_core, + ] + # For nightly, also wait on build_android. Use always() so this + # job isn't skipped when build_android is skipped (release mode). + # The explicit status checks below handle the real gating. + if: | + always() && + (needs.determine_mode.outputs.mode == 'release' || needs.determine_mode.outputs.mode == 'nightly') && + needs.determine_mode.result == 'success' && + needs.prebuild_apple_dependencies.result == 'success' && + needs.prebuild_react_native_core.result == 'success' && + (needs.determine_mode.outputs.mode == 'release' || needs.build_android.result == 'success') runs-on: ubuntu-latest environment: npm-publish # `id-token: write` is required so the npm CLI can mint the OIDC @@ -91,14 +183,17 @@ jobs: - name: Build and Publish NPM Package uses: ./.github/actions/build-npm-package with: - release-type: ${{ inputs.release-type }} + release-type: ${{ needs.determine_mode.outputs.release-type }} gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - skip-apple-prebuilts: ${{ inputs.skip-apple-prebuilts && 'true' || 'false' }} - publish-monorepo-packages: - if: inputs.mode == 'monorepo-packages' + # ─── Publish bumped monorepo packages (branch push mode) ───────── + publish_bumped_packages: + needs: [determine_mode] + if: needs.determine_mode.outputs.mode == 'bumped-packages' runs-on: ubuntu-latest environment: npm-publish + # `id-token: write` is required so the npm CLI can mint the OIDC + # token that npm Trusted Publishing exchanges for a publish token. permissions: contents: read id-token: write @@ -134,3 +229,80 @@ jobs: run: yarn build-types --skip-snapshot - name: Find and publish all bumped packages run: node ./scripts/releases-ci/publish-updated-packages.js + + # ─── Release-only: post-publish steps ──────────────────────────── + post_publish: + runs-on: ubuntu-latest + needs: [determine_mode, publish_react_native] + if: needs.determine_mode.outputs.mode == 'release' + env: + REACT_NATIVE_BOT_GITHUB_TOKEN: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + - name: Publish @react-native-community/template + id: publish-template-to-npm + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} + script: | + const {publishTemplate} = require('./.github/workflow-scripts/publishTemplate.js') + const version = "${{ github.ref_name }}" + const isDryRun = false + await publishTemplate(github, version, isDryRun); + - name: Wait for template to be published + timeout-minutes: 3 + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} + script: | + const {verifyPublishedTemplate, isLatest} = require('./.github/workflow-scripts/publishTemplate.js') + const version = "${{ github.ref_name }}" + await verifyPublishedTemplate(version, isLatest()); + - name: Update rn-diff-purge to generate upgrade-support diff + run: | + curl -X POST https://api.github.com/repos/react-native-community/rn-diff-purge/dispatches \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: Bearer $REACT_NATIVE_BOT_GITHUB_TOKEN" \ + -d "{\"event_type\": \"publish\", \"client_payload\": { \"version\": \"${{ github.ref_name }}\" }}" + - name: Verify Release is on NPM + timeout-minutes: 3 + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} + script: | + const {verifyReleaseOnNpm} = require('./.github/workflow-scripts/verifyReleaseOnNpm.js'); + const {isLatest} = require('./.github/workflow-scripts/publishTemplate.js'); + const version = "${{ github.ref_name }}"; + await verifyReleaseOnNpm(version, isLatest()); + - name: Verify that artifacts are on Maven + uses: actions/github-script@v8 + with: + script: | + const {verifyArtifactsAreOnMaven} = require('./.github/workflow-scripts/verifyArtifactsAreOnMaven.js'); + const version = "${{ github.ref_name }}"; + await verifyArtifactsAreOnMaven(version); + + # ─── Release-only: changelog, podfile bump, draft release ──────── + generate_changelog: + needs: [determine_mode, publish_react_native] + if: needs.determine_mode.outputs.mode == 'release' + uses: ./.github/workflows/generate-changelog.yml + secrets: inherit + + bump_podfile_lock: + needs: [determine_mode, publish_react_native] + if: needs.determine_mode.outputs.mode == 'release' + uses: ./.github/workflows/bump-podfile-lock.yml + secrets: inherit + + create_draft_release: + needs: [determine_mode, generate_changelog, set_hermes_version] + if: needs.determine_mode.outputs.mode == 'release' + uses: ./.github/workflows/create-draft-release.yml + secrets: inherit + with: + hermesVersion: ${{ needs.set_hermes_version.outputs.HERMES_VERSION }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml deleted file mode 100644 index b896b89cfd7f..000000000000 --- a/.github/workflows/publish-release.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: Publish Release -on: - push: - tags: - - "v0.*.*" # This should match v0.X.Y - - "v0.*.*-rc.*" # This should match v0.X.Y-RC.0 -jobs: - set_release_type: - runs-on: ubuntu-latest - if: github.repository == 'react/react-native' - outputs: - RELEASE_TYPE: ${{ steps.set_release_type.outputs.RELEASE_TYPE }} - env: - EVENT_NAME: ${{ github.event_name }} - REF: ${{ github.ref }} - steps: - - id: set_release_type - run: | - echo "Setting release type to release" - echo "RELEASE_TYPE=release" >> $GITHUB_OUTPUT - - set_hermes_version: - runs-on: ubuntu-latest - if: github.repository == 'react/react-native' - outputs: - HERMES_VERSION: ${{ steps.set_hermes_version.outputs.HERMES_VERSION }} - steps: - - name: Checkout - uses: actions/checkout@v6 - - id: set_hermes_version - run: | - hermes_version=$(grep -oE 'HERMES_VERSION_NAME=([0-9]+\.[0-9]+\.[0-9]+)' packages/react-native/sdks/hermes-engine/version.properties | cut -d'=' -f2) - echo "HERMES_VERSION=$hermes_version" >> $GITHUB_OUTPUT - echo "HERMES_VERSION=$hermes_version" - - prebuild_apple_dependencies: - if: github.repository == 'react/react-native' - uses: ./.github/workflows/prebuild-ios-dependencies.yml - secrets: inherit - - prebuild_react_native_core: - uses: ./.github/workflows/prebuild-ios-core.yml - secrets: inherit - needs: [prebuild_apple_dependencies] - - # Delegate the actual npm publish to the shared reusable workflow so - # every `npm publish` in this repo originates from one workflow file — - # required because npm Trusted Publishing only accepts one - # (org, repo, workflow_filename) per package. - build_npm_package: - needs: - [ - set_release_type, - prebuild_apple_dependencies, - prebuild_react_native_core, - ] - uses: ./.github/workflows/publish-npm.yml - secrets: inherit - with: - mode: react-native - release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }} - - post_publish: - runs-on: ubuntu-latest - needs: [build_npm_package] - env: - REACT_NATIVE_BOT_GITHUB_TOKEN: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - fetch-tags: true - - name: Publish @react-native-community/template - id: publish-template-to-npm - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} - script: | - const {publishTemplate} = require('./.github/workflow-scripts/publishTemplate.js') - const version = "${{ github.ref_name }}" - const isDryRun = false - await publishTemplate(github, version, isDryRun); - - name: Wait for template to be published - timeout-minutes: 3 - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} - script: | - const {verifyPublishedTemplate, isLatest} = require('./.github/workflow-scripts/publishTemplate.js') - const version = "${{ github.ref_name }}" - await verifyPublishedTemplate(version, isLatest()); - - name: Update rn-diff-purge to generate upgrade-support diff - run: | - curl -X POST https://api.github.com/repos/react-native-community/rn-diff-purge/dispatches \ - -H "Accept: application/vnd.github.v3+json" \ - -H "Authorization: Bearer $REACT_NATIVE_BOT_GITHUB_TOKEN" \ - -d "{\"event_type\": \"publish\", \"client_payload\": { \"version\": \"${{ github.ref_name }}\" }}" - - name: Verify Release is on NPM - timeout-minutes: 3 - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} - script: | - const {verifyReleaseOnNpm} = require('./.github/workflow-scripts/verifyReleaseOnNpm.js'); - const {isLatest} = require('./.github/workflow-scripts/publishTemplate.js'); - const version = "${{ github.ref_name }}"; - await verifyReleaseOnNpm(version, isLatest()); - - name: Verify that artifacts are on Maven - uses: actions/github-script@v8 - with: - script: | - const {verifyArtifactsAreOnMaven} = require('./.github/workflow-scripts/verifyArtifactsAreOnMaven.js'); - const version = "${{ github.ref_name }}"; - await verifyArtifactsAreOnMaven(version); - - generate_changelog: - needs: build_npm_package - uses: ./.github/workflows/generate-changelog.yml - secrets: inherit - - bump_podfile_lock: - needs: build_npm_package - uses: ./.github/workflows/bump-podfile-lock.yml - secrets: inherit - - create_draft_release: - needs: [generate_changelog, set_hermes_version] - uses: ./.github/workflows/create-draft-release.yml - secrets: inherit - with: - hermesVersion: ${{ needs.set_hermes_version.outputs.HERMES_VERSION }}