Skip to content

Commit 9eb8889

Browse files
committed
feat: support react-native-version for expo and update to sdk 55
previously, `--react-native-version` parameter was ignored for expo example. now it'll look-up appropriate expo sdk version for the given react native version. in addition, we now specify an explicit expo sdk version (currently sdk 55).
1 parent affd1b7 commit 9eb8889

File tree

5 files changed

+150
-82
lines changed

5 files changed

+150
-82
lines changed

packages/create-react-native-library/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export const FALLBACK_BOB_VERSION = '0.41.0';
22
export const FALLBACK_NITRO_MODULES_VERSION = '0.35.3';
33
export const SUPPORTED_MONOREPO_CONFIG_VERSION = '0.3.3';
44
export const SUPPORTED_REACT_NATIVE_VERSION = '0.85.0';
5+
export const SUPPORTED_EXPO_SDK_VERSION = '55';

packages/create-react-native-library/src/exampleApp/generateExampleApp.ts

Lines changed: 138 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import dedent from 'dedent';
22
import fs from 'fs-extra';
33
import { getLatestVersion } from 'get-latest-version';
4-
import https from 'https';
4+
import kleur from 'kleur';
55
import 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';
711
import type { TemplateConfiguration } from '../template';
812
import sortObjectKeys from '../utils/sortObjectKeys';
913
import { 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-9a-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+
51120
export 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-9a-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

packages/create-react-native-library/src/index.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,10 @@ import path from 'path';
66
import {
77
FALLBACK_BOB_VERSION,
88
FALLBACK_NITRO_MODULES_VERSION,
9-
SUPPORTED_REACT_NATIVE_VERSION,
109
} from './constants';
1110
import { alignDependencyVersionsWithExampleApp } from './exampleApp/dependencies';
1211
import generateExampleApp from './exampleApp/generateExampleApp';
13-
import {
14-
printLocalLibNextSteps,
15-
printNonLocalLibNextSteps,
16-
printUsedRNVersion,
17-
} from './inform';
12+
import { printLocalLibNextSteps, printNonLocalLibNextSteps } from './inform';
1813
import { prompt } from './prompt';
1914
import { applyTemplates, generateTemplateConfiguration } from './template';
2015
import { assertNpxExists } from './utils/assert';
@@ -72,13 +67,6 @@ async function create() {
7267

7368
await fs.mkdirp(folder);
7469

75-
if (
76-
answers.reactNativeVersion != null &&
77-
answers.reactNativeVersion !== SUPPORTED_REACT_NATIVE_VERSION
78-
) {
79-
printUsedRNVersion(answers.reactNativeVersion, config);
80-
}
81-
8270
const spinner = ora().start();
8371

8472
if (config.example != null) {
@@ -89,6 +77,14 @@ async function create() {
8977
reactNativeVersion: answers.reactNativeVersion,
9078
config,
9179
});
80+
} else {
81+
if (answers.reactNativeVersion) {
82+
console.warn(
83+
`${kleur.yellow(
84+
'⚠'
85+
)} Ignoring --react-native-version for library without example app`
86+
);
87+
}
9288
}
9389

9490
spinner.text = 'Copying files';

packages/create-react-native-library/src/inform.ts

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -102,30 +102,3 @@ export function printErrorHelp(message: string, error: Error) {
102102
throw error;
103103
}
104104
}
105-
106-
export function printUsedRNVersion(
107-
version: string,
108-
config: TemplateConfiguration
109-
) {
110-
if (config.example === 'vanilla' || config.example === 'test-app') {
111-
console.log(
112-
`${kleur.blue('ℹ')} Using untested ${kleur.cyan(
113-
`react-native@${version}`
114-
)} for the example`
115-
);
116-
} else if (config.example != null) {
117-
console.warn(
118-
`${kleur.yellow(
119-
'⚠'
120-
)} Ignoring --react-native-version for unsupported example type: ${kleur.cyan(
121-
config.example
122-
)}`
123-
);
124-
} else {
125-
console.warn(
126-
`${kleur.yellow(
127-
'⚠'
128-
)} Ignoring --react-native-version for library without example app`
129-
);
130-
}
131-
}

packages/create-react-native-library/src/prompt.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import validateNpmPackage from 'validate-npm-package-name';
55
import { spawn } from './utils/spawn';
66
import githubUsername from 'github-username';
77
import { AVAILABLE_TOOLS } from './utils/configureTools';
8-
import { SUPPORTED_REACT_NATIVE_VERSION } from './constants';
98

109
export type Answers = NonNullable<Awaited<ReturnType<typeof prompt.show>>>;
1110

@@ -388,11 +387,10 @@ export const prompt = create(['[name]'], {
388387
description: 'Version of React Native to use in the example app',
389388
message:
390389
'Which version of React Native do you want to use in the example app?',
391-
default: SUPPORTED_REACT_NATIVE_VERSION,
392390
validate: (input) =>
393391
input === 'latest' ||
394-
/^\d+\.\d+\.\d+(-.+)?$/.test(input) ||
395-
'Must be a valid semver version',
392+
/^\d+\.\d+(?:\.\d+)?(?:-.+)?$/.test(input) ||
393+
'Must be a valid version',
396394
skip: true,
397395
},
398396
});

0 commit comments

Comments
 (0)