11import dedent from 'dedent' ;
22import fs from 'fs-extra' ;
33import { getLatestVersion } from 'get-latest-version' ;
4- import https from 'https ' ;
4+ import kleur from 'kleur ' ;
55import path from 'path' ;
6- import { SUPPORTED_MONOREPO_CONFIG_VERSION } from '../constants' ;
6+ import {
7+ SUPPORTED_EXPO_SDK_VERSION ,
8+ SUPPORTED_MONOREPO_CONFIG_VERSION ,
9+ SUPPORTED_REACT_NATIVE_VERSION ,
10+ } from '../constants' ;
711import type { TemplateConfiguration } from '../template' ;
812import sortObjectKeys from '../utils/sortObjectKeys' ;
913import { spawn } from '../utils/spawn' ;
@@ -48,10 +52,75 @@ const PACKAGES_TO_ADD_DEV_EXPO_NATIVE = {
4852 'expo-dev-client' : '~5.0.3' ,
4953} ;
5054
55+ async function fetchReactNativeVersion ( version : string ) {
56+ const matchedReactNativeVersion = / ( \d + \. \d + [ - . 0 - 9 a - z ] * ) / . test ( version )
57+ ? version
58+ : await getLatestVersion ( 'react-native' , {
59+ range : version ,
60+ } ) ;
61+
62+ if ( ! matchedReactNativeVersion ) {
63+ throw new Error (
64+ `Could not find a matching version for react-native: ${ version } `
65+ ) ;
66+ }
67+
68+ return matchedReactNativeVersion ;
69+ }
70+
71+ async function fetchCompatibleExpoSDK ( reactNativeVersion : string ) {
72+ const matchedReactNativeVersion =
73+ await fetchReactNativeVersion ( reactNativeVersion ) ;
74+
75+ const res = await fetch ( 'https://api.expo.dev/v2/versions/latest' ) ;
76+
77+ if ( ! res . ok ) {
78+ throw new Error (
79+ `Failed to fetch Expo SDK versions: ${ String ( res . status ) } ${ res . statusText } `
80+ ) ;
81+ }
82+
83+ const result = await res . json ( ) ;
84+
85+ const sdkVersion = Object . entries ( result . data . sdkVersions )
86+ . find ( ( [ , sdkVersionInfo ] ) => {
87+ if (
88+ typeof sdkVersionInfo === 'object' &&
89+ sdkVersionInfo != null &&
90+ 'facebookReactNativeVersion' in sdkVersionInfo &&
91+ typeof sdkVersionInfo . facebookReactNativeVersion === 'string'
92+ ) {
93+ const requested = matchedReactNativeVersion . split ( '.' ) ;
94+ const supported = sdkVersionInfo . facebookReactNativeVersion . split ( '.' ) ;
95+
96+ return (
97+ requested [ 0 ] === supported [ 0 ] &&
98+ requested [ 1 ] === supported [ 1 ] &&
99+ ( requested [ 2 ] ? requested [ 2 ] === supported [ 2 ] : true )
100+ ) ;
101+ }
102+
103+ return false ;
104+ } ) ?. [ 0 ]
105+ // Get major SDK version (e.g. "55" from "55.0.0")
106+ . split ( '.' ) [ 0 ] ;
107+
108+ if ( sdkVersion == null ) {
109+ throw new Error (
110+ `Couldn't find a compatible Expo SDK for react-native@${ reactNativeVersion } `
111+ ) ;
112+ }
113+
114+ return {
115+ sdkVersion,
116+ reactNativeVersion : matchedReactNativeVersion ,
117+ } ;
118+ }
119+
51120export default async function generateExampleApp ( {
52121 config,
53122 root,
54- reactNativeVersion = 'latest' ,
123+ reactNativeVersion,
55124} : {
56125 config : TemplateConfiguration ;
57126 root : string ;
@@ -63,6 +132,17 @@ export default async function generateExampleApp({
63132
64133 switch ( config . example ) {
65134 case 'vanilla' :
135+ if (
136+ reactNativeVersion != null &&
137+ reactNativeVersion !== SUPPORTED_REACT_NATIVE_VERSION
138+ ) {
139+ console . log (
140+ `${ kleur . blue ( 'ℹ' ) } Using untested ${ kleur . cyan (
141+ `react-native@${ reactNativeVersion } `
142+ ) } for the example`
143+ ) ;
144+ }
145+
66146 // `npx @react-native-community/cli init <projectName> --directory example --skip-install`
67147 args = [
68148 `@react-native-community/cli` ,
@@ -73,7 +153,7 @@ export default async function generateExampleApp({
73153 '--directory' ,
74154 directory ,
75155 '--version' ,
76- reactNativeVersion ,
156+ reactNativeVersion || SUPPORTED_REACT_NATIVE_VERSION ,
77157 '--skip-install' ,
78158 '--skip-git-init' ,
79159 '--pm' ,
@@ -82,18 +162,19 @@ export default async function generateExampleApp({
82162 break ;
83163 case 'test-app' :
84164 {
85- // Test App requires React Native version to be a semver version
86- const matchedReactNativeVersion = / ( \d + \. \d + [ - . 0 - 9 a - z ] * ) / . test (
87- reactNativeVersion
88- )
89- ? reactNativeVersion
90- : await getLatestVersion ( 'react-native' , {
91- range : reactNativeVersion ,
92- } ) ;
93-
94- if ( ! matchedReactNativeVersion ) {
95- throw new Error (
96- `Could not find a matching version for react-native: ${ reactNativeVersion } `
165+ // Test App doesn't support a semver range for the version
166+ const matchedReactNativeVersion = reactNativeVersion
167+ ? await fetchReactNativeVersion ( reactNativeVersion )
168+ : SUPPORTED_REACT_NATIVE_VERSION ;
169+
170+ if (
171+ reactNativeVersion != null &&
172+ reactNativeVersion !== SUPPORTED_REACT_NATIVE_VERSION
173+ ) {
174+ console . log (
175+ `${ kleur . blue ( 'ℹ' ) } Using untested ${ kleur . cyan (
176+ `react-native@${ matchedReactNativeVersion } `
177+ ) } for the example`
97178 ) ;
98179 }
99180
@@ -115,16 +196,36 @@ export default async function generateExampleApp({
115196 ] ;
116197 }
117198 break ;
118- case 'expo' :
199+ case 'expo' : {
119200 // `npx create-expo-app example --no-install --template blank`
201+ const { sdkVersion, reactNativeVersion : matchedReactNativeVersion } =
202+ reactNativeVersion
203+ ? await fetchCompatibleExpoSDK ( reactNativeVersion )
204+ : {
205+ sdkVersion : SUPPORTED_EXPO_SDK_VERSION ,
206+ reactNativeVersion : null ,
207+ } ;
208+
209+ if (
210+ sdkVersion !== SUPPORTED_EXPO_SDK_VERSION &&
211+ matchedReactNativeVersion
212+ ) {
213+ console . log (
214+ `${ kleur . blue ( 'ℹ' ) } Using untested ${ kleur . cyan (
215+ `expo@${ sdkVersion } `
216+ ) } with ${ kleur . cyan ( `react-native@${ matchedReactNativeVersion } ` ) } for the example`
217+ ) ;
218+ }
219+
120220 args = [
121221 'create-expo-app@latest' ,
122222 directory ,
123223 '--no-install' ,
124224 '--template' ,
125- ' blank' ,
225+ ` blank@sdk- ${ sdkVersion } ` ,
126226 ] ;
127227 break ;
228+ }
128229 case undefined :
129230 case null : {
130231 // Do nothing
@@ -242,27 +343,26 @@ export default async function generateExampleApp({
242343 let bundledNativeModules : Record < string , string > ;
243344
244345 try {
245- bundledNativeModules = await new Promise ( ( resolve , reject ) => {
246- https
247- . get (
248- `https://raw.githubusercontent.com/expo/expo/sdk-${ sdkVersion } /packages/expo/bundledNativeModules.json` ,
249- ( res ) => {
250- let data = '' ;
251-
252- res . on ( 'data' , ( chunk : string ) => ( data += chunk ) ) ;
253- res . on ( 'end' , ( ) => {
254- try {
255- resolve ( JSON . parse ( data ) ) ;
256- } catch ( e ) {
257- // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
258- reject ( e ) ;
259- }
260- } ) ;
261- }
262- )
263- . on ( 'error' , reject ) ;
264- } ) ;
346+ const res = await fetch (
347+ `https://raw.githubusercontent.com/expo/expo/sdk-${ sdkVersion } /packages/expo/bundledNativeModules.json`
348+ ) ;
349+
350+ if ( ! res . ok ) {
351+ throw new Error (
352+ `Failed to fetch bundled native modules for Expo SDK ${ sdkVersion } : ${ String ( res . status ) } ${ res . statusText } `
353+ ) ;
354+ }
355+
356+ bundledNativeModules = await res . json ( ) ;
265357 } catch ( e ) {
358+ console . warn (
359+ `${ kleur . yellow (
360+ '⚠'
361+ ) } Failed to fetch compatibility data for Expo SDK ${ sdkVersion } : ${ kleur . cyan (
362+ config . example
363+ ) } `
364+ ) ;
365+
266366 bundledNativeModules = { } ;
267367 }
268368
0 commit comments