Skip to content

Commit a71fc0c

Browse files
feat!: add ability to use react-native-test-app for example/ app (#572)
<!-- Please provide enough information so that others can review your pull request. --> <!-- Keep pull requests small and focused on a single change. --> ### Summary Use [`react-native-test-app`](https://github.com/microsoft/react-native-test-app/) for `exmaple/` app. RNTA was created to help library maintainers focus on building library by providing seamless React Native versions switching, first-class support for OOT platforms and a lot more! ### Test plan Generated app should work in the same way as before (created library should be automatically linked). ![CleanShot 2024-06-27 at 16 51 30@2x](https://github.com/callstack/react-native-builder-bob/assets/63900941/89c01dc4-aa45-4f03-989f-15466cb84b96) --------- Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
1 parent cb8f969 commit a71fc0c

File tree

8 files changed

+195
-72
lines changed

8 files changed

+195
-72
lines changed

.github/workflows/build-templates.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ jobs:
116116
--repo-url https://test.test \
117117
--type ${{ matrix.type }} \
118118
--languages ${{ matrix.language }} \
119+
--example ${{ matrix.language == 'js' && 'expo' || 'vanilla' }} \
119120
--no-local
120121
121122
- name: Cache dependencies of library

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

Lines changed: 87 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import ora from 'ora';
88
import validateNpmPackage from 'validate-npm-package-name';
99
import githubUsername from 'github-username';
1010
import prompts, { type PromptObject } from './utils/prompts';
11-
import generateExampleApp from './utils/generateExampleApp';
11+
import generateExampleApp, {
12+
type ExampleType,
13+
} from './utils/generateExampleApp';
1214
import { spawn } from './utils/spawn';
1315
import { version } from '../package.json';
1416

@@ -117,7 +119,7 @@ type Answers = {
117119
repoUrl: string;
118120
languages: ProjectLanguages;
119121
type?: ProjectType;
120-
example?: boolean;
122+
example?: ExampleType;
121123
reactNativeVersion?: string;
122124
local?: boolean;
123125
};
@@ -173,6 +175,24 @@ const LANGUAGE_CHOICES: {
173175
},
174176
];
175177

178+
const EXAMPLE_CHOICES = [
179+
{
180+
title: 'Test app',
181+
value: 'test-app',
182+
description: "app's native code is abstracted away",
183+
},
184+
{
185+
title: 'Vanilla',
186+
value: 'vanilla',
187+
description: "provides access to app's native code",
188+
},
189+
{
190+
title: 'Expo',
191+
value: 'expo',
192+
description: 'managed expo project with web support',
193+
},
194+
] as const;
195+
176196
const NEWARCH_DESCRIPTION = 'requires new arch (experimental)';
177197
const BACKCOMPAT_DESCRIPTION = 'supports new arch (experimental)';
178198

@@ -260,9 +280,9 @@ const args: Record<ArgName, yargs.Options> = {
260280
type: 'boolean',
261281
},
262282
'example': {
263-
description: 'Whether to create an example app',
264-
type: 'boolean',
265-
default: true,
283+
description: 'Type of the example app to create',
284+
type: 'string',
285+
choices: EXAMPLE_CHOICES.map(({ value }) => value),
266286
},
267287
};
268288

@@ -452,51 +472,76 @@ async function create(_argv: yargs.Arguments<any>) {
452472
});
453473
},
454474
},
455-
];
456-
457-
// Validate arguments passed to the CLI
458-
for (const [key, value] of Object.entries(argv)) {
459-
if (value == null) {
460-
continue;
461-
}
475+
{
476+
type: 'select',
477+
name: 'example',
478+
message: 'What type of example app do you want to create?',
479+
choices: (_, values) => {
480+
return EXAMPLE_CHOICES.filter((choice) => {
481+
if (values.type) {
482+
return values.type === 'library'
483+
? choice.value === 'expo'
484+
: choice.value !== 'expo';
485+
}
462486

463-
const question = questions.find((q) => q.name === key);
487+
return true;
488+
});
489+
},
490+
},
491+
];
464492

465-
if (question == null) {
466-
continue;
467-
}
493+
const validate = (answers: Answers) => {
494+
for (const [key, value] of Object.entries(answers)) {
495+
if (value == null) {
496+
continue;
497+
}
468498

469-
let valid = question.validate ? question.validate(String(value)) : true;
499+
const question = questions.find((q) => q.name === key);
470500

471-
// We also need to guard against invalid choices
472-
// If we don't already have a validation message to provide a better error
473-
if (typeof valid !== 'string' && 'choices' in question) {
474-
const choices =
475-
typeof question.choices === 'function'
476-
? question.choices(undefined, argv, question)
477-
: question.choices;
501+
if (question == null) {
502+
continue;
503+
}
478504

479-
if (choices && !choices.some((choice) => choice.value === value)) {
480-
valid = `Supported values are - ${choices.map((c) =>
481-
kleur.green(c.value)
482-
)}`;
505+
let valid = question.validate ? question.validate(String(value)) : true;
506+
507+
// We also need to guard against invalid choices
508+
// If we don't already have a validation message to provide a better error
509+
if (typeof valid !== 'string' && 'choices' in question) {
510+
const choices =
511+
typeof question.choices === 'function'
512+
? question.choices(
513+
undefined,
514+
// @ts-expect-error: it complains about optional values, but it should be fine
515+
answers,
516+
question
517+
)
518+
: question.choices;
519+
520+
if (choices && !choices.some((choice) => choice.value === value)) {
521+
valid = `Supported values are - ${choices.map((c) =>
522+
kleur.green(c.value)
523+
)}`;
524+
}
483525
}
484-
}
485526

486-
if (valid !== true) {
487-
let message = `Invalid value ${kleur.red(
488-
String(value)
489-
)} passed for ${kleur.blue(key)}`;
527+
if (valid !== true) {
528+
let message = `Invalid value ${kleur.red(
529+
String(value)
530+
)} passed for ${kleur.blue(key)}`;
490531

491-
if (typeof valid === 'string') {
492-
message += `: ${valid}`;
493-
}
532+
if (typeof valid === 'string') {
533+
message += `: ${valid}`;
534+
}
494535

495-
console.log(message);
536+
console.log(message);
496537

497-
process.exit(1);
538+
process.exit(1);
539+
}
498540
}
499-
}
541+
};
542+
543+
// Validate arguments passed to the CLI
544+
validate(argv);
500545

501546
const answers = {
502547
...argv,
@@ -546,6 +591,8 @@ async function create(_argv: yargs.Arguments<any>) {
546591
)),
547592
} as Answers;
548593

594+
validate(answers);
595+
549596
const {
550597
slug,
551598
description,
@@ -555,7 +602,7 @@ async function create(_argv: yargs.Arguments<any>) {
555602
repoUrl,
556603
type = 'module-mixed',
557604
languages = type === 'library' ? 'js' : 'java-objc',
558-
example: hasExample,
605+
example = local ? 'none' : type === 'library' ? 'expo' : 'test-app',
559606
reactNativeVersion,
560607
} = answers;
561608

@@ -582,9 +629,6 @@ async function create(_argv: yargs.Arguments<any>) {
582629
? 'mixed'
583630
: 'legacy';
584631

585-
const example =
586-
hasExample && !local ? (type === 'library' ? 'expo' : 'native') : 'none';
587-
588632
const project = slug.replace(/^(react-native-|@[^/]+\/)/, '');
589633

590634
let namespace: string | undefined;

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

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import path from 'path';
33
import https from 'https';
44
import { spawn } from './spawn';
55

6+
export type ExampleType = 'vanilla' | 'test-app' | 'expo' | 'none';
7+
68
const FILES_TO_DELETE = [
79
'__tests__',
810
'.buckconfig',
@@ -51,39 +53,70 @@ export default async function generateExampleApp({
5153
arch,
5254
reactNativeVersion = 'latest',
5355
}: {
54-
type: 'expo' | 'native';
56+
type: ExampleType;
5557
dest: string;
5658
slug: string;
5759
projectName: string;
5860
arch: 'new' | 'mixed' | 'legacy';
5961
reactNativeVersion?: string;
6062
}) {
6163
const directory = path.join(dest, 'example');
62-
const args =
63-
type === 'native'
64-
? // `npx react-native init <projectName> --directory example --skip-install`
65-
[
66-
`react-native@${reactNativeVersion}`,
67-
'init',
68-
`${projectName}Example`,
69-
'--directory',
70-
directory,
71-
'--version',
72-
reactNativeVersion,
73-
'--skip-install',
74-
'--npm',
75-
]
76-
: // `npx create-expo-app example --no-install --template blank`
77-
[
78-
'create-expo-app@latest',
79-
directory,
80-
'--no-install',
81-
'--template',
82-
'blank',
83-
];
64+
65+
// `npx --package react-native-test-app@latest init --name ${projectName}Example --destination example --version ${reactNativeVersion}`
66+
const testAppArgs = [
67+
'--package',
68+
`react-native-test-app@latest`,
69+
'init',
70+
'--name',
71+
`${projectName}Example`,
72+
`--destination`,
73+
directory,
74+
...(reactNativeVersion !== 'latest'
75+
? ['--version', reactNativeVersion]
76+
: []),
77+
'--platform',
78+
'ios',
79+
'--platform',
80+
'android',
81+
];
82+
83+
// `npx react-native init <projectName> --directory example --skip-install`
84+
const vanillaArgs = [
85+
`react-native@${reactNativeVersion}`,
86+
'init',
87+
`${projectName}Example`,
88+
'--directory',
89+
directory,
90+
'--version',
91+
reactNativeVersion,
92+
'--skip-install',
93+
'--npm',
94+
];
95+
96+
// `npx create-expo-app example --no-install --template blank`
97+
const expoArgs = [
98+
'create-expo-app@latest',
99+
directory,
100+
'--no-install',
101+
'--template',
102+
'blank',
103+
];
104+
105+
let args: string[] = [];
106+
107+
switch (type) {
108+
case 'vanilla':
109+
args = vanillaArgs;
110+
break;
111+
case 'test-app':
112+
args = testAppArgs;
113+
break;
114+
case 'expo':
115+
args = expoArgs;
116+
break;
117+
}
84118

85119
await spawn('npx', args, {
86-
cwd: dest,
87120
env: { ...process.env, npm_config_yes: 'true' },
88121
});
89122

@@ -113,7 +146,7 @@ export default async function generateExampleApp({
113146
'build:ios': `react-native build-ios --scheme ${projectName}Example --mode Debug --extra-params "-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO"`,
114147
};
115148

116-
if (type === 'native') {
149+
if (type !== 'expo') {
117150
Object.assign(scripts, SCRIPTS_TO_ADD);
118151
}
119152

@@ -164,7 +197,7 @@ export default async function generateExampleApp({
164197
spaces: 2,
165198
});
166199

167-
if (type === 'native') {
200+
if (type !== 'expo') {
168201
let gradleProperties = await fs.readFile(
169202
path.join(directory, 'android', 'gradle.properties'),
170203
'utf8'

packages/create-react-native-library/templates/common/$package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"test": "jest",
3333
"typecheck": "tsc --noEmit",
3434
"lint": "eslint \"**/*.{js,ts,tsx}\"",
35-
<% if (example === 'native') { -%>
35+
<% if (example !== 'expo') { -%>
3636
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
3737
<% } else { -%>
3838
"clean": "del-cli lib",
@@ -76,7 +76,7 @@
7676
"react-native": "0.73.0",
7777
"react-native-builder-bob": "^<%- bob.version %>",
7878
"release-it": "^15.0.0",
79-
<% if (example === 'native') { -%>
79+
<% if (example !== 'expo') { -%>
8080
"turbo": "^1.10.7",
8181
<% } -%>
8282
"typescript": "^5.2.2"

packages/create-react-native-library/templates/common/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ npm install <%- project.slug %>
1111
## Usage
1212

1313
<% if (project.view) { -%>
14+
1415
```js
1516
import { <%- project.name -%>View } from "<%- project.slug -%>";
1617

1718
// ...
1819

1920
<<%- project.name -%>View color="tomato" />
2021
```
22+
2123
<% } else if (project.arch === 'new' && project.module) { -%>
2224

2325
```js
@@ -27,14 +29,17 @@ import { multiply } from '<%- project.slug -%>';
2729

2830
const result = multiply(3, 7);
2931
```
32+
3033
<% } else { -%>
34+
3135
```js
3236
import { multiply } from '<%- project.slug -%>';
3337

3438
// ...
3539

3640
const result = await multiply(3, 7);
3741
```
42+
3843
<% } -%>
3944

4045
## Contributing

0 commit comments

Comments
 (0)