Skip to content

Commit cf8bde1

Browse files
committed
Distribute React Native DevTools binaries via GitHub Releases (facebook#52930)
Summary: bypass-github-export-checks OSS release infrastructure for the (experimental) React Native DevTools standalone shell. Currently, binaries are built continuously on Meta infra and served from the Meta CDN using fbcdn.net URLs checked into a DotSlash file in the repo, e.g.: https://github.com/facebook/react-native/blob/15373218ec572c0e43325845b80a849ad5174cc3/packages/debugger-shell/bin/react-native-devtools#L9-L18 For open source releases we want to primarily distribute the binaries as GitHub release assets, while keeping the Meta CDN URLs as a secondary option. This PR makes the necessary changes to the release workflows to support this: * `workflows/create-release.yml` (modified): As part of the release commit, rewrite the DotSlash file to include the release asset URLs. * **NOTE:** After this commit, **the new URLs don't work yet**, because they refer to a release that hasn't been published. Despite this, the DotSlash file remains valid and usable (because DotSlash will happily fall back to the Meta CDN URLs, which are still in the file). * `workflows/create-draft-release.yml` (modified): After creating a draft release, fetch the binaries from the Meta CDN and reupload them to GitHub as release assets. This is based on the contents of the DotSlash file rewritten by `create-release.yml`. * `workflows/validate-dotslash-artifacts.yml` (new): After the release is published, all URLs referenced by the DotSlash (both Meta CDN URL and GH release asset URLs) should be valid and refer to the same artifacts. This workflow checks that this is the case. * If this workflow fails on a published release, the release may need to be burned or a hotfix release may be necessary - as the release will stop working correctly once the Meta CDN stops serving the assets. * This workflow will also be running continuously on `main`. If it fails on a commit in `main`, there might be a connectivity issue between the GHA runner and the Meta CDN, or there might be an issue on the Meta side. NOTE: These changes to the release pipeline are generic and reusable; if we later add another DotSlash-based tool whose binaries need to be mirrored as GitHub release assets, we just need to add it to the `FIRST_PARTY_DOTSLASH_FILES` array. [Internal] Mirror React Native DevTools binaries in GitHub Releases Pull Request resolved: facebook#52930 Test Plan: I've added unit tests for `dotslash-utils`, `curl-utils`, and for the majority of the logic that makes up the new release scripts (`write-dotslash-release-assets-urls`, `upload-release-assets-for-dotslash`, `validate-dotslash-artifacts`). Created a test branch and draft PR: facebook#53147. Locally created a release commit, simulating the create-release GH workflow: ``` node scripts/releases/create-release-commit.js --reactNativeVersion 0.82.0-20250903-0830 --no-dry-run ``` This updated the DotSlash file in the branch: facebook@2deeb7e#diff-205a9ff6005e30be061eaa64b9cb50b15b0e909dd188e0866189e952655a3483 NOTE: I've also ensured that the `create-release-commit` script correctly updates the DotSlash file when running from a branch that already has a release commit - see screenshot: <img width="1483" height="587" alt="image" src="https://github.com/user-attachments/assets/1ffd859b-e02b-483d-8067-9cc9116829a4" /> Enabled testing the create-draft-release GH workflow in the test branch using these temporary hacks: * facebook@81f334e * facebook@6d88516 * facebook@1428a8d Workflow run: https://github.com/facebook/react-native/actions/runs/17426711373/job/49475327346 Draft release: https://github.com/facebook/react-native/releases/tag/untagged-c6a62a58e5baa37936e1 Draft release screenshot for posterity (since we'll likely delete the draft release after landing this): <img width="1024" height="814" alt="image" src="https://github.com/user-attachments/assets/1900da15-48f6-4274-b29c-0ac2019d92c0" /> For obvious reasons, I've avoided actually publishing the above draft release. But I have run the `validate-dotslash-artifacts` workflow on the *current* branch to ensure that the logic is correct: https://github.com/motiz88/react-native/actions/runs/17426885205/job/49475888486 Running `node scripts/releases/validate-dotslash-artifacts.js` in the release branch (without publishing the release first) fails, as expected: <img width="1105" height="748" alt="image" src="https://github.com/user-attachments/assets/ed23a2e2-7a31-42eb-a324-f1d50eafe2fb" /> This PR is all the infra needed ahead of the 0.82 ~~branch cut~~ infra freeze to support the React Native DevTools standalone shell, at least on the GitHub side. ~~Some minor infra work remains on the Meta side, plus some product/logic changes to the React Native DevTools standalone shell that I'm intending to finish in time for 0.82 (for an experimental rollout).~~ EDIT: All the planned work has landed; the feature is code-complete on `main` as well as in `0.82-stable` (apart from this infra change). As a one-off, once we've actually published 0.82.0-rc.1, we'll want to have a human look at the published artifacts and CI workflow logs to ensure everything is in order. (I'll make sure to communicate this to the 0.82 release crew.) Afterwards, the automation added in this PR should be sufficient. Reviewed By: huntie Differential Revision: D81578704 Pulled By: motiz88 fbshipit-source-id: 6a4a48c3713221a89dd5fc88851674c1ddc6bb10
1 parent e5647a1 commit cf8bde1

27 files changed

+3215
-22
lines changed

.github/workflow-scripts/__tests__/createDraftRelease-test.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ View the whole changelog in the [CHANGELOG.md file](https://github.com/facebook/
188188
status: 201,
189189
json: () =>
190190
Promise.resolve({
191+
id: 1,
191192
html_url:
192193
'https://github.com/facebook/react-native/releases/tag/v0.77.1',
193194
}),
@@ -208,9 +209,11 @@ View the whole changelog in the [CHANGELOG.md file](https://github.com/facebook/
208209
body: fetchBody,
209210
},
210211
);
211-
expect(response).toEqual(
212-
'https://github.com/facebook/react-native/releases/tag/v0.77.1',
213-
);
212+
expect(response).toEqual({
213+
id: 1,
214+
html_url:
215+
'https://github.com/facebook/react-native/releases/tag/v0.77.1',
216+
});
214217
});
215218

216219
it('creates a draft release for prerelease on GitHub', async () => {
@@ -238,6 +241,7 @@ View the whole changelog in the [CHANGELOG.md file](https://github.com/facebook/
238241
status: 201,
239242
json: () =>
240243
Promise.resolve({
244+
id: 1,
241245
html_url:
242246
'https://github.com/facebook/react-native/releases/tag/v0.77.1',
243247
}),
@@ -258,9 +262,11 @@ View the whole changelog in the [CHANGELOG.md file](https://github.com/facebook/
258262
body: fetchBody,
259263
},
260264
);
261-
expect(response).toEqual(
262-
'https://github.com/facebook/react-native/releases/tag/v0.77.1',
263-
);
265+
expect(response).toEqual({
266+
id: 1,
267+
html_url:
268+
'https://github.com/facebook/react-native/releases/tag/v0.77.1',
269+
});
264270
});
265271

266272
it('throws if the post failes', async () => {

.github/workflow-scripts/createDraftRelease.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ async function _createDraftReleaseOnGitHub(version, body, latest, token) {
101101
}
102102

103103
const data = await response.json();
104-
return data.html_url;
104+
const {html_url, id} = data;
105+
return {
106+
html_url,
107+
id,
108+
};
105109
}
106110

107111
function moveToChangelogBranch(version) {
@@ -124,7 +128,8 @@ async function createDraftRelease(version, latest, token) {
124128
latest,
125129
token,
126130
);
127-
log(`Created draft release: ${release}`);
131+
log(`Created draft release: ${release.html_url}, ID ${release.id}`);
132+
return release;
128133
}
129134

130135
module.exports = {

.github/workflows/create-draft-release.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,24 @@ jobs:
2121
git config --local user.name "React Native Bot"
2222
- name: Create draft release
2323
uses: actions/github-script@v6
24+
id: create-draft-release
2425
with:
2526
script: |
2627
const {createDraftRelease} = require('./.github/workflow-scripts/createDraftRelease.js');
2728
const version = '${{ github.ref_name }}';
2829
const {isLatest} = require('./.github/workflow-scripts/publishTemplate.js');
29-
await createDraftRelease(version, isLatest(), '${{secrets.REACT_NATIVE_BOT_GITHUB_TOKEN}}');
30+
return (await createDraftRelease(version, isLatest(), '${{secrets.REACT_NATIVE_BOT_GITHUB_TOKEN}}')).id;
31+
result-encoding: string
32+
- name: Upload release assets for DotSlash
33+
uses: actions/github-script@v6
34+
env:
35+
RELEASE_ID: ${{ steps.create-draft-release.outputs.result }}
36+
with:
37+
script: |
38+
const {uploadReleaseAssetsForDotSlashFiles} = require('./scripts/releases/upload-release-assets-for-dotslash.js');
39+
const version = '${{ github.ref_name }}';
40+
await uploadReleaseAssetsForDotSlashFiles({
41+
version,
42+
token: '${{secrets.REACT_NATIVE_BOT_GITHUB_TOKEN}}',
43+
releaseId: process.env.RELEASE_ID,
44+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Validate DotSlash Artifacts
2+
3+
on:
4+
workflow_dispatch:
5+
release:
6+
types: [published]
7+
push:
8+
branches:
9+
- main
10+
paths:
11+
- packages/debugger-shell/bin/react-native-devtools
12+
- "scripts/releases/**"
13+
- package.json
14+
- yarn.lock
15+
pull_request:
16+
branches:
17+
- main
18+
paths:
19+
- packages/debugger-shell/bin/react-native-devtools
20+
- "scripts/releases/**"
21+
- package.json
22+
- yarn.lock
23+
# Same time as the nightly build: 2:15 AM UTC
24+
schedule:
25+
- cron: "15 2 * * *"
26+
27+
jobs:
28+
validate-dotslash-artifacts:
29+
runs-on: ubuntu-latest
30+
steps:
31+
- name: Checkout repository
32+
uses: actions/checkout@v4
33+
with:
34+
fetch-depth: 0
35+
fetch-tags: true
36+
- name: Install dependencies
37+
uses: ./.github/actions/yarn-install
38+
- name: Configure Git
39+
shell: bash
40+
run: |
41+
git config --local user.email "[email protected]"
42+
git config --local user.name "React Native Bot"
43+
- name: Validate DotSlash artifacts
44+
uses: actions/github-script@v6
45+
with:
46+
script: |
47+
const {validateDotSlashArtifacts} = require('./scripts/releases/validate-dotslash-artifacts.js');
48+
await validateDotSlashArtifacts();
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
declare module '@expo/spawn-async' {
12+
type SpawnOptions = {
13+
cwd?: string,
14+
env?: Object,
15+
argv0?: string,
16+
stdio?: string | Array<any>,
17+
detached?: boolean,
18+
uid?: number,
19+
gid?: number,
20+
shell?: boolean | string,
21+
windowsVerbatimArguments?: boolean,
22+
windowsHide?: boolean,
23+
encoding?: string,
24+
ignoreStdio?: boolean,
25+
};
26+
27+
declare class SpawnPromise<T> extends Promise<T> {
28+
child: child_process$ChildProcess;
29+
}
30+
type SpawnResult = {
31+
pid?: number,
32+
output: string[],
33+
stdout: string,
34+
stderr: string,
35+
status: number | null,
36+
signal: string | null,
37+
};
38+
39+
declare function spawnAsync(
40+
command: string,
41+
args?: $ReadOnlyArray<string>,
42+
options?: SpawnOptions,
43+
): SpawnPromise<SpawnResult>;
44+
45+
declare module.exports: typeof spawnAsync;
46+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
// Partial types for Octokit based on the usage in react-native-github
12+
declare module '@octokit/rest' {
13+
declare class Octokit {
14+
constructor(options?: {auth?: string, ...}): this;
15+
16+
repos: $ReadOnly<{
17+
listReleaseAssets: (
18+
params: $ReadOnly<{
19+
owner: string,
20+
repo: string,
21+
release_id: string,
22+
}>,
23+
) => Promise<{
24+
data: Array<{
25+
id: string,
26+
name: string,
27+
...
28+
}>,
29+
...
30+
}>,
31+
uploadReleaseAsset: (
32+
params: $ReadOnly<{
33+
owner: string,
34+
repo: string,
35+
release_id: string,
36+
name: string,
37+
data: Buffer,
38+
headers: $ReadOnly<{
39+
'content-type': string,
40+
...
41+
}>,
42+
...
43+
}>,
44+
) => Promise<{
45+
data: {
46+
browser_download_url: string,
47+
...
48+
},
49+
...
50+
}>,
51+
deleteReleaseAsset: (params: {
52+
owner: string,
53+
repo: string,
54+
asset_id: string,
55+
...
56+
}) => Promise<mixed>,
57+
}>;
58+
}
59+
60+
declare export {Octokit};
61+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
declare module 'fb-dotslash' {
12+
declare module.exports: string;
13+
}

0 commit comments

Comments
 (0)