Skip to content

Commit 6bac0d4

Browse files
committed
Create React sync automatically
1 parent 5888bb9 commit 6bac0d4

File tree

2 files changed

+152
-19
lines changed

2 files changed

+152
-19
lines changed

.github/workflows/update_react.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Update React
2+
3+
on:
4+
schedule:
5+
# At 40 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
6+
# i.e. 30min past React nightlies: https://github.com/facebook/react/blob/941e1b4a0a81ca3d5f2ac6ef35682e2f8e96dae1/.github/workflows/runtime_prereleases_nightly.yml#L6
7+
# TODO: automatically trigger on React release
8+
- cron: 40 16 * * 1,2,3,4,5
9+
# Allow manual runs
10+
workflow_dispatch:
11+
inputs:
12+
version:
13+
description: 'The version to update to. Uses latest Canary if omitted.'
14+
required: false
15+
16+
17+
env:
18+
NODE_LTS_VERSION: 20
19+
20+
jobs:
21+
create-pull-request:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
with:
27+
# Commits made with the default `GITHUB_TOKEN` won't trigger workflows.
28+
# See: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow
29+
token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
30+
31+
- name: Setup node
32+
uses: actions/setup-node@v4
33+
with:
34+
node-version: ${{ env.NODE_LTS_VERSION }}
35+
check-latest: true
36+
37+
- run: corepack enable
38+
39+
- name: Install dependencies
40+
shell: bash
41+
run: pnpm i
42+
43+
- name: Create Pull Request
44+
shell: bash
45+
run: pnpm sync-react --actor "${{ github.actor }}" --version "${{ inputs.version }}" --create-pull
46+
env:
47+
GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

scripts/sync-react.js

Lines changed: 105 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ const yargs = require('yargs')
99
/** @type {any} */
1010
const fetch = require('node-fetch')
1111

12+
const repoOwner = 'vercel'
13+
const repoName = 'next.js'
14+
const pullRequestLabels = ['type: react-sync']
15+
const pullRequestReviewers = ['eps1lon']
16+
1217
const filesReferencingReactPeerDependencyVersion = [
1318
'run-tests.js',
1419
'packages/create-next-app/templates/index.ts',
@@ -153,9 +158,39 @@ async function getChangelogFromGitHub(baseSha, newSha) {
153158
async function main() {
154159
const cwd = process.cwd()
155160
const errors = []
156-
const { noInstall, version } = await yargs(process.argv.slice(2))
157-
.options('version', { default: null, type: 'string' })
158-
.options('no-install', { default: false, type: 'boolean' }).argv
161+
const { actor, createPull, noCommit, noInstall, version } = await yargs(
162+
process.argv.slice(2)
163+
)
164+
.options('actor', {
165+
type: 'string',
166+
description:
167+
'Required with `--create-pull`. The actor (GitHub username) that runs this script. Will be used for notifications but not commit attribution.',
168+
})
169+
.options('create-pull', {
170+
default: false,
171+
type: 'boolean',
172+
description: 'Create a Pull Request in vercel/next.js',
173+
})
174+
.options('no-commit', {
175+
default: false,
176+
type: 'boolean',
177+
description: 'Will not create any commit',
178+
})
179+
.options('no-install', { default: false, type: 'boolean' })
180+
.options('version', { default: null, type: 'string' }).argv
181+
182+
if (createPull && !actor) {
183+
throw new Error(
184+
`Pull Request cannot be created without a GitHub actor (received '${String(actor)}'). ` +
185+
'Pass an actor via `--actor "some-actor"`.'
186+
)
187+
}
188+
const githubToken = process.env.GITHUB_TOKEN
189+
if (createPull && !githubToken) {
190+
throw new Error(
191+
`Environment variable 'GITHUB_TOKEN' not specified but required when --create-pull is specified.`
192+
)
193+
}
159194

160195
let newVersionStr = version
161196
if (newVersionStr === null) {
@@ -201,13 +236,21 @@ Or, run this command with no arguments to use the most recently published versio
201236
noInstall,
202237
channel: 'experimental',
203238
})
239+
if (!noCommit) {
240+
await execa('git', ['add', '-A'])
241+
await execa('git', ['commit', '--message', 'Update `react@experimental`'])
242+
}
204243
await sync({
205244
newDateString,
206245
newSha,
207246
newVersionStr,
208247
noInstall,
209248
channel: 'rc',
210249
})
250+
if (!noCommit) {
251+
await execa('git', ['add', '-A'])
252+
await execa('git', ['commit', '--message', 'Update `react@rc`'])
253+
}
211254

212255
const baseVersionInfo = extractInfoFromReactVersion(baseVersionStr)
213256
if (!baseVersionInfo) {
@@ -267,6 +310,15 @@ Or, run this command with no arguments to use the most recently published versio
267310
)
268311
}
269312

313+
if (!noCommit) {
314+
await execa('git', ['add', '-A'])
315+
await execa('git', [
316+
'commit',
317+
'--message',
318+
'Updated peer dependency references',
319+
])
320+
}
321+
270322
// Install the updated dependencies and build the vendored React files.
271323
if (noInstall) {
272324
console.log('Skipping install step because --no-install flag was passed.\n')
@@ -300,32 +352,28 @@ Or, run this command with no arguments to use the most recently published versio
300352

301353
// Print extra newline after ncc output
302354
console.log()
355+
356+
if (!noCommit) {
357+
await execa('git', ['add', '-A'])
358+
await execa('git', ['commit', '--message', 'Updated lockfile'])
359+
}
303360
}
304361

305-
console.log(
306-
`**breaking change for canary users: Bumps peer dependency of React from \`${baseVersionStr}\` to \`${newVersionStr}\`**`
307-
)
362+
let prDescription = `**breaking change for canary users: Bumps peer dependency of React from \`${baseVersionStr}\` to \`${newVersionStr}\`**\n\n`
308363

309364
// Fetch the changelog from GitHub and print it to the console.
310-
console.log(
311-
`[diff facebook/react@${baseSha}...${newSha}](https://github.com/facebook/react/compare/${baseSha}...${newSha})`
312-
)
365+
prDescription += `[diff facebook/react@${baseSha}...${newSha}](https://github.com/facebook/react/compare/${baseSha}...${newSha})\n\n`
313366
try {
314367
const changelog = await getChangelogFromGitHub(baseSha, newSha)
315368
if (changelog === null) {
316-
console.log(
317-
`GitHub reported no changes between ${baseSha} and ${newSha}.`
318-
)
369+
prDescription += `GitHub reported no changes between ${baseSha} and ${newSha}.`
319370
} else {
320-
console.log(
321-
`<details>\n<summary>React upstream changes</summary>\n\n${changelog}\n\n</details>`
322-
)
371+
prDescription += `<details>\n<summary>React upstream changes</summary>\n\n${changelog}\n\n</details>`
323372
}
324373
} catch (error) {
325374
console.error(error)
326-
console.log(
375+
prDescription +=
327376
'\nFailed to fetch changelog from GitHub. Changes were applied, anyway.\n'
328-
)
329377
}
330378

331379
if (noInstall) {
@@ -341,13 +389,51 @@ Or run this command again without the --no-install flag to do both automatically
341389
)
342390
}
343391

344-
await fsp.writeFile(path.join(cwd, '.github/.react-version'), newVersionStr)
345-
346392
if (errors.length) {
347393
// eslint-disable-next-line no-undef -- Defined in Node.js
348394
throw new AggregateError(errors)
349395
}
350396

397+
if (createPull) {
398+
const github = await import('@actions/github')
399+
const octokit = github.getOctokit(githubToken)
400+
const branchName = `update/react/${newSha}-${newDateString}`
401+
402+
const pullRequest = await octokit.rest.pulls.create({
403+
owner: repoOwner,
404+
repo: repoName,
405+
head: branchName,
406+
base: 'main',
407+
draft: false,
408+
title: `Upgrade React from \`${baseSha}-${baseDateString}\` to \`${newSha}-${newDateString}\``,
409+
body: prDescription,
410+
})
411+
412+
await Promise.all([
413+
actor
414+
? octokit.rest.issues.addAssignees({
415+
owner: repoOwner,
416+
repo: repoName,
417+
issue_number: pullRequest.data.number,
418+
assignees: [actor],
419+
})
420+
: Promise.resolve(),
421+
octokit.rest.pulls.requestReviewers({
422+
owner: repoOwner,
423+
repo: repoName,
424+
pull_number: pullRequest.data.number,
425+
reviewers: pullRequestReviewers,
426+
}),
427+
octokit.rest.issues.addLabels({
428+
owner: repoOwner,
429+
repo: repoName,
430+
issue_number: pullRequest.data.number,
431+
labels: pullRequestLabels,
432+
}),
433+
])
434+
}
435+
436+
console.log(prDescription)
351437
console.log(
352438
`Successfully updated React from \`${baseSha}-${baseDateString}\` to \`${newSha}-${newDateString}\``
353439
)

0 commit comments

Comments
 (0)