@@ -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+
1938export 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