Skip to content

Commit 2a5d0fd

Browse files
committed
refactor: replace inquirer with prompts
prompts is much smaller than inquirer, so this should help in installing the CLI faster.
1 parent 5f39c67 commit 2a5d0fd

File tree

6 files changed

+145
-84
lines changed

6 files changed

+145
-84
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@
4747
"fs-extra": "^9.0.1",
4848
"github-username": "^5.0.1",
4949
"glob": "^7.1.6",
50-
"inquirer": "^7.0.4",
5150
"is-git-dirty": "^2.0.1",
5251
"json5": "^2.1.3",
52+
"prompts": "^2.4.0",
5353
"validate-npm-package-name": "^3.0.0",
5454
"which": "^2.0.2",
5555
"yargs": "^15.3.1"
@@ -70,8 +70,8 @@
7070
"@types/ejs": "^3.0.4",
7171
"@types/fs-extra": "^9.0.1",
7272
"@types/glob": "^7.1.2",
73-
"@types/inquirer": "^6.5.0",
7473
"@types/json5": "^0.0.30",
74+
"@types/prompts": "^2.0.9",
7575
"@types/validate-npm-package-name": "^3.0.0",
7676
"@types/which": "^1.3.2",
7777
"@types/yargs": "^15.0.5",

src/cli.ts

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import path from 'path';
22
import fs from 'fs-extra';
33
import chalk from 'chalk';
44
import yargs from 'yargs';
5-
import inquirer from 'inquirer';
65
import { cosmiconfigSync } from 'cosmiconfig';
76
import isGitDirty from 'is-git-dirty';
87
import create, { args as CreateArgs } from './create';
8+
import prompts, { PromptObject } from './utils/prompts';
99
import * as logger from './utils/logger';
1010
import buildAAR from './targets/aar';
1111
import buildCommonJS from './targets/commonjs';
@@ -30,11 +30,11 @@ yargs
3030
const pak = path.join(root, 'package.json');
3131

3232
if (isGitDirty()) {
33-
const { shouldContinue } = await inquirer.prompt({
33+
const { shouldContinue } = await prompts({
3434
type: 'confirm',
3535
name: 'shouldContinue',
3636
message: `The working directory is not clean. You should commit or stash your changes before configuring bob. Continue anyway?`,
37-
default: false,
37+
initial: false,
3838
});
3939

4040
if (!shouldContinue) {
@@ -48,11 +48,11 @@ yargs
4848
);
4949
}
5050

51-
const { source } = await inquirer.prompt({
52-
type: 'input',
51+
const { source } = await prompts({
52+
type: 'text',
5353
name: 'source',
5454
message: 'Where are your source files?',
55-
default: 'src',
55+
initial: 'src',
5656
validate: (input) => Boolean(input),
5757
});
5858

@@ -74,21 +74,41 @@ yargs
7474
}
7575

7676
const pkg = JSON.parse(await fs.readFile(pak, 'utf-8'));
77-
const questions: inquirer.Question[] = [
77+
const questions: PromptObject[] = [
7878
{
79-
type: 'input',
79+
type: 'text',
8080
name: 'output',
8181
message: 'Where do you want to generate the output files?',
82-
default: 'lib',
83-
validate: (input) => Boolean(input),
82+
initial: 'lib',
83+
validate: (input: string) => Boolean(input),
8484
},
8585
{
86-
type: 'checkbox',
86+
type: 'multiselect',
8787
name: 'targets',
8888
message: 'Which targets do you want to build?',
89-
// @ts-ignore
90-
choices: ['aar', 'commonjs', 'module', 'typescript'],
91-
validate: (input) => Boolean(input.length),
89+
choices: [
90+
{
91+
title: 'commonjs - for running in Node (tests, SSR etc.)',
92+
value: 'commonjs',
93+
selected: true,
94+
},
95+
{
96+
title: 'module - for bundlers (metro, webpack etc.)',
97+
value: 'module',
98+
selected: true,
99+
},
100+
{
101+
title: 'typescript - declaration files for typechecking',
102+
value: 'typescript',
103+
selected: /\.tsx?$/.test(entryFile),
104+
},
105+
{
106+
title: 'aar - bundle android code to a binary',
107+
value: 'aar',
108+
selected: false,
109+
},
110+
],
111+
validate: (input: string) => Boolean(input.length),
92112
},
93113
];
94114

@@ -102,11 +122,11 @@ yargs
102122
type: 'confirm',
103123
name: 'flow',
104124
message: 'Do you want to publish definitions for flow?',
105-
default: Object.keys(pkg.devDependencies || {}).includes('flow-bin'),
125+
initial: Object.keys(pkg.devDependencies || {}).includes('flow-bin'),
106126
});
107127
}
108128

109-
const { output, targets, flow } = await inquirer.prompt(questions);
129+
const { output, targets, flow } = await prompts(questions);
110130

111131
const target =
112132
targets[0] === 'commonjs' || targets[0] === 'module'
@@ -128,11 +148,11 @@ yargs
128148
entries.types = path.join(output, 'typescript', source, 'index.d.ts');
129149

130150
if (!(await fs.pathExists(path.join(root, 'tsconfig.json')))) {
131-
const { tsconfig } = await inquirer.prompt({
151+
const { tsconfig } = await prompts({
132152
type: 'confirm',
133153
name: 'tsconfig',
134154
message: `You have enabled 'typescript' compilation, but we couldn't find a 'tsconfig.json' in project root. Generate one?`,
135-
default: true,
155+
initial: true,
136156
});
137157

138158
if (tsconfig) {
@@ -182,11 +202,11 @@ yargs
182202
const entry = entries[key];
183203

184204
if (pkg[key] && pkg[key] !== entry) {
185-
const { replace } = await inquirer.prompt({
205+
const { replace } = await prompts({
186206
type: 'confirm',
187207
name: 'replace',
188208
message: `Your package.json has the '${key}' field set to '${pkg[key]}'. Do you want to replace it with '${entry}'?`,
189-
default: true,
209+
initial: true,
190210
});
191211

192212
if (replace) {
@@ -198,11 +218,11 @@ yargs
198218
}
199219

200220
if (pkg.scripts && pkg.scripts.prepare && pkg.scripts.prepare !== prepare) {
201-
const { replace } = await inquirer.prompt({
221+
const { replace } = await prompts({
202222
type: 'confirm',
203223
name: 'replace',
204224
message: `Your package.json has the 'scripts.prepare' field set to '${pkg.scripts.prepare}'. Do you want to replace it with '${prepare}'?`,
205-
default: true,
225+
initial: true,
206226
});
207227

208228
if (replace) {
@@ -218,11 +238,11 @@ yargs
218238
JSON.stringify(pkg.files.slice().sort()) !==
219239
JSON.stringify(files.slice().sort())
220240
) {
221-
const { update } = await inquirer.prompt({
241+
const { update } = await prompts({
222242
type: 'confirm',
223243
name: 'update',
224244
message: `Your package.json already has a 'files' field. Do you want to update it?`,
225-
default: true,
245+
initial: true,
226246
});
227247

228248
if (update) {

src/create.ts

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import fs from 'fs-extra';
33
import ejs from 'ejs';
44
import dedent from 'dedent';
55
import chalk from 'chalk';
6-
import inquirer from 'inquirer';
76
import type yargs from 'yargs';
87
import spawn from 'cross-spawn';
98
import validateNpmPackage from 'validate-npm-package-name';
109
import githubUsername from 'github-username';
10+
import prompts, { PromptObject } from './utils/prompts';
1111
import pack from '../package.json';
1212

1313
const BINARIES = /(gradlew|\.(jar|keystore|png|jpg|gif))$/;
@@ -140,11 +140,17 @@ export default async function create(argv: yargs.Arguments<any>) {
140140

141141
const basename = path.basename(argv.name);
142142

143-
const questions: Record<ArgName, inquirer.Question> = {
143+
const questions: Record<
144+
ArgName,
145+
Omit<PromptObject<keyof Answers>, 'validate'> & {
146+
validate?: (value: string) => boolean | string;
147+
}
148+
> = {
144149
'slug': {
145-
type: 'input',
150+
type: 'text',
151+
name: 'slug',
146152
message: 'What is the name of the npm package?',
147-
default: validateNpmPackage(basename).validForNewPackages
153+
initial: validateNpmPackage(basename).validForNewPackages
148154
? /^(@|react-native)/.test(basename)
149155
? basename
150156
: `react-native-${basename}`
@@ -154,29 +160,34 @@ export default async function create(argv: yargs.Arguments<any>) {
154160
'Must be a valid npm package name',
155161
},
156162
'description': {
157-
type: 'input',
163+
type: 'text',
164+
name: 'description',
158165
message: 'What is the description for the package?',
159-
validate: (input) => Boolean(input),
166+
validate: (input) => Boolean(input) || 'Cannot be empty',
160167
},
161168
'author-name': {
162-
type: 'input',
169+
type: 'text',
170+
name: 'authorName',
163171
message: 'What is the name of package author?',
164-
default: name,
165-
validate: (input) => Boolean(input),
172+
initial: name,
173+
validate: (input) => Boolean(input) || 'Cannot be empty',
166174
},
167175
'author-email': {
168-
type: 'input',
176+
type: 'text',
177+
name: 'authorEmail',
169178
message: 'What is the email address for the package author?',
170-
default: email,
179+
initial: email,
171180
validate: (input) =>
172181
/^\S+@\S+$/.test(input) || 'Must be a valid email address',
173182
},
174183
'author-url': {
175-
type: 'input',
184+
type: 'text',
185+
name: 'authorUrl',
176186
message: 'What is the URL for the package author?',
177-
default: async (answers: any) => {
187+
// @ts-ignore: this is supported, but types are wrong
188+
initial: async (previous: string) => {
178189
try {
179-
const username = await githubUsername(answers.authorEmail);
190+
const username = await githubUsername(previous);
180191

181192
return `https://github.com/${username}`;
182193
} catch (e) {
@@ -188,39 +199,46 @@ export default async function create(argv: yargs.Arguments<any>) {
188199
validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL',
189200
},
190201
'repo-url': {
191-
type: 'input',
202+
type: 'text',
203+
name: 'repoUrl',
192204
message: 'What is the URL for the repository?',
193-
default: (answers: any) => {
205+
// @ts-ignore: this is supported, but types are wrong
206+
initial: (_: string, answers: Answers) => {
194207
if (/^https?:\/\/github.com\/[^/]+/.test(answers.authorUrl)) {
195208
return `${answers.authorUrl}/${answers.slug
196209
.replace(/^@/, '')
197210
.replace(/\//g, '-')}`;
198211
}
199212

200-
return undefined;
213+
return '';
201214
},
202215
validate: (input) => /^https?:\/\//.test(input) || 'Must be a valid URL',
203216
},
204217
'type': {
205-
type: 'list',
218+
type: 'select',
219+
name: 'type',
206220
message: 'What type of package do you want to develop?',
207-
// @ts-ignore - seems types are wrong for inquirer
208221
choices: [
209-
{ name: 'Native module in Kotlin and Objective-C', value: 'native' },
210-
{ name: 'Native module in Kotlin and Swift', value: 'native-swift' },
211-
{ name: 'Native module with C++ code', value: 'cpp' },
212-
{ name: 'Native view in Kotlin and Objective-C', value: 'native-view' },
213-
{ name: 'Native view in Kotlin and Swift', value: 'native-view-swift' },
222+
{ title: 'Native module in Kotlin and Objective-C', value: 'native' },
223+
{ title: 'Native module in Kotlin and Swift', value: 'native-swift' },
224+
{ title: 'Native module with C++ code', value: 'cpp' },
225+
{
226+
title: 'Native view in Kotlin and Objective-C',
227+
value: 'native-view',
228+
},
214229
{
215-
name: 'JavaScript library with native example',
230+
title: 'Native view in Kotlin and Swift',
231+
value: 'native-view-swift',
232+
},
233+
{
234+
title: 'JavaScript library with native example',
216235
value: 'js',
217236
},
218237
{
219-
name: 'JavaScript library with Expo example and Web support',
238+
title: 'JavaScript library with Expo example and Web support',
220239
value: 'expo',
221240
},
222241
],
223-
default: 'native',
224242
},
225243
};
226244

@@ -234,19 +252,15 @@ export default async function create(argv: yargs.Arguments<any>) {
234252
type,
235253
} = {
236254
...argv,
237-
...(await inquirer.prompt(
238-
Object.entries(questions).map(([key, value]) => ({
239-
...value,
240-
name: key.replace(/\b-([a-z])/g, (_, char) => char.toUpperCase()),
241-
when: !(argv[key] && value.validate
242-
? value.validate(argv[key]) === true
243-
: Boolean(argv[key])),
244-
default:
245-
typeof value.default === 'function'
246-
? (answers: Partial<Answers>) =>
247-
value.default({ ...argv, ...answers })
248-
: value.default,
249-
}))
255+
...(await prompts(
256+
Object.entries(questions)
257+
.filter(
258+
([k, v]) =>
259+
!(argv[k] && v.validate
260+
? v.validate(argv[k]) === true
261+
: Boolean(argv[k]))
262+
)
263+
.map(([, v]) => v)
250264
)),
251265
} as Answers;
252266

src/utils/logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const logger = (type: string, color: Function) => (...messages: unknown[]) => {
77
export const info = logger('ℹ', chalk.blue);
88
export const warn = logger('⚠', chalk.yellow);
99
export const error = logger('✖', chalk.red);
10-
export const success = logger('', chalk.green);
10+
export const success = logger('', chalk.green);
1111

1212
export const exit = (...messages: unknown[]) => {
1313
error(...messages);

src/utils/prompts.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import promptsModule from 'prompts';
2+
3+
export default function prompts(
4+
args: promptsModule.PromptObject | promptsModule.PromptObject[],
5+
options?: promptsModule.Options
6+
) {
7+
return promptsModule(args, {
8+
onCancel() {
9+
process.exit(1);
10+
},
11+
...options,
12+
});
13+
}
14+
15+
export type PromptObject<
16+
T extends string = string
17+
> = promptsModule.PromptObject<T>;

0 commit comments

Comments
 (0)