Skip to content

sync-react: Handle version bumps across SemVer minors and release channels #74091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 46 additions & 40 deletions scripts/sync-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const pullRequestReviewers = ['eps1lon']
*/
const pagesRouterReact = '^19.0.0'

const defaultLatestChannel = 'canary'
const filesReferencingReactPeerDependencyVersion = [
'run-tests.js',
'packages/create-next-app/templates/index.ts',
Expand All @@ -38,6 +39,24 @@ const appManifestsInstallingNextjsPeerDependencies = [
'test/e2e/next-test/first-time-setup-ts/package.json',
]

async function getSchedulerVersion(reactVersion) {
const url = `https://registry.npmjs.org/react-dom/${reactVersion}`
const response = await fetch(url, {
headers: {
Accept: 'application/json',
},
})
if (!response.ok) {
throw new Error(
`${url}: ${response.status} ${response.statusText}\n${await response.text()}`
)
}

const manifest = await response.json()

return manifest.dependencies['scheduler']
}

// Use this script to update Next's vendored copy of React and related packages:
//
// Basic usage (defaults to most recent React canary version):
Expand All @@ -46,13 +65,7 @@ const appManifestsInstallingNextjsPeerDependencies = [
// Update package.json but skip installing the dependencies automatically:
// pnpm run sync-react --no-install

async function sync({
channel,
newVersionStr,
newSha,
newDateString,
noInstall,
}) {
async function sync({ channel, newVersionStr, noInstall }) {
const useExperimental = channel === 'experimental'
const cwd = process.cwd()
const pkgJson = JSON.parse(
Expand All @@ -64,38 +77,35 @@ async function sync({
useExperimental ? 'react-experimental-builtin' : 'react-builtin'
].replace(/^npm:react@/, '')

const baseVersionInfo = extractInfoFromReactVersion(baseVersionStr)
if (!baseVersionInfo) {
throw new Error(
'Base react version does not match expected format: ' + baseVersionStr
)
}

const {
sha: baseSha,
releaseLabel: baseReleaseLabel,
dateString: baseDateString,
} = baseVersionInfo

console.log(`Updating "react@${channel}" to ${newSha}...\n`)
if (newSha === baseSha) {
console.log(`Updating "react@${channel}" to ${newVersionStr}...`)
if (newVersionStr === baseVersionStr) {
console.log('Already up to date.')
return
}

const baseSchedulerVersionStr = devDependencies[
useExperimental ? 'scheduler-experimental-builtin' : 'scheduler-builtin'
].replace(/^npm:scheduler@/, '')
const newSchedulerVersionStr = await getSchedulerVersion(newVersionStr)
console.log(`Updating "scheduler@${channel}" to ${newSchedulerVersionStr}...`)

for (const [dep, version] of Object.entries(devDependencies)) {
if (version.endsWith(`${baseReleaseLabel}-${baseSha}-${baseDateString}`)) {
if (version.endsWith(baseVersionStr)) {
devDependencies[dep] = version.replace(baseVersionStr, newVersionStr)
} else if (version.endsWith(baseSchedulerVersionStr)) {
devDependencies[dep] = version.replace(
`${baseReleaseLabel}-${baseSha}-${baseDateString}`,
`${channel}-${newSha}-${newDateString}`
baseSchedulerVersionStr,
newSchedulerVersionStr
)
}
}
for (const [dep, version] of Object.entries(pnpmOverrides)) {
if (version.endsWith(`${baseReleaseLabel}-${baseSha}-${baseDateString}`)) {
if (version.endsWith(baseVersionStr)) {
pnpmOverrides[dep] = version.replace(baseVersionStr, newVersionStr)
} else if (version.endsWith(baseSchedulerVersionStr)) {
pnpmOverrides[dep] = version.replace(
`${baseReleaseLabel}-${baseSha}-${baseDateString}`,
`${channel}-${newSha}-${newDateString}`
baseSchedulerVersionStr,
newSchedulerVersionStr
)
}
}
Expand Down Expand Up @@ -224,7 +234,7 @@ async function main() {
) {
const { stdout, stderr } = await execa(
'npm',
['--silent', 'view', 'react@canary', 'version'],
['--silent', 'view', `react@${defaultLatestChannel}`, 'version'],
{
// Avoid "Usage Error: This project is configured to use pnpm".
cwd: '/tmp',
Expand All @@ -236,7 +246,7 @@ async function main() {
}
newVersionStr = stdout.trim()
console.log(
`--version was not provided. Using react@canary: ${newVersionStr}`
`--version was not provided. Using react@${defaultLatestChannel}: ${newVersionStr}`
)
}

Expand All @@ -253,7 +263,7 @@ Or, run this command with no arguments to use the most recently published versio
}
const { sha: newSha, dateString: newDateString } = newVersionInfo

const branchName = `update/react/${newSha}-${newDateString}`
const branchName = `update/react/${newVersionStr}`
if (createPull) {
const { exitCode, all, command } = await execa(
'git',
Expand Down Expand Up @@ -292,24 +302,20 @@ Or, run this command with no arguments to use the most recently published versio
)

await sync({
newDateString,
newSha,
newVersionStr,
newVersionStr: `0.0.0-experimental-${newSha}-${newDateString}`,
noInstall: !install,
channel: 'experimental',
})
if (commit) {
await commitEverything('Update `react@experimental`')
}
await sync({
newDateString,
newSha,
newVersionStr,
noInstall: !install,
channel: 'rc',
channel: '<framework-stable>',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't a meaningful channel here. It's whatever you passed in --version which is what we consider "framework stable".

})
if (commit) {
await commitEverything('Update `react@rc`')
await commitEverything('Update `react`')
}

const baseVersionInfo = extractInfoFromReactVersion(baseVersionStr)
Expand Down Expand Up @@ -394,9 +400,9 @@ Or, run this command with no arguments to use the most recently published versio

// Install the updated dependencies and build the vendored React files.
if (!install) {
console.log('Skipping install step because --no-install flag was passed.\n')
console.log('Skipping install step because --no-install flag was passed.')
} else {
console.log('Installing dependencies...\n')
console.log('Installing dependencies...')

const installSubprocess = execa('pnpm', [
'install',
Expand Down
Loading