Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add ability to use react-native-test-app for example/ app #572

Merged
merged 11 commits into from
Jul 3, 2024
Merged
1 change: 1 addition & 0 deletions .github/workflows/build-templates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ jobs:
--repo-url https://test.test \
--type ${{ matrix.type }} \
--languages ${{ matrix.language }} \
--example ${{ matrix.language == 'js' && 'expo' || 'vanilla' }} \
--no-local

- name: Cache dependencies of library
Expand Down
130 changes: 87 additions & 43 deletions packages/create-react-native-library/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import ora from 'ora';
import validateNpmPackage from 'validate-npm-package-name';
import githubUsername from 'github-username';
import prompts, { type PromptObject } from './utils/prompts';
import generateExampleApp from './utils/generateExampleApp';
import generateExampleApp, {
type ExampleType,
} from './utils/generateExampleApp';
import { spawn } from './utils/spawn';
import { version } from '../package.json';

Expand Down Expand Up @@ -117,7 +119,7 @@ type Answers = {
repoUrl: string;
languages: ProjectLanguages;
type?: ProjectType;
example?: boolean;
example?: ExampleType;
reactNativeVersion?: string;
local?: boolean;
};
Expand Down Expand Up @@ -173,6 +175,24 @@ const LANGUAGE_CHOICES: {
},
];

const EXAMPLE_CHOICES = [
{
title: 'Test app',
value: 'test-app',
description: "app's native code is abstracted away",
},
{
title: 'Vanilla',
value: 'vanilla',
description: "provides access to app's native code",
},
{
title: 'Expo',
value: 'expo',
description: 'managed expo project with web support',
},
] as const;

const NEWARCH_DESCRIPTION = 'requires new arch (experimental)';
const BACKCOMPAT_DESCRIPTION = 'supports new arch (experimental)';

Expand Down Expand Up @@ -260,9 +280,9 @@ const args: Record<ArgName, yargs.Options> = {
type: 'boolean',
},
'example': {
description: 'Whether to create an example app',
type: 'boolean',
default: true,
description: 'Type of the example app to create',
type: 'string',
choices: EXAMPLE_CHOICES.map(({ value }) => value),
},
};

Expand Down Expand Up @@ -452,51 +472,76 @@ async function create(_argv: yargs.Arguments<any>) {
});
},
},
];

// Validate arguments passed to the CLI
for (const [key, value] of Object.entries(argv)) {
if (value == null) {
continue;
}
{
type: 'select',
name: 'example',
message: 'What type of example app do you want to create?',
choices: (_, values) => {
return EXAMPLE_CHOICES.filter((choice) => {
if (values.type) {
return values.type === 'library'
? choice.value === 'expo'
: choice.value !== 'expo';
}

const question = questions.find((q) => q.name === key);
return true;
});
},
},
];

if (question == null) {
continue;
}
const validate = (answers: Answers) => {
for (const [key, value] of Object.entries(answers)) {
if (value == null) {
continue;
}

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

// We also need to guard against invalid choices
// If we don't already have a validation message to provide a better error
if (typeof valid !== 'string' && 'choices' in question) {
const choices =
typeof question.choices === 'function'
? question.choices(undefined, argv, question)
: question.choices;
if (question == null) {
continue;
}

if (choices && !choices.some((choice) => choice.value === value)) {
valid = `Supported values are - ${choices.map((c) =>
kleur.green(c.value)
)}`;
let valid = question.validate ? question.validate(String(value)) : true;

// We also need to guard against invalid choices
// If we don't already have a validation message to provide a better error
if (typeof valid !== 'string' && 'choices' in question) {
const choices =
typeof question.choices === 'function'
? question.choices(
undefined,
// @ts-expect-error: it complains about optional values, but it should be fine
answers,
question
)
: question.choices;

if (choices && !choices.some((choice) => choice.value === value)) {
valid = `Supported values are - ${choices.map((c) =>
kleur.green(c.value)
)}`;
}
}
}

if (valid !== true) {
let message = `Invalid value ${kleur.red(
String(value)
)} passed for ${kleur.blue(key)}`;
if (valid !== true) {
let message = `Invalid value ${kleur.red(
String(value)
)} passed for ${kleur.blue(key)}`;

if (typeof valid === 'string') {
message += `: ${valid}`;
}
if (typeof valid === 'string') {
message += `: ${valid}`;
}

console.log(message);
console.log(message);

process.exit(1);
process.exit(1);
}
}
}
};

// Validate arguments passed to the CLI
validate(argv);

const answers = {
...argv,
Expand Down Expand Up @@ -546,6 +591,8 @@ async function create(_argv: yargs.Arguments<any>) {
)),
} as Answers;

validate(answers);

const {
slug,
description,
Expand All @@ -555,7 +602,7 @@ async function create(_argv: yargs.Arguments<any>) {
repoUrl,
type = 'module-mixed',
languages = type === 'library' ? 'js' : 'java-objc',
example: hasExample,
example = local ? 'none' : type === 'library' ? 'expo' : 'test-app',
reactNativeVersion,
} = answers;

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

const example =
hasExample && !local ? (type === 'library' ? 'expo' : 'native') : 'none';

const project = slug.replace(/^(react-native-|@[^/]+\/)/, '');

let namespace: string | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import path from 'path';
import https from 'https';
import { spawn } from './spawn';

export type ExampleType = 'vanilla' | 'test-app' | 'expo' | 'none';

const FILES_TO_DELETE = [
'__tests__',
'.buckconfig',
Expand Down Expand Up @@ -51,39 +53,70 @@ export default async function generateExampleApp({
arch,
reactNativeVersion = 'latest',
}: {
type: 'expo' | 'native';
type: ExampleType;
dest: string;
slug: string;
projectName: string;
arch: 'new' | 'mixed' | 'legacy';
reactNativeVersion?: string;
}) {
const directory = path.join(dest, 'example');
const args =
type === 'native'
? // `npx react-native init <projectName> --directory example --skip-install`
[
`react-native@${reactNativeVersion}`,
'init',
`${projectName}Example`,
'--directory',
directory,
'--version',
reactNativeVersion,
'--skip-install',
'--npm',
]
: // `npx create-expo-app example --no-install --template blank`
[
'create-expo-app@latest',
directory,
'--no-install',
'--template',
'blank',
];

// `npx --package react-native-test-app@latest init --name ${projectName}Example --destination example --version ${reactNativeVersion}`
const testAppArgs = [
'--package',
`react-native-test-app@latest`,
'init',
'--name',
`${projectName}Example`,
`--destination`,
directory,
...(reactNativeVersion !== 'latest'
? ['--version', reactNativeVersion]
: []),
'--platform',
'ios',
'--platform',
'android',
];

// `npx react-native init <projectName> --directory example --skip-install`
const vanillaArgs = [
`react-native@${reactNativeVersion}`,
'init',
`${projectName}Example`,
'--directory',
directory,
'--version',
reactNativeVersion,
'--skip-install',
'--npm',
];

// `npx create-expo-app example --no-install --template blank`
const expoArgs = [
'create-expo-app@latest',
directory,
'--no-install',
'--template',
'blank',
];

let args: string[] = [];

switch (type) {
case 'vanilla':
args = vanillaArgs;
break;
case 'test-app':
args = testAppArgs;
break;
case 'expo':
args = expoArgs;
break;
}

await spawn('npx', args, {
cwd: dest,
env: { ...process.env, npm_config_yes: 'true' },
});

Expand Down Expand Up @@ -113,7 +146,7 @@ export default async function generateExampleApp({
'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"`,
};

if (type === 'native') {
if (type !== 'expo') {
Object.assign(scripts, SCRIPTS_TO_ADD);
}

Expand Down Expand Up @@ -164,7 +197,7 @@ export default async function generateExampleApp({
spaces: 2,
});

if (type === 'native') {
if (type !== 'expo') {
let gradleProperties = await fs.readFile(
path.join(directory, 'android', 'gradle.properties'),
'utf8'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"test": "jest",
"typecheck": "tsc --noEmit",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
<% if (example === 'native') { -%>
<% if (example !== 'expo') { -%>
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
<% } else { -%>
"clean": "del-cli lib",
Expand Down Expand Up @@ -76,7 +76,7 @@
"react-native": "0.73.0",
"react-native-builder-bob": "^<%- bob.version %>",
"release-it": "^15.0.0",
<% if (example === 'native') { -%>
<% if (example !== 'expo') { -%>
"turbo": "^1.10.7",
<% } -%>
"typescript": "^5.2.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ npm install <%- project.slug %>
## Usage

<% if (project.view) { -%>

```js
import { <%- project.name -%>View } from "<%- project.slug -%>";

// ...

<<%- project.name -%>View color="tomato" />
```

<% } else if (project.arch === 'new' && project.module) { -%>

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

const result = multiply(3, 7);
```

<% } else { -%>

```js
import { multiply } from '<%- project.slug -%>';

// ...

const result = await multiply(3, 7);
```

<% } -%>

## Contributing
Expand Down
Loading
Loading