UNPKG

6.28 kBJavaScriptView Raw
1'use strict';
2
3const assert = require('assert');
4const Events = require('events');
5const utils = require('./lib/utils');
6
7/**
8 * Create an instance of `Enquirer`.
9 *
10 * ```js
11 * const Enquirer = require('enquirer');
12 * const enquirer = new Enquirer();
13 * ```
14 * @name Enquirer
15 * @param {Object} `options` (optional) Options to use with all prompts.
16 * @param {Object} `answers` (optional) Answers object to initialize with.
17 * @api public
18 */
19
20class Enquirer extends Events {
21 constructor(options, answers) {
22 super();
23 this.options = utils.merge({}, options);
24 this.answers = { ...answers };
25 }
26
27 /**
28 * Register a custom prompt type.
29 *
30 * ```js
31 * const Enquirer = require('enquirer');
32 * const enquirer = new Enquirer();
33 * enquirer.register('customType', require('./custom-prompt'));
34 * ```
35 * @name register()
36 * @param {String} `type`
37 * @param {Function|Prompt} `fn` `Prompt` class, or a function that returns a `Prompt` class.
38 * @return {Object} Returns the Enquirer instance
39 * @api public
40 */
41
42 register(type, fn) {
43 if (utils.isObject(type)) {
44 for (let key of Object.keys(type)) this.register(key, type[key]);
45 return this;
46 }
47 assert.equal(typeof fn, 'function', 'expected a function');
48 let name = type.toLowerCase();
49 if (fn.prototype instanceof this.Prompt) {
50 this.prompts[name] = fn;
51 } else {
52 this.prompts[name] = fn(this.Prompt, this);
53 }
54 return this;
55 }
56
57 /**
58 * Prompt function that takes a "question" object or array of question objects,
59 * and returns an object with responses from the user.
60 *
61 * ```js
62 * const Enquirer = require('enquirer');
63 * const enquirer = new Enquirer();
64 *
65 * const response = await enquirer.prompt({
66 * type: 'input',
67 * name: 'username',
68 * message: 'What is your username?'
69 * });
70 * console.log(response);
71 * ```
72 * @name prompt()
73 * @param {Array|Object} `questions` Options objects for one or more prompts to run.
74 * @return {Promise} Promise that returns an "answers" object with the user's responses.
75 * @api public
76 */
77
78 async prompt(questions = []) {
79 for (let question of [].concat(questions)) {
80 try {
81 if (typeof question === 'function') question = await question.call(this);
82 await this.ask(utils.merge({}, this.options, question));
83 } catch (err) {
84 return Promise.reject(err);
85 }
86 }
87 return this.answers;
88 }
89
90 async ask(question) {
91 if (typeof question === 'function') {
92 question = await question.call(this);
93 }
94
95 let opts = utils.merge({}, this.options, question);
96 let { type, name } = question;
97 let { set, get } = utils;
98
99 if (typeof type === 'function') {
100 type = await type.call(this, question, this.answers);
101 }
102
103 if (!type) return this.answers[name];
104
105 assert(this.prompts[type], `Prompt "${type}" is not registered`);
106
107 let prompt = new this.prompts[type](opts);
108 let value = get(this.answers, name);
109
110 prompt.state.answers = this.answers;
111 prompt.enquirer = this;
112
113 if (name) {
114 prompt.on('submit', value => {
115 this.emit('answer', name, value, prompt);
116 set(this.answers, name, value);
117 });
118 }
119
120 // bubble events
121 let emit = prompt.emit.bind(prompt);
122 prompt.emit = (...args) => {
123 this.emit.call(this, ...args);
124 return emit(...args);
125 };
126
127 this.emit('prompt', prompt, this);
128
129 if (opts.autofill && value != null) {
130 prompt.value = prompt.input = value;
131
132 // if "autofill=show" render the prompt, otherwise stay "silent"
133 if (opts.autofill === 'show') {
134 await prompt.submit();
135 }
136 } else {
137 value = prompt.value = await prompt.run();
138 }
139
140 return value;
141 }
142
143 /**
144 * Use an enquirer plugin.
145 *
146 * ```js
147 * const Enquirer = require('enquirer');
148 * const enquirer = new Enquirer();
149 * const plugin = enquirer => {
150 * // do stuff to enquire instance
151 * };
152 * enquirer.use(plugin);
153 * ```
154 * @name use()
155 * @param {Function} `plugin` Plugin function that takes an instance of Enquirer.
156 * @return {Object} Returns the Enquirer instance.
157 * @api public
158 */
159
160 use(plugin) {
161 plugin.call(this, this);
162 return this;
163 }
164
165 set Prompt(value) {
166 this._Prompt = value;
167 }
168 get Prompt() {
169 return this._Prompt || this.constructor.Prompt;
170 }
171
172 get prompts() {
173 return this.constructor.prompts;
174 }
175
176 static set Prompt(value) {
177 this._Prompt = value;
178 }
179 static get Prompt() {
180 return this._Prompt || require('./lib/prompt');
181 }
182
183 static get prompts() {
184 return require('./lib/prompts');
185 }
186
187 static get types() {
188 return require('./lib/types');
189 }
190
191 /**
192 * Prompt function that takes a "question" object or array of question objects,
193 * and returns an object with responses from the user.
194 *
195 * ```js
196 * const { prompt } = require('enquirer');
197 * const response = await prompt({
198 * type: 'input',
199 * name: 'username',
200 * message: 'What is your username?'
201 * });
202 * console.log(response);
203 * ```
204 * @name Enquirer#prompt
205 * @param {Array|Object} `questions` Options objects for one or more prompts to run.
206 * @return {Promise} Promise that returns an "answers" object with the user's responses.
207 * @api public
208 */
209
210 static get prompt() {
211 const fn = (questions, ...rest) => {
212 let enquirer = new this(...rest);
213 let emit = enquirer.emit.bind(enquirer);
214 enquirer.emit = (...args) => {
215 fn.emit(...args);
216 return emit(...args);
217 };
218 return enquirer.prompt(questions);
219 };
220 utils.mixinEmitter(fn, new Events());
221 return fn;
222 }
223}
224
225utils.mixinEmitter(Enquirer, new Events());
226const prompts = Enquirer.prompts;
227
228for (let name of Object.keys(prompts)) {
229 let key = name.toLowerCase();
230
231 let run = options => new prompts[name](options).run();
232 Enquirer.prompt[key] = run;
233 Enquirer[key] = run;
234
235 if (!Enquirer[name]) {
236 Reflect.defineProperty(Enquirer, name, { get: () => prompts[name] });
237 }
238}
239
240const exp = name => {
241 utils.defineExport(Enquirer, name, () => Enquirer.types[name]);
242};
243
244exp('ArrayPrompt');
245exp('AuthPrompt');
246exp('BooleanPrompt');
247exp('NumberPrompt');
248exp('StringPrompt');
249
250module.exports = Enquirer;