Skip to content

Commit daa25bf

Browse files
committed
Auto-upload RNDT binaries as GH release assets
1 parent af5a10a commit daa25bf

File tree

6 files changed

+336
-9
lines changed

6 files changed

+336
-9
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: 6 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,7 @@ 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}`);
128132
}
129133

130134
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 {uploadReleaseAssetsForDotSlash} = require('./scripts/releases/upload-release-assets-for-dotslash.js');
39+
const version = '${{ github.ref_name }}';
40+
await uploadReleaseAssetsForDotSlash({
41+
version,
42+
token: '${{secrets.REACT_NATIVE_BOT_GITHUB_TOKEN}}',
43+
releaseId: process.env.RELEASE_ID,
44+
});

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"@jest/create-cache-key-function": "^29.7.0",
5858
"@microsoft/api-extractor": "^7.52.2",
5959
"@motizilberman/dotslash": "0.5.5-1751306809495",
60+
"@octokit/rest": "^22.0.0",
6061
"@react-native/metro-babel-transformer": "0.82.0-main",
6162
"@react-native/metro-config": "0.82.0-main",
6263
"@tsconfig/node22": "22.0.2",
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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+
'use strict';
12+
13+
/*::
14+
import type {PackageJson} from '../shared/monorepoUtils';
15+
*/
16+
17+
const {validateAndParseDotSlashFile} = require('./utils/dotslash-utils');
18+
const {REPO_ROOT} = require('../shared/consts');
19+
const {parseArgs, styleText} = require('util');
20+
const path = require('path');
21+
const {Octokit} = require('@octokit/rest');
22+
const {
23+
FIRST_PARTY_DOTSLASH_FILES,
24+
} = require('./write-dotslash-release-asset-urls');
25+
26+
const config = {
27+
allowPositionals: true,
28+
options: {
29+
token: {type: 'string'},
30+
releaseId: {type: 'string'},
31+
force: {type: 'boolean', default: false},
32+
dryRun: {type: 'boolean', default: false},
33+
help: {type: 'boolean'},
34+
},
35+
};
36+
37+
async function main() {
38+
const {
39+
positionals: [version],
40+
values: {help, token, releaseId, force, dryRun},
41+
/* $FlowFixMe[incompatible-call] Natural Inference rollout. See
42+
* https://fburl.com/workplace/6291gfvu */
43+
} = parseArgs(config);
44+
45+
if (help) {
46+
console.log(`
47+
Usage: node ./scripts/releases/upload-release-assets-for-dotslash.js <version> --release_id <id> --token <github-token> [--force]
48+
49+
Scans first-party DotSlash files in the repo for URLs referencing assets of
50+
an upcoming release, and uploads the actual assets to the GitHub release
51+
identified by the given release ID.
52+
53+
If run with --force, the script will overwrite any assets that happen to
54+
already exist at the given URLs. This is useful for retrying failed or
55+
corrupted uploads.
56+
`);
57+
return;
58+
}
59+
60+
if (version == null) {
61+
throw new Error('Missing version argument');
62+
}
63+
64+
await uploadReleaseAssetsForDotSlash({
65+
version,
66+
token,
67+
releaseId,
68+
force,
69+
dryRun,
70+
});
71+
}
72+
73+
async function uploadReleaseAssetsForDotSlash(
74+
{version, token, releaseId, force = false, dryRun = false} /*: {
75+
version: string,
76+
token: string,
77+
releaseId: string,
78+
force?: boolean,
79+
dryRun?: boolean,
80+
} */,
81+
) /*: Promise<void> */ {
82+
const releaseTag = `v${version}`;
83+
const releaseAssetPrefix = `https://github.com/facebook/react-native/releases/download/${encodeURIComponent(releaseTag)}/`;
84+
const octokit = new Octokit({auth: token});
85+
const existingAssets = await octokit.repos.listReleaseAssets({
86+
owner: 'facebook',
87+
repo: 'react-native',
88+
release_id: releaseId,
89+
});
90+
const existingAssetsByName = new Map(
91+
existingAssets.data.map(asset => [asset.name, asset]),
92+
);
93+
for (const filename of FIRST_PARTY_DOTSLASH_FILES) {
94+
const fullPath = path.join(REPO_ROOT, filename);
95+
console.log(`Uploading assets for ${filename}...`);
96+
const uploadPromises = [];
97+
await processDotSlashFileInPlace(
98+
fullPath,
99+
(
100+
providers,
101+
// Ignore _suggestedFilename in favour of reading the actual asset URLs
102+
_suggestedFilename,
103+
) => {
104+
let upstreamUrl, targetReleaseAssetInfo;
105+
for (const provider of providers) {
106+
if (provider.type != null && provider.type !== 'http') {
107+
continue;
108+
}
109+
const url = provider.url;
110+
if (url.startsWith(releaseAssetPrefix)) {
111+
const name = decodeURIComponent(
112+
url.slice(releaseAssetPrefix.length),
113+
);
114+
targetReleaseAssetInfo = {name, url};
115+
} else {
116+
upstreamUrl = url;
117+
}
118+
if (upstreamUrl != null && targetReleaseAssetInfo != null) {
119+
break;
120+
}
121+
}
122+
if (targetReleaseAssetInfo == null) {
123+
// This DotSlash providers array does not reference any relevant release asset URLs, so we can ignore it.
124+
return;
125+
}
126+
if (upstreamUrl == null) {
127+
throw new Error(
128+
`No upstream URL found for release asset ${targetReleaseAssetInfo.name}`,
129+
);
130+
}
131+
uploadPromises.push(
132+
(async () => {
133+
if (existingAssetsByName.has(targetReleaseAssetInfo.name)) {
134+
if (!force) {
135+
console.log(
136+
`[${targetReleaseAssetInfo.name}] Skipping existing release asset...`,
137+
);
138+
return;
139+
}
140+
if (dryRun) {
141+
console.log(
142+
`[${targetReleaseAssetInfo.name}] Dry run: Not deleting existing release asset.`,
143+
);
144+
} else {
145+
console.log(
146+
`[${targetReleaseAssetInfo.name}] Deleting existing release asset...`,
147+
);
148+
await octokit.repos.deleteReleaseAsset({
149+
owner: 'facebook',
150+
repo: 'react-native',
151+
asset_id: existingAssetsByName.get(
152+
targetReleaseAssetInfo.name,
153+
).id,
154+
});
155+
}
156+
}
157+
console.log(
158+
`[${targetReleaseAssetInfo.name}] Downloading from ${upstreamUrl}...`,
159+
);
160+
const response = await fetch(upstreamUrl);
161+
const data = await response.buffer();
162+
const contentType = response.headers.get('content-type');
163+
if (dryRun) {
164+
console.log(
165+
`[${targetReleaseAssetInfo.name}] Dry run: Not uploading to release.`,
166+
);
167+
return;
168+
} else {
169+
console.log(
170+
`[${targetReleaseAssetInfo.name}] Uploading to release...`,
171+
);
172+
await octokit.repos.uploadReleaseAsset({
173+
owner: 'facebook',
174+
repo: 'react-native',
175+
release_id: releaseId,
176+
name: targetReleaseAssetInfo.name,
177+
data,
178+
headers: {
179+
'content-type': contentType,
180+
},
181+
});
182+
}
183+
})(),
184+
);
185+
},
186+
);
187+
await Promise.all(uploadPromises);
188+
}
189+
}
190+
191+
module.exports = {
192+
uploadReleaseAssetsForDotSlash,
193+
};
194+
195+
if (require.main === module) {
196+
void main();
197+
}

0 commit comments

Comments
 (0)