base.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. 'use strict';
  2. /**
  3. * Base prompt implementation
  4. * Should be extended by prompt types.
  5. */
  6. var _ = {
  7. assign: require('lodash/assign'),
  8. defaults: require('lodash/defaults'),
  9. clone: require('lodash/clone'),
  10. };
  11. var chalk = require('chalk');
  12. var runAsync = require('run-async');
  13. var { filter, flatMap, share, take, takeUntil } = require('rxjs/operators');
  14. var Choices = require('../objects/choices');
  15. var ScreenManager = require('../utils/screen-manager');
  16. class Prompt {
  17. constructor(question, rl, answers) {
  18. // Setup instance defaults property
  19. _.assign(this, {
  20. answers: answers,
  21. status: 'pending',
  22. });
  23. // Set defaults prompt options
  24. this.opt = _.defaults(_.clone(question), {
  25. validate: () => true,
  26. filter: (val) => val,
  27. when: () => true,
  28. suffix: '',
  29. prefix: chalk.green('?'),
  30. });
  31. // Make sure name is present
  32. if (!this.opt.name) {
  33. this.throwParamError('name');
  34. }
  35. // Set default message if no message defined
  36. if (!this.opt.message) {
  37. this.opt.message = this.opt.name + ':';
  38. }
  39. // Normalize choices
  40. if (Array.isArray(this.opt.choices)) {
  41. this.opt.choices = new Choices(this.opt.choices, answers);
  42. }
  43. this.rl = rl;
  44. this.screen = new ScreenManager(this.rl);
  45. }
  46. /**
  47. * Start the Inquiry session and manage output value filtering
  48. * @return {Promise}
  49. */
  50. run() {
  51. return new Promise((resolve, reject) => {
  52. this._run(
  53. (value) => resolve(value),
  54. (error) => reject(error)
  55. );
  56. });
  57. }
  58. // Default noop (this one should be overwritten in prompts)
  59. _run(cb) {
  60. cb();
  61. }
  62. /**
  63. * Throw an error telling a required parameter is missing
  64. * @param {String} name Name of the missing param
  65. * @return {Throw Error}
  66. */
  67. throwParamError(name) {
  68. throw new Error('You must provide a `' + name + '` parameter');
  69. }
  70. /**
  71. * Called when the UI closes. Override to do any specific cleanup necessary
  72. */
  73. close() {
  74. this.screen.releaseCursor();
  75. }
  76. /**
  77. * Run the provided validation method each time a submit event occur.
  78. * @param {Rx.Observable} submit - submit event flow
  79. * @return {Object} Object containing two observables: `success` and `error`
  80. */
  81. handleSubmitEvents(submit) {
  82. var self = this;
  83. var validate = runAsync(this.opt.validate);
  84. var asyncFilter = runAsync(this.opt.filter);
  85. var validation = submit.pipe(
  86. flatMap((value) =>
  87. asyncFilter(value, self.answers).then(
  88. (filteredValue) =>
  89. validate(filteredValue, self.answers).then(
  90. (isValid) => ({ isValid: isValid, value: filteredValue }),
  91. (err) => ({ isValid: err, value: filteredValue })
  92. ),
  93. (err) => ({ isValid: err })
  94. )
  95. ),
  96. share()
  97. );
  98. var success = validation.pipe(
  99. filter((state) => state.isValid === true),
  100. take(1)
  101. );
  102. var error = validation.pipe(
  103. filter((state) => state.isValid !== true),
  104. takeUntil(success)
  105. );
  106. return {
  107. success: success,
  108. error: error,
  109. };
  110. }
  111. /**
  112. * Generate the prompt question string
  113. * @return {String} prompt question string
  114. */
  115. getQuestion() {
  116. var message =
  117. this.opt.prefix +
  118. ' ' +
  119. chalk.bold(this.opt.message) +
  120. this.opt.suffix +
  121. chalk.reset(' ');
  122. // Append the default if available, and if question isn't answered
  123. if (this.opt.default != null && this.status !== 'answered') {
  124. // If default password is supplied, hide it
  125. if (this.opt.type === 'password') {
  126. message += chalk.italic.dim('[hidden] ');
  127. } else {
  128. message += chalk.dim('(' + this.opt.default + ') ');
  129. }
  130. }
  131. return message;
  132. }
  133. }
  134. module.exports = Prompt;