Skip to content

Commit 567b9f0

Browse files
robhoganmeta-codesync[bot]
authored andcommitted
Fix OIDC publish, unify top-level package-publishing workflows (#57255)
Summary: ## Problem npm Trusted Publishing matches the `workflow_ref` OIDC claim, which is always the top-level workflow filename. npm allows only ONE trusted publisher per package. The prior migration (#57099) used `workflow_call` to route all publishes through `publish-npm.yml`, but `workflow_ref` resolves to the *caller* (e.g. `nightly.yml`), not the reusable child, so the Trusted Publisher entry for `publish-npm.yml` never matches. ## Solution Merge all three publish entry points into `publish-npm.yml` itself, triggered by all three event types: - `push.tags: v0.*` -> release mode (was publish-release.yml) - `schedule + workflow_dispatch` -> nightly mode (was nightly.yml) - `push.branches: main, *-stable` -> bumped-packages mode (was publish-bumped-packages.yml) A `determine_mode` job inspects the trigger and sets the mode. Downstream jobs use conditional `if:` expressions to run only the relevant build/publish steps. Since `publish-npm.yml` is now always the top-level workflow, `workflow_ref` always resolves to `publish-npm.yml`, which matches what's already configured on npm. Changelog: [Internal] Pull Request resolved: #57255 Reviewed By: cortinico Differential Revision: D108894981 Pulled By: robhogan fbshipit-source-id: 743d5b75cbce1eedfec681ec98fd17332f05f14d
1 parent 08ef7b1 commit 567b9f0

4 files changed

Lines changed: 214 additions & 283 deletions

File tree

.github/workflows/nightly.yml

Lines changed: 0 additions & 90 deletions
This file was deleted.

.github/workflows/publish-bumped-packages.yml

Lines changed: 0 additions & 19 deletions
This file was deleted.

.github/workflows/publish-npm.yml

Lines changed: 214 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,139 @@
1-
# Reusable workflow that performs every `npm publish` in this repo.
1+
# Single top-level workflow for every npm publish in this repo.
22
#
3-
# Why this exists: npmjs.com Trusted Publishing accepts only ONE
4-
# (org, repo, workflow_filename, environment) tuple per package. If
5-
# `react-native` were published from `publish-release.yml` AND
6-
# `nightly.yml` directly, we'd need two Trusted Publisher entries per
7-
# package — npm rejects that. By moving every `npm publish` into this
8-
# single reusable workflow file, the OIDC `job_workflow_ref` claim
9-
# always resolves to `publish-npm.yml` regardless of which top-level
10-
# workflow triggered the run, so each package needs exactly one
11-
# Trusted Publisher entry pointing here.
3+
# Why: npmjs.com Trusted Publishing matches the `workflow_ref` OIDC claim,
4+
# which is always the TOP-LEVEL workflow filename. npm allows only ONE
5+
# trusted publisher per package, so every `npm publish` must originate
6+
# from the same top-level file. By consolidating all publish triggers
7+
# here, the OIDC claim is always `publish-npm.yml`.
128
#
13-
# See https://docs.npmjs.com/trusted-publishers and
14-
# https://docs.github.com/en/actions/sharing-automations/reusing-workflows .
15-
name: Publish to npm (reusable)
9+
# This replaces the previous separate entry points:
10+
# - publish-release.yml (tag push) → mode=release
11+
# - nightly.yml (cron/dispatch) → mode=nightly
12+
# - publish-bumped-packages.yml (main/stable branch push) → mode=bumped-packages
13+
#
14+
# See https://docs.npmjs.com/trusted-publishers
15+
name: Publish to npm
1616

1717
on:
18-
workflow_call:
19-
inputs:
20-
mode:
21-
description: |
22-
'react-native' runs the full Android/iOS-prebuilt + JS build
23-
and publishes via scripts/releases-ci/publish-npm.js (which
24-
publishes `react-native` and, in nightly mode, every
25-
@react-native/* package). 'monorepo-packages' runs only the
26-
JS build and publishes via
27-
scripts/releases-ci/publish-updated-packages.js (delta-based,
28-
gated on a #publish-packages-to-npm commit message).
29-
type: string
30-
required: true
31-
release-type:
32-
description: "For mode=react-native: release | nightly | dry-run."
33-
type: string
34-
required: false
35-
default: "dry-run"
36-
skip-apple-prebuilts:
37-
description: "For mode=react-native: skip downloading prebuilt Apple artifacts."
38-
type: boolean
39-
required: false
40-
default: false
18+
push:
19+
tags:
20+
- "v0.*.*" # This should match v0.X.Y
21+
- "v0.*.*-rc.*" # This should match v0.X.Y-RC.0
22+
branches:
23+
- "main"
24+
- "*-stable"
25+
workflow_dispatch:
26+
# nightly build @ 2:15 AM UTC
27+
schedule:
28+
- cron: "15 2 * * *"
29+
30+
permissions:
31+
contents: read
4132

4233
jobs:
43-
publish-react-native:
44-
if: inputs.mode == 'react-native'
34+
# ─── Determine what kind of publish this is ──────────────────────
35+
determine_mode:
36+
runs-on: ubuntu-latest
37+
if: github.repository == 'react/react-native'
38+
outputs:
39+
mode: ${{ steps.mode.outputs.mode }}
40+
release-type: ${{ steps.mode.outputs.release-type }}
41+
steps:
42+
- id: mode
43+
run: |
44+
if [[ "${{ github.ref_type }}" == "tag" ]]; then
45+
echo "mode=release" >> $GITHUB_OUTPUT
46+
echo "release-type=release" >> $GITHUB_OUTPUT
47+
elif [[ "${{ github.event_name }}" == "schedule" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then
48+
echo "mode=nightly" >> $GITHUB_OUTPUT
49+
echo "release-type=nightly" >> $GITHUB_OUTPUT
50+
elif [[ "${{ github.event_name }}" == "push" ]]; then
51+
echo "mode=bumped-packages" >> $GITHUB_OUTPUT
52+
echo "release-type=" >> $GITHUB_OUTPUT
53+
fi
54+
- run: |
55+
echo "Mode: ${{ steps.mode.outputs.mode }}"
56+
echo "Release type: ${{ steps.mode.outputs.release-type }}"
57+
58+
# ─── Release-only: extract Hermes version for draft release ──────
59+
set_hermes_version:
60+
runs-on: ubuntu-latest
61+
if: github.ref_type == 'tag'
62+
outputs:
63+
HERMES_VERSION: ${{ steps.set_hermes_version.outputs.HERMES_VERSION }}
64+
steps:
65+
- name: Checkout
66+
uses: actions/checkout@v6
67+
- id: set_hermes_version
68+
run: |
69+
hermes_version=$(grep -oE 'HERMES_VERSION_NAME=([0-9]+\.[0-9]+\.[0-9]+)' packages/react-native/sdks/hermes-engine/version.properties | cut -d'=' -f2)
70+
echo "HERMES_VERSION=$hermes_version" >> $GITHUB_OUTPUT
71+
echo "HERMES_VERSION=$hermes_version"
72+
73+
# ─── Apple prebuilds (release + nightly) ─────────────────────────
74+
prebuild_apple_dependencies:
75+
needs: [determine_mode]
76+
if: needs.determine_mode.outputs.mode == 'release' || needs.determine_mode.outputs.mode == 'nightly'
77+
uses: ./.github/workflows/prebuild-ios-dependencies.yml
78+
secrets: inherit
79+
80+
prebuild_react_native_core:
81+
needs: [determine_mode, prebuild_apple_dependencies]
82+
if: needs.determine_mode.outputs.mode == 'release' || needs.determine_mode.outputs.mode == 'nightly'
83+
uses: ./.github/workflows/prebuild-ios-core.yml
84+
secrets: inherit
85+
with:
86+
use-hermes-prebuilt: ${{ needs.determine_mode.outputs.mode == 'nightly' }}
87+
version-type: ${{ needs.determine_mode.outputs.mode == 'nightly' && 'nightly' || '' }}
88+
89+
# ─── Android build (nightly only — releases handle this in the
90+
# build-npm-package action's Gradle step) ─────────────────────
91+
build_android:
92+
needs: [determine_mode]
93+
if: needs.determine_mode.outputs.mode == 'nightly'
94+
runs-on: ubuntu-latest
95+
container:
96+
image: reactnativecommunity/react-native-android:latest
97+
env:
98+
TERM: "dumb"
99+
# Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers
100+
# via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127
101+
LC_ALL: C.UTF8
102+
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
103+
ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }}
104+
ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }}
105+
ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }}
106+
ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }}
107+
REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads
108+
steps:
109+
- name: Checkout
110+
uses: actions/checkout@v6
111+
- name: Build Android
112+
uses: ./.github/actions/build-android
113+
with:
114+
release-type: nightly
115+
gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
116+
117+
# ─── Build + Publish: react-native + all @react-native/* packages
118+
# (release and nightly modes) ─────────────────────────────────
119+
publish_react_native:
120+
needs:
121+
[
122+
determine_mode,
123+
build_android,
124+
prebuild_apple_dependencies,
125+
prebuild_react_native_core,
126+
]
127+
# For nightly, also wait on build_android. Use always() so this
128+
# job isn't skipped when build_android is skipped (release mode).
129+
# The explicit status checks below handle the real gating.
130+
if: |
131+
always() &&
132+
(needs.determine_mode.outputs.mode == 'release' || needs.determine_mode.outputs.mode == 'nightly') &&
133+
needs.determine_mode.result == 'success' &&
134+
needs.prebuild_apple_dependencies.result == 'success' &&
135+
needs.prebuild_react_native_core.result == 'success' &&
136+
(needs.determine_mode.outputs.mode == 'release' || needs.build_android.result == 'success')
45137
runs-on: ubuntu-latest
46138
environment: npm-publish
47139
# `id-token: write` is required so the npm CLI can mint the OIDC
@@ -91,14 +183,17 @@ jobs:
91183
- name: Build and Publish NPM Package
92184
uses: ./.github/actions/build-npm-package
93185
with:
94-
release-type: ${{ inputs.release-type }}
186+
release-type: ${{ needs.determine_mode.outputs.release-type }}
95187
gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
96-
skip-apple-prebuilts: ${{ inputs.skip-apple-prebuilts && 'true' || 'false' }}
97188

98-
publish-monorepo-packages:
99-
if: inputs.mode == 'monorepo-packages'
189+
# ─── Publish bumped monorepo packages (main/stable push) ─────────
190+
publish_bumped_packages:
191+
needs: [determine_mode]
192+
if: needs.determine_mode.outputs.mode == 'bumped-packages'
100193
runs-on: ubuntu-latest
101194
environment: npm-publish
195+
# `id-token: write` is required so the npm CLI can mint the OIDC
196+
# token that npm Trusted Publishing exchanges for a publish token.
102197
permissions:
103198
contents: read
104199
id-token: write
@@ -134,3 +229,80 @@ jobs:
134229
run: yarn build-types --skip-snapshot
135230
- name: Find and publish all bumped packages
136231
run: node ./scripts/releases-ci/publish-updated-packages.js
232+
233+
# ─── Release-only: post-publish steps ────────────────────────────
234+
post_publish:
235+
runs-on: ubuntu-latest
236+
needs: [determine_mode, publish_react_native]
237+
if: needs.determine_mode.outputs.mode == 'release'
238+
env:
239+
REACT_NATIVE_BOT_GITHUB_TOKEN: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
240+
steps:
241+
- name: Checkout
242+
uses: actions/checkout@v6
243+
with:
244+
fetch-depth: 0
245+
fetch-tags: true
246+
- name: Publish @react-native-community/template
247+
id: publish-template-to-npm
248+
uses: actions/github-script@v8
249+
with:
250+
github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
251+
script: |
252+
const {publishTemplate} = require('./.github/workflow-scripts/publishTemplate.js')
253+
const version = "${{ github.ref_name }}"
254+
const isDryRun = false
255+
await publishTemplate(github, version, isDryRun);
256+
- name: Wait for template to be published
257+
timeout-minutes: 3
258+
uses: actions/github-script@v8
259+
with:
260+
github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
261+
script: |
262+
const {verifyPublishedTemplate, isLatest} = require('./.github/workflow-scripts/publishTemplate.js')
263+
const version = "${{ github.ref_name }}"
264+
await verifyPublishedTemplate(version, isLatest());
265+
- name: Update rn-diff-purge to generate upgrade-support diff
266+
run: |
267+
curl -X POST https://api.github.com/repos/react-native-community/rn-diff-purge/dispatches \
268+
-H "Accept: application/vnd.github.v3+json" \
269+
-H "Authorization: Bearer $REACT_NATIVE_BOT_GITHUB_TOKEN" \
270+
-d "{\"event_type\": \"publish\", \"client_payload\": { \"version\": \"${{ github.ref_name }}\" }}"
271+
- name: Verify Release is on NPM
272+
timeout-minutes: 3
273+
uses: actions/github-script@v8
274+
with:
275+
github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
276+
script: |
277+
const {verifyReleaseOnNpm} = require('./.github/workflow-scripts/verifyReleaseOnNpm.js');
278+
const {isLatest} = require('./.github/workflow-scripts/publishTemplate.js');
279+
const version = "${{ github.ref_name }}";
280+
await verifyReleaseOnNpm(version, isLatest());
281+
- name: Verify that artifacts are on Maven
282+
uses: actions/github-script@v8
283+
with:
284+
script: |
285+
const {verifyArtifactsAreOnMaven} = require('./.github/workflow-scripts/verifyArtifactsAreOnMaven.js');
286+
const version = "${{ github.ref_name }}";
287+
await verifyArtifactsAreOnMaven(version);
288+
289+
# ─── Release-only: changelog, podfile bump, draft release ────────
290+
generate_changelog:
291+
needs: [determine_mode, publish_react_native]
292+
if: needs.determine_mode.outputs.mode == 'release'
293+
uses: ./.github/workflows/generate-changelog.yml
294+
secrets: inherit
295+
296+
bump_podfile_lock:
297+
needs: [determine_mode, publish_react_native]
298+
if: needs.determine_mode.outputs.mode == 'release'
299+
uses: ./.github/workflows/bump-podfile-lock.yml
300+
secrets: inherit
301+
302+
create_draft_release:
303+
needs: [determine_mode, generate_changelog, set_hermes_version]
304+
if: needs.determine_mode.outputs.mode == 'release'
305+
uses: ./.github/workflows/create-draft-release.yml
306+
secrets: inherit
307+
with:
308+
hermesVersion: ${{ needs.set_hermes_version.outputs.HERMES_VERSION }}

0 commit comments

Comments
 (0)