Skip to content

Commit b8f0252

Browse files
committed
refactor: move options into config
1 parent 6717bd9 commit b8f0252

File tree

2 files changed

+152
-105
lines changed

2 files changed

+152
-105
lines changed

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

Lines changed: 47 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { addCodegenBuildScript } from './exampleApp/addCodegenBuildScript';
1818
import { createInitialGitCommit } from './utils/initialCommit';
1919
import { assertNpx } from './utils/assert';
2020
import { resolveBobVersionWithFallback } from './utils/promiseWithFallback';
21+
import { generateTemplateConfiguration } from './template/config';
2122

2223
const FALLBACK_BOB_VERSION = '0.32.0';
2324

@@ -98,7 +99,7 @@ type ProjectType =
9899
| 'view-legacy'
99100
| 'library';
100101

101-
type Answers = {
102+
export type Answers = {
102103
name: string;
103104
slug: string;
104105
description: string;
@@ -510,83 +511,17 @@ async function create(_argv: yargs.Arguments<any>) {
510511
local,
511512
...singleChoiceAnswers,
512513
...promptAnswers,
513-
} as Answers;
514+
} as Required<Answers>;
514515

515516
assertOptions(questions, answers);
516517

517-
const {
518-
slug,
519-
description,
520-
authorName,
521-
authorEmail,
522-
authorUrl,
523-
repoUrl,
524-
type = 'module-mixed',
525-
languages = type === 'library' ? 'js' : 'kotlin-objc',
526-
example = local ? 'none' : type === 'library' ? 'expo' : 'vanilla',
527-
reactNativeVersion,
528-
} = answers;
529-
530-
const moduleType = type.startsWith('view-') ? 'view' : 'module';
531-
const arch =
532-
type === 'module-new' || type === 'view-new'
533-
? 'new'
534-
: type === 'module-mixed' || type === 'view-mixed'
535-
? 'mixed'
536-
: 'legacy';
537-
538-
const project = slug.replace(/^(react-native-|@[^/]+\/)/, '');
539-
540-
let namespace: string | undefined;
541-
542-
if (slug.startsWith('@') && slug.includes('/')) {
543-
namespace = slug
544-
.split('/')[0]
545-
?.replace(/[^a-z0-9]/g, '')
546-
.toLowerCase();
547-
}
548-
549-
// Create a package identifier with specified namespace when possible
550-
const pack = `${namespace ? `${namespace}.` : ''}${project
551-
.replace(/[^a-z0-9]/g, '')
552-
.toLowerCase()}`;
553-
554518
const bobVersion = await resolveBobVersion();
555-
const options = {
556-
bob: {
557-
version: bobVersion,
558-
},
559-
project: {
560-
slug,
561-
description,
562-
name:
563-
/^[A-Z]/.test(basename) && /^[a-z0-9]+$/i.test(basename)
564-
? // If the project name is already in PascalCase, use it as-is
565-
basename
566-
: // Otherwise, convert it to PascalCase and remove any non-alphanumeric characters
567-
`${project.charAt(0).toUpperCase()}${project
568-
.replace(/[^a-z0-9](\w)/g, (_, $1) => $1.toUpperCase())
569-
.slice(1)}`,
570-
package: pack,
571-
package_dir: pack.replace(/\./g, '/'),
572-
package_cpp: pack.replace(/\./g, '_'),
573-
identifier: slug.replace(/[^a-z0-9]+/g, '-').replace(/^-/, ''),
574-
native: languages !== 'js',
575-
arch,
576-
cpp: languages === 'cpp',
577-
swift: languages === 'kotlin-swift',
578-
view: moduleType === 'view',
579-
module: moduleType === 'module',
580-
},
581-
author: {
582-
name: authorName,
583-
email: authorEmail,
584-
url: authorUrl,
585-
},
586-
repo: repoUrl,
587-
example,
588-
year: new Date().getFullYear(),
589-
};
519+
520+
const config = generateTemplateConfiguration({
521+
bobVersion,
522+
basename,
523+
answers,
524+
});
590525

591526
async function applyTemplate(source: string, destination: string) {
592527
await fs.mkdirp(destination);
@@ -596,7 +531,7 @@ async function create(_argv: yargs.Arguments<any>) {
596531
for (const f of files) {
597532
const target = path.join(
598533
destination,
599-
ejs.render(f.replace(/^\$/, ''), options, {
534+
ejs.render(f.replace(/^\$/, ''), config, {
600535
openDelimiter: '{',
601536
closeDelimiter: '}',
602537
})
@@ -610,7 +545,7 @@ async function create(_argv: yargs.Arguments<any>) {
610545
} else if (!BINARIES.some((r) => r.test(file))) {
611546
const content = await fs.readFile(file, 'utf8');
612547

613-
await fs.writeFile(target, ejs.render(content, options));
548+
await fs.writeFile(target, ejs.render(content, config));
614549
} else {
615550
await fs.copyFile(file, target);
616551
}
@@ -619,36 +554,36 @@ async function create(_argv: yargs.Arguments<any>) {
619554

620555
await fs.mkdirp(folder);
621556

622-
if (reactNativeVersion != null) {
623-
if (example === 'vanilla') {
557+
if (answers.reactNativeVersion != null) {
558+
if (config.example === 'vanilla') {
624559
console.log(
625560
`${kleur.blue('ℹ')} Using ${kleur.cyan(
626-
`react-native@${reactNativeVersion}`
561+
`react-native@${answers.reactNativeVersion}`
627562
)} for the example`
628563
);
629564
} else {
630565
console.warn(
631566
`${kleur.yellow(
632567
'⚠'
633568
)} Ignoring --react-native-version for unsupported example type: ${kleur.cyan(
634-
example
569+
config.example
635570
)}`
636571
);
637572
}
638573
}
639574

640575
const spinner = ora().start();
641576

642-
if (example !== 'none') {
577+
if (config.example !== 'none') {
643578
spinner.text = 'Generating example app';
644579

645580
await generateExampleApp({
646-
type: example,
581+
type: config.example,
647582
dest: folder,
648-
arch,
649-
project: options.project,
583+
arch: config.project.arch,
584+
project: config.project,
650585
bobVersion,
651-
reactNativeVersion,
586+
reactNativeVersion: answers.reactNativeVersion,
652587
});
653588
}
654589

@@ -659,50 +594,55 @@ async function create(_argv: yargs.Arguments<any>) {
659594
} else {
660595
await applyTemplate(COMMON_FILES, folder);
661596

662-
if (example !== 'none') {
597+
if (config.example !== 'none') {
663598
await applyTemplate(COMMON_EXAMPLE_FILES, folder);
664599
}
665600
}
666601

667-
if (languages === 'js') {
602+
if (answers.languages === 'js') {
668603
await applyTemplate(JS_FILES, folder);
669604
await applyTemplate(EXPO_FILES, folder);
670605
} else {
671606
await applyTemplate(NATIVE_COMMON_FILES, folder);
672607

673-
if (example !== 'none') {
608+
if (config.example !== 'none') {
674609
await applyTemplate(NATIVE_COMMON_EXAMPLE_FILES, folder);
675610
}
676611

677-
if (moduleType === 'module') {
678-
await applyTemplate(NATIVE_FILES[`${moduleType}_${arch}`], folder);
612+
if (config.project.module) {
613+
await applyTemplate(
614+
NATIVE_FILES[`module_${config.project.arch}`],
615+
folder
616+
);
679617
} else {
680-
await applyTemplate(NATIVE_FILES[`${moduleType}_${arch}`], folder);
618+
await applyTemplate(NATIVE_FILES[`view_${config.project.arch}`], folder);
681619
}
682620

683-
if (options.project.swift) {
684-
await applyTemplate(SWIFT_FILES[`${moduleType}_legacy`], folder);
621+
if (config.project.swift) {
622+
await applyTemplate(SWIFT_FILES[`module_legacy`], folder);
685623
} else {
686-
if (moduleType === 'module') {
687-
await applyTemplate(OBJC_FILES[`${moduleType}_common`], folder);
624+
if (config.project.module) {
625+
await applyTemplate(OBJC_FILES[`module_common`], folder);
688626
} else {
689-
await applyTemplate(OBJC_FILES[`view_${arch}`], folder);
627+
await applyTemplate(OBJC_FILES[`view_${config.project.arch}`], folder);
690628
}
691629
}
692630

693-
const templateType = `${moduleType}_${arch}` as const;
631+
const templateType = `${config.project.module ? 'module' : 'view'}_${
632+
config.project.arch
633+
}` as const;
694634

695635
await applyTemplate(KOTLIN_FILES[templateType], folder);
696636

697-
if (options.project.cpp) {
637+
if (config.project.cpp) {
698638
await applyTemplate(CPP_FILES, folder);
699-
await fs.remove(path.join(folder, 'ios', `${options.project.name}.m`));
639+
await fs.remove(path.join(folder, 'ios', `${config.project.name}.m`));
700640
}
701641
}
702642

703643
const rootPackageJson = await fs.readJson(path.join(folder, 'package.json'));
704644

705-
if (example !== 'none') {
645+
if (config.example !== 'none') {
706646
// Set `react` and `react-native` versions of root `package.json` from example `package.json`
707647
const examplePackageJson = await fs.readJSON(
708648
path.join(folder, 'example', 'package.json')
@@ -719,7 +659,7 @@ async function create(_argv: yargs.Arguments<any>) {
719659
examplePackageJson.dependencies['react-native'];
720660
}
721661

722-
if (example === 'vanilla') {
662+
if (config.example === 'vanilla') {
723663
// React Native doesn't provide the community CLI as a dependency.
724664
// We have to get read the version from the example app and put to the root package json
725665
const exampleCommunityCLIVersion =
@@ -733,7 +673,7 @@ async function create(_argv: yargs.Arguments<any>) {
733673
rootPackageJson.devDependencies['@react-native-community/cli'] =
734674
exampleCommunityCLIVersion;
735675

736-
if (arch !== 'legacy') {
676+
if (config.project.arch !== 'legacy') {
737677
addCodegenBuildScript(folder);
738678
}
739679
}
@@ -800,7 +740,7 @@ async function create(_argv: yargs.Arguments<any>) {
800740

801741
if (isReactNativeProject) {
802742
packageJson.dependencies = packageJson.dependencies || {};
803-
packageJson.dependencies[slug] =
743+
packageJson.dependencies[config.project.slug] =
804744
packageManager === 'yarn'
805745
? `link:./${path.relative(process.cwd(), folder)}`
806746
: `file:./${path.relative(process.cwd(), folder)}`;
@@ -833,7 +773,9 @@ async function create(_argv: yargs.Arguments<any>) {
833773
`- Run ${kleur.blue('npx react-native run-android')} or ${kleur.blue(
834774
'npx react-native run-ios'
835775
)} to build and run the app\n` +
836-
`- Import from ${kleur.blue(slug)} and use it in your app.`
776+
`- Import from ${kleur.blue(
777+
config.project.slug
778+
)} and use it in your app.`
837779
}
838780
839781
${kleur.yellow(`Good luck!`)}
@@ -843,7 +785,7 @@ async function create(_argv: yargs.Arguments<any>) {
843785
const platforms = {
844786
ios: { name: 'iOS', color: 'cyan' },
845787
android: { name: 'Android', color: 'green' },
846-
...(example === 'expo'
788+
...(config.example === 'expo'
847789
? ({ web: { name: 'Web', color: 'blue' } } as const)
848790
: null),
849791
} as const;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type { Answers } from 'create-react-native-library';
2+
3+
// Please think at least 5 times before introducing a new config key
4+
// You can just reuse the existing ones most of the time
5+
export type TemplateConfiguration = {
6+
bob: {
7+
version: string;
8+
};
9+
project: {
10+
slug: string;
11+
description: string;
12+
name: string;
13+
package: string;
14+
package_dir: string;
15+
package_cpp: string;
16+
identifier: string;
17+
native: boolean;
18+
arch: SupportedArchitecture;
19+
cpp: boolean;
20+
swift: boolean;
21+
view: boolean;
22+
module: boolean;
23+
};
24+
author: {
25+
name: string;
26+
email: string;
27+
url: string;
28+
};
29+
repo: string;
30+
example: ExampleApp;
31+
year: number;
32+
};
33+
34+
export type SupportedArchitecture = 'new' | 'mixed' | 'legacy';
35+
export type ExampleApp = 'none' | 'test-app' | 'expo' | 'vanilla';
36+
37+
export function generateTemplateConfiguration({
38+
bobVersion,
39+
basename,
40+
answers,
41+
}: {
42+
bobVersion: string;
43+
basename: string;
44+
answers: Required<Answers>;
45+
}): TemplateConfiguration {
46+
const { slug, languages, type } = answers;
47+
48+
const arch =
49+
type === 'module-new' || type === 'view-new'
50+
? 'new'
51+
: type === 'module-mixed' || type === 'view-mixed'
52+
? 'mixed'
53+
: 'legacy';
54+
55+
const project = slug.replace(/^(react-native-|@[^/]+\/)/, '');
56+
let namespace: string | undefined;
57+
58+
if (slug.startsWith('@') && slug.includes('/')) {
59+
namespace = slug
60+
.split('/')[0]
61+
?.replace(/[^a-z0-9]/g, '')
62+
.toLowerCase();
63+
}
64+
65+
// Create a package identifier with specified namespace when possible
66+
const pack = `${namespace ? `${namespace}.` : ''}${project
67+
.replace(/[^a-z0-9]/g, '')
68+
.toLowerCase()}`;
69+
70+
return {
71+
bob: {
72+
version: bobVersion,
73+
},
74+
project: {
75+
slug,
76+
description: answers.description,
77+
name:
78+
/^[A-Z]/.test(basename) && /^[a-z0-9]+$/i.test(basename)
79+
? // If the project name is already in PascalCase, use it as-is
80+
basename
81+
: // Otherwise, convert it to PascalCase and remove any non-alphanumeric characters
82+
`${project.charAt(0).toUpperCase()}${project
83+
.replace(/[^a-z0-9](\w)/g, (_, $1) => $1.toUpperCase())
84+
.slice(1)}`,
85+
package: pack,
86+
package_dir: pack.replace(/\./g, '/'),
87+
package_cpp: pack.replace(/\./g, '_'),
88+
identifier: slug.replace(/[^a-z0-9]+/g, '-').replace(/^-/, ''),
89+
native: languages !== 'js',
90+
arch,
91+
cpp: languages === 'cpp',
92+
swift: languages === 'kotlin-swift',
93+
view: answers.type.startsWith('view'),
94+
module: answers.type.startsWith('module'),
95+
},
96+
author: {
97+
name: answers.authorName,
98+
email: answers.authorEmail,
99+
url: answers.authorUrl,
100+
},
101+
repo: answers.repoUrl,
102+
example: answers.example,
103+
year: new Date().getFullYear(),
104+
};
105+
}

0 commit comments

Comments
 (0)