Skip to content

Commit 606470a

Browse files
authored
next-upgrade: Infer React types version from runtime version (#70626)
1 parent de40fa9 commit 606470a

File tree

1 file changed

+53
-4
lines changed

1 file changed

+53
-4
lines changed

packages/next-codemod/bin/upgrade.ts

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@ interface Response {
1616
version: StandardVersionSpecifier
1717
}
1818

19+
/**
20+
* @param query
21+
* @example loadHighestNPMVersionMatching("react@^18.3.0 || ^19.0.0") === Promise<"19.0.0">
22+
*/
23+
async function loadHighestNPMVersionMatching(query: string) {
24+
const versionsJSON = execSync(
25+
`npm --silent view "${query}" --json --field version`,
26+
{ encoding: 'utf-8' }
27+
)
28+
const versions = JSON.parse(versionsJSON)
29+
if (versions.length < 1) {
30+
throw new Error(
31+
`Found no React versions matching "${query}". This is a bug in the upgrade tool.`
32+
)
33+
}
34+
35+
return versions[versions.length - 1]
36+
}
37+
1938
export async function runUpgrade(): Promise<void> {
2039
const appPackageJsonPath = path.resolve(process.cwd(), 'package.json')
2140
let appPackageJson = JSON.parse(fs.readFileSync(appPackageJsonPath, 'utf8'))
@@ -115,6 +134,27 @@ export async function runUpgrade(): Promise<void> {
115134

116135
const targetNextVersion = targetNextPackageJson.version
117136

137+
// We're resolving a specific version here to avoid including "ugly" version queries
138+
// in the manifest.
139+
// E.g. in peerDependencies we could have `^18.2.0 || ^19.0.0 || 20.0.0-canary`
140+
// If we'd just `npm add` that, the manifest would read the same version query.
141+
// This is basically a `npm --save-exact react@$versionQuery` that works for every package manager.
142+
const [
143+
targetReactVersion,
144+
targetReactTypesVersion,
145+
targetReactDOMTypesVersion,
146+
] = await Promise.all([
147+
loadHighestNPMVersionMatching(
148+
`react@${targetNextPackageJson.peerDependencies['react']}`
149+
),
150+
loadHighestNPMVersionMatching(
151+
`@types/react@${targetNextPackageJson.peerDependencies['react']}`
152+
),
153+
loadHighestNPMVersionMatching(
154+
`@types/react-dom@${targetNextPackageJson.peerDependencies['react']}`
155+
),
156+
])
157+
118158
if (
119159
targetNextVersion &&
120160
compareVersions(targetNextVersion, '15.0.0-canary') >= 0
@@ -127,11 +167,20 @@ export async function runUpgrade(): Promise<void> {
127167
const packageManager: PackageManager = getPkgManager(process.cwd())
128168
const nextDependency = `next@${targetNextVersion}`
129169
const reactDependencies = [
130-
`react@${targetNextPackageJson.peerDependencies['react']}`,
131-
`@types/react@${targetNextPackageJson.devDependencies['@types/react']}`,
132-
`react-dom@${targetNextPackageJson.peerDependencies['react-dom']}`,
133-
`@types/react-dom@${targetNextPackageJson.devDependencies['@types/react-dom']}`,
170+
`react@${targetReactVersion}`,
171+
`react-dom@${targetReactVersion}`,
134172
]
173+
if (
174+
targetReactVersion.startsWith('19.0.0-canary') ||
175+
targetReactVersion.startsWith('19.0.0-beta') ||
176+
targetReactVersion.startsWith('19.0.0-rc')
177+
) {
178+
reactDependencies.push(`@types/react@npm:types-react@rc`)
179+
reactDependencies.push(`@types/react-dom@npm:types-react-dom@rc`)
180+
} else {
181+
reactDependencies.push(`@types/react@${targetReactTypesVersion}`)
182+
reactDependencies.push(`@types/react-dom@${targetReactDOMTypesVersion}`)
183+
}
135184

136185
installPackages([nextDependency, ...reactDependencies], packageManager)
137186

0 commit comments

Comments
 (0)