diff --git a/packages/create-react-native-library/src/index.ts b/packages/create-react-native-library/src/index.ts index c757a8eed..d621b8818 100644 --- a/packages/create-react-native-library/src/index.ts +++ b/packages/create-react-native-library/src/index.ts @@ -10,6 +10,7 @@ import githubUsername from 'github-username'; import prompts, { type PromptObject } from './utils/prompts'; import generateExampleApp from './utils/generateExampleApp'; import { spawn } from './utils/spawn'; +import { version } from '../package.json'; const FALLBACK_BOB_VERSION = '0.20.0'; @@ -107,6 +108,7 @@ type ProjectType = | 'library'; type Answers = { + name: string; slug: string; description: string; authorName: string; @@ -117,6 +119,7 @@ type Answers = { type?: ProjectType; example?: boolean; reactNativeVersion?: string; + local?: boolean; }; const LANGUAGE_CHOICES: { @@ -265,7 +268,10 @@ const args: Record = { // FIXME: fix the type // eslint-disable-next-line @typescript-eslint/no-explicit-any -async function create(argv: yargs.Arguments) { +async function create(_argv: yargs.Arguments) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { _, $0, ...argv } = _argv; + let local = false; if (typeof argv.local === 'boolean') { @@ -355,13 +361,11 @@ async function create(argv: yargs.Arguments) { const basename = path.basename(folder); - const questions: Record< - ArgName, - Omit, 'validate'> & { - validate?: (value: string) => boolean | string; - } - > = { - 'slug': { + const questions: (Omit, 'validate' | 'name'> & { + validate?: (value: string) => boolean | string; + name: string; + })[] = [ + { type: 'text', name: 'slug', message: 'What is the name of the npm package?', @@ -374,20 +378,20 @@ async function create(argv: yargs.Arguments) { validateNpmPackage(input).validForNewPackages || 'Must be a valid npm package name', }, - 'description': { + { type: 'text', name: 'description', message: 'What is the description for the package?', validate: (input) => Boolean(input) || 'Cannot be empty', }, - 'author-name': { + { type: local ? null : 'text', name: 'authorName', message: 'What is the name of package author?', initial: name, validate: (input) => Boolean(input) || 'Cannot be empty', }, - 'author-email': { + { type: local ? null : 'text', name: 'authorEmail', message: 'What is the email address for the package author?', @@ -395,7 +399,7 @@ async function create(argv: yargs.Arguments) { validate: (input) => /^\S+@\S+$/.test(input) || 'Must be a valid email address', }, - 'author-url': { + { type: local ? null : 'text', name: 'authorUrl', message: 'What is the URL for the package author?', @@ -413,7 +417,7 @@ async function create(argv: yargs.Arguments) { }, validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL', }, - 'repo-url': { + { type: local ? null : 'text', name: 'repoUrl', message: 'What is the URL for the repository?', @@ -428,13 +432,13 @@ async function create(argv: yargs.Arguments) { }, validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL', }, - 'type': { + { type: 'select', name: 'type', message: 'What type of library do you want to develop?', choices: TYPE_CHOICES, }, - 'languages': { + { type: 'select', name: 'languages', message: 'Which languages do you want to use?', @@ -448,7 +452,7 @@ async function create(argv: yargs.Arguments) { }); }, }, - }; + ]; // Validate arguments passed to the CLI for (const [key, value] of Object.entries(argv)) { @@ -456,7 +460,7 @@ async function create(argv: yargs.Arguments) { continue; } - const question = questions[key as ArgName]; + const question = questions.find((q) => q.name === key); if (question == null) { continue; @@ -494,49 +498,37 @@ async function create(argv: yargs.Arguments) { } } - const { - slug, - description, - authorName, - authorEmail, - authorUrl, - repoUrl, - type = 'module-mixed', - languages = type === 'library' ? 'js' : 'java-objc', - example: hasExample, - reactNativeVersion, - } = { + const answers = { ...argv, + local, ...(await prompts( - Object.entries(questions) - .filter(([k, v]) => { - // Skip 'with-recommended-options' question if type of language is passed + questions + .filter((question) => { + // Skip questions which are passed as parameter and pass validation if ( - k === 'with-recommended-options' && - (argv.languages || argv.type) + argv[question.name] != null && + question.validate?.(argv[question.name]) !== false ) { return false; } - // Skip questions which are passed as parameter and pass validation - if (argv[k] != null && v.validate?.(argv[k]) !== false) { - return false; - } - // Skip questions with a single choice - if (Array.isArray(v.choices) && v.choices.length === 1) { + if ( + Array.isArray(question.choices) && + question.choices.length === 1 + ) { return false; } return true; }) - .map(([, v]) => { - const { type, choices } = v; + .map((question) => { + const { type, choices } = question; // Skip dynamic questions with a single choice if (type === 'select' && typeof choices === 'function') { return { - ...v, + ...question, type: (prev, values, prompt) => { const result = choices(prev, { ...argv, ...values }, prompt); @@ -549,16 +541,29 @@ async function create(argv: yargs.Arguments) { }; } - return v; + return question; }) )), } as Answers; + const { + slug, + description, + authorName, + authorEmail, + authorUrl, + repoUrl, + type = 'module-mixed', + languages = type === 'library' ? 'js' : 'java-objc', + example: hasExample, + reactNativeVersion, + } = answers; + // Get latest version of Bob from NPM - let version: string; + let bobVersion: string; try { - version = await Promise.race([ + bobVersion = await Promise.race([ new Promise((resolve) => { setTimeout(() => resolve(FALLBACK_BOB_VERSION), 1000); }), @@ -566,7 +571,7 @@ async function create(argv: yargs.Arguments) { ]); } catch (e) { // Fallback to a known version if we couldn't fetch - version = FALLBACK_BOB_VERSION; + bobVersion = FALLBACK_BOB_VERSION; } const moduleType = type.startsWith('view-') ? 'view' : 'module'; @@ -598,7 +603,7 @@ async function create(argv: yargs.Arguments) { const options = { bob: { - version: version || FALLBACK_BOB_VERSION, + version: bobVersion || FALLBACK_BOB_VERSION, }, project: { slug, @@ -795,6 +800,40 @@ async function create(argv: yargs.Arguments) { } } + // Some of the passed args can already be derived from the generated package.json file. + const ignoredAnswers: (keyof Answers)[] = [ + 'name', + 'slug', + 'description', + 'authorName', + 'authorEmail', + 'authorUrl', + 'repoUrl', + 'example', + 'reactNativeVersion', + 'local', + ]; + + type AnswerEntries = [ + T, + Answers[T], + ][]; + + const libraryMetadata = Object.fromEntries( + (Object.entries(answers) as AnswerEntries).filter( + ([answer]) => !ignoredAnswers.includes(answer) + ) + ); + libraryMetadata.version = version; + + const libraryPackageJson = await fs.readJson( + path.join(folder, 'package.json') + ); + libraryPackageJson['create-react-native-library'] = libraryMetadata; + await fs.writeJson(path.join(folder, 'package.json'), libraryPackageJson, { + spaces: 2, + }); + spinner.succeed( `Project created successfully at ${kleur.yellow( path.relative(process.cwd(), folder) @@ -916,4 +955,8 @@ yargs process.exit(1); }) + .parserConfiguration({ + // don't pass kebab-case args to handler. + 'strip-dashed': true, + }) .strict().argv; diff --git a/tsconfig.json b/tsconfig.json index 520914d78..90faea635 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,6 @@ "react-native-builder-bob": ["./packages/react-native-builder-bob/src"] }, "outDir": "./typescript", - "composite": true, "allowUnreachableCode": false, "allowUnusedLabels": false, "esModuleInterop": true,