Skip to content
Closed
Changes from 1 commit
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
Next Next commit
sync-react: Handle version bumps across SemVer minors and release cha…
…nnels
  • Loading branch information
eps1lon committed Dec 18, 2024
commit 3375e2c91cd2d6c535f367f0fcb50925317cf7dc
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>',
})
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