11import path from 'path' ;
22import fs from 'fs-extra' ;
33import { getLoader , logger } from '@react-native-community/cli-tools' ;
4+ import * as fetch from 'npm-registry-fetch' ;
45import chalk from 'chalk' ;
56import { prompt } from 'prompts' ;
67import execa from 'execa' ;
8+ import semver from 'semver' ;
9+ import { isProjectUsingYarn } from './yarn' ;
10+ import { installPods } from '@react-native-community/cli-doctor' ;
711
812interface DependencyInfo {
913 path : string ;
1014 peerDependencies : { [ key : string ] : string } ;
1115}
1216
13- function isUsingYarn ( root : string ) {
14- return fs . existsSync ( path . join ( root , 'yarn.lock' ) ) ;
17+ async function fetchAvailableVersions ( packageName : string ) : Promise < string [ ] > {
18+ const response = await fetch . json ( `/${ packageName } ` ) ;
19+
20+ return Object . keys ( response . versions || { } ) ;
21+ }
22+
23+ async function calculateWorkingVersion (
24+ ranges : string [ ] ,
25+ availableVersions : string [ ] ,
26+ ) : Promise < string | null > {
27+ const sortedVersions = availableVersions
28+ . filter ( ( version ) =>
29+ ranges . every ( ( range ) => semver . satisfies ( version , range ) ) ,
30+ )
31+ . sort ( semver . rcompare ) ;
32+
33+ return sortedVersions . length > 0 ? sortedVersions [ 0 ] : null ;
1534}
1635
1736function getPeerDependencies (
@@ -61,21 +80,19 @@ function getPeerDependencies(
6180function excludeInstalledPeerDependencies (
6281 root : string ,
6382 peerDependencies : Map < string , DependencyInfo > ,
64- yarn = true ,
83+ packageJson : any ,
6584) {
66- const packageJson = require ( path . join ( root , 'package.json' ) ) ;
6785 const missingPeerDependencies : Record < string , Record < string , string > > = { } ;
6886 peerDependencies . forEach ( ( value , key ) => {
6987 const missingDeps = Object . entries ( value . peerDependencies ) . reduce (
7088 ( missingDepsList , [ name , version ] ) => {
7189 const rootPath = path . join ( root , 'node_modules' , name ) ;
7290 if (
73- ( yarn && ! fs . existsSync ( rootPath ) ) ||
74- ( ! yarn &&
75- ( fs . existsSync ( path . join ( rootPath , 'ios' ) ) ||
76- fs . existsSync ( path . join ( rootPath , 'android' ) ) ) &&
77- ! Object . keys ( packageJson . dependencies ) . includes ( name ) )
91+ ( fs . existsSync ( path . join ( rootPath , 'ios' ) ) ||
92+ fs . existsSync ( path . join ( rootPath , 'android' ) ) ) &&
93+ ! Object . keys ( packageJson . dependencies ) . includes ( name )
7894 ) {
95+ //@ts -ignore
7996 missingDepsList [ name ] = version ;
8097 }
8198 return missingDepsList ;
@@ -93,56 +110,110 @@ function excludeInstalledPeerDependencies(
93110
94111export default async function installTransitiveDeps ( ) {
95112 const root = process . cwd ( ) ;
96- const packageJsonPath = path . join ( root , 'package.json' ) ;
97- const packageJson = require ( packageJsonPath ) ;
98- const isYarn = isUsingYarn ( root ) ;
99- const peerDependencies = getPeerDependencies ( root , packageJson ) ;
100- const depsToInstall = excludeInstalledPeerDependencies (
101- root ,
102- peerDependencies ,
103- isYarn ,
104- ) ;
105-
106- const dependenciesWithMissingDeps = Object . keys ( depsToInstall ) ;
107- if ( dependenciesWithMissingDeps . length > 0 ) {
108- logger . warn (
109- 'Looks like you are missing some of the peer dependencies of your libraries:\n' ,
113+ const isYarn = ! ! isProjectUsingYarn ( root ) ;
114+
115+ let newDependenciesFound = true ;
116+
117+ while ( newDependenciesFound ) {
118+ const packageJsonPath = path . join ( root , 'package.json' ) ;
119+ const packageJson = JSON . parse (
120+ fs . readFileSync ( packageJsonPath , { encoding : 'utf8' } ) ,
110121 ) ;
111- logger . log (
112- dependenciesWithMissingDeps
113- . map (
114- ( dep ) =>
115- `\t${ chalk . bold ( dep ) } :\n ${ Object . keys ( depsToInstall [ dep ] ) . map (
116- ( d ) => `\t- ${ d } \n` ,
117- ) } `,
118- )
119- . join ( '\n' )
120- . replace ( / , / g, '' ) ,
122+ const peerDependencies = getPeerDependencies ( root , packageJson ) ;
123+ const depsToInstall = excludeInstalledPeerDependencies (
124+ root ,
125+ peerDependencies ,
126+ packageJson ,
121127 ) ;
122- const { install} = await prompt ( {
123- type : 'confirm' ,
124- name : 'install' ,
125- message :
126- 'Do you want to install them now? The matching versions will be added as project dependencies and become visible for autolinking.' ,
127- } ) ;
128- const loader = getLoader ( { text : 'Installing peer dependencies...' } ) ;
129-
130- if ( install ) {
131- let deps = new Set ( ) ;
132- dependenciesWithMissingDeps . map ( ( dep ) => {
133- const missingDeps = depsToInstall [ dep ] ;
134- deps . add ( Object . keys ( missingDeps ) ) ;
128+ const dependenciesWithMissingDeps = Object . keys ( depsToInstall ) ;
129+
130+ if ( dependenciesWithMissingDeps . length > 0 ) {
131+ logger . warn (
132+ 'Looks like you are missing some of the peer dependencies of your libraries:\n' ,
133+ ) ;
134+ logger . log (
135+ dependenciesWithMissingDeps
136+ . map (
137+ ( dep ) =>
138+ `\t${ chalk . bold ( dep ) } :\n ${ Object . keys ( depsToInstall [ dep ] ) . map (
139+ ( d ) => `\t- ${ d } \n` ,
140+ ) } `,
141+ )
142+ . join ( '\n' )
143+ . replace ( / , / g, '' ) ,
144+ ) ;
145+
146+ const packageToRanges : { [ pkg : string ] : string [ ] } = { } ;
147+
148+ for ( const dependency in depsToInstall ) {
149+ const packages = depsToInstall [ dependency ] ;
150+
151+ for ( const packageName in packages ) {
152+ if ( ! packageToRanges [ packageName ] ) {
153+ packageToRanges [ packageName ] = [ ] ;
154+ }
155+ packageToRanges [ packageName ] . push ( packages [ packageName ] ) ;
156+ }
157+ }
158+
159+ const workingVersions : { [ pkg : string ] : string | null } = { } ;
160+
161+ for ( const packageName in packageToRanges ) {
162+ const ranges = packageToRanges [ packageName ] ;
163+ const availableVersions = await fetchAvailableVersions ( packageName ) ;
164+ const workingVersion = await calculateWorkingVersion (
165+ ranges ,
166+ availableVersions ,
167+ ) ;
168+ workingVersions [ packageName ] = workingVersion ;
169+ }
170+
171+ const { install} = await prompt ( {
172+ type : 'confirm' ,
173+ name : 'install' ,
174+ message :
175+ 'Do you want to install them now? The matching versions will be added as project dependencies and become visible for autolinking.' ,
135176 } ) ;
136- const arr = Array . from ( deps ) as string [ ] ;
137- const flat = [ ] . concat ( ...arr ) ;
138- loader . start ( ) ;
177+ const loader = getLoader ( { text : 'Installing peer dependencies...' } ) ;
178+
179+ if ( install ) {
180+ const arr = Object . entries ( workingVersions ) . map (
181+ ( [ name , version ] ) => `${ name } @^${ version } ` ,
182+ ) ;
183+ //@ts -ignore
184+ const flat = [ ] . concat ( ...arr ) ;
185+
186+ loader . start ( ) ;
139187
140- if ( isYarn ) {
141- execa . sync ( 'yarn' , [ 'add' , ...flat . map ( ( dep ) => dep ) ] ) ;
188+ if ( isYarn ) {
189+ execa . sync ( 'yarn' , [ 'add' , ...flat . map ( ( dep ) => dep ) ] ) ;
190+ } else {
191+ execa . sync ( 'npm' , [ 'install' , ...flat . map ( ( dep ) => dep ) ] ) ;
192+ }
193+ loader . succeed ( ) ;
142194 } else {
143- execa . sync ( 'npm' , [ 'install' , ... flat . map ( ( dep ) => dep ) ] ) ;
195+ newDependenciesFound = false ;
144196 }
145- loader . succeed ( ) ;
197+ } else {
198+ newDependenciesFound = false ;
146199 }
147200 }
201+
202+ return ! newDependenciesFound ;
203+ }
204+
205+ export async function resolvePodsInstallation ( ) {
206+ const { install} = await prompt ( {
207+ type : 'confirm' ,
208+ name : 'install' ,
209+ message :
210+ 'Do you want to install pods? This will make sure your transitive dependencies are linked properly.' ,
211+ } ) ;
212+
213+ if ( install && process . platform === 'darwin' ) {
214+ const loader = getLoader ( { text : 'Installing pods...' } ) ;
215+ loader . start ( ) ;
216+ await installPods ( loader ) ;
217+ loader . succeed ( ) ;
218+ }
148219}
0 commit comments