You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
6.1 KiB

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