tis-cli前端项目快速搭建命令行工具
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.

150 lines
3.8 KiB

4 years ago
  1. 'use strict';
  2. const _ = {
  3. isPlainObject: require('lodash/isPlainObject'),
  4. clone: require('lodash/clone'),
  5. isArray: require('lodash/isArray'),
  6. get: require('lodash/get'),
  7. set: require('lodash/set'),
  8. isFunction: require('lodash/isFunction'),
  9. };
  10. const { defer, empty, from, of } = require('rxjs');
  11. const { concatMap, filter, publish, reduce } = require('rxjs/operators');
  12. const runAsync = require('run-async');
  13. const utils = require('../utils/utils');
  14. const Base = require('./baseUI');
  15. /**
  16. * Base interface class other can inherits from
  17. */
  18. class PromptUI extends Base {
  19. constructor(prompts, opt) {
  20. super(opt);
  21. this.prompts = prompts;
  22. }
  23. run(questions, answers) {
  24. // Keep global reference to the answers
  25. if (_.isPlainObject(answers)) {
  26. this.answers = _.clone(answers);
  27. } else {
  28. this.answers = {};
  29. }
  30. // Make sure questions is an array.
  31. if (_.isPlainObject(questions)) {
  32. // It's either an object of questions or a single question
  33. questions = Object.values(questions).every(
  34. (v) => _.isPlainObject(v) && v.name === undefined
  35. )
  36. ? Object.entries(questions).map(([name, question]) => ({ name, ...question }))
  37. : [questions];
  38. }
  39. // Create an observable, unless we received one as parameter.
  40. // Note: As this is a public interface, we cannot do an instanceof check as we won't
  41. // be using the exact same object in memory.
  42. const obs = _.isArray(questions) ? from(questions) : questions;
  43. this.process = obs.pipe(
  44. concatMap(this.processQuestion.bind(this)),
  45. publish() // Creates a hot Observable. It prevents duplicating prompts.
  46. );
  47. this.process.connect();
  48. return this.process
  49. .pipe(
  50. reduce((answers, answer) => {
  51. _.set(answers, answer.name, answer.answer);
  52. return answers;
  53. }, this.answers)
  54. )
  55. .toPromise(Promise)
  56. .then(this.onCompletion.bind(this), this.onError.bind(this));
  57. }
  58. /**
  59. * Once all prompt are over
  60. */
  61. onCompletion() {
  62. this.close();
  63. return this.answers;
  64. }
  65. onError(error) {
  66. this.close();
  67. return Promise.reject(error);
  68. }
  69. processQuestion(question) {
  70. question = _.clone(question);
  71. return defer(() => {
  72. const obs = of(question);
  73. return obs.pipe(
  74. concatMap(this.setDefaultType.bind(this)),
  75. concatMap(this.filterIfRunnable.bind(this)),
  76. concatMap(() =>
  77. utils.fetchAsyncQuestionProperty(question, 'message', this.answers)
  78. ),
  79. concatMap(() =>
  80. utils.fetchAsyncQuestionProperty(question, 'default', this.answers)
  81. ),
  82. concatMap(() =>
  83. utils.fetchAsyncQuestionProperty(question, 'choices', this.answers)
  84. ),
  85. concatMap(this.fetchAnswer.bind(this))
  86. );
  87. });
  88. }
  89. fetchAnswer(question) {
  90. const Prompt = this.prompts[question.type];
  91. this.activePrompt = new Prompt(question, this.rl, this.answers);
  92. return defer(() =>
  93. from(this.activePrompt.run().then((answer) => ({ name: question.name, answer })))
  94. );
  95. }
  96. setDefaultType(question) {
  97. // Default type to input
  98. if (!this.prompts[question.type]) {
  99. question.type = 'input';
  100. }
  101. return defer(() => of(question));
  102. }
  103. filterIfRunnable(question) {
  104. if (
  105. question.askAnswered !== true &&
  106. _.get(this.answers, question.name) !== undefined
  107. ) {
  108. return empty();
  109. }
  110. if (question.when === false) {
  111. return empty();
  112. }
  113. if (!_.isFunction(question.when)) {
  114. return of(question);
  115. }
  116. const { answers } = this;
  117. return defer(() =>
  118. from(
  119. runAsync(question.when)(answers).then((shouldRun) => {
  120. if (shouldRun) {
  121. return question;
  122. }
  123. })
  124. ).pipe(filter((val) => val != null))
  125. );
  126. }
  127. }
  128. module.exports = PromptUI;