task.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. 'use strict';
  2. const isStream = require('is-stream');
  3. const isPromise = require('is-promise');
  4. const streamToObservable = require('stream-to-observable');
  5. const Subject = require('rxjs/Subject').Subject;
  6. const renderer = require('./renderer');
  7. const state = require('./state');
  8. const utils = require('./utils');
  9. const ListrError = require('./listr-error');
  10. const defaultSkipFn = () => false;
  11. class Task extends Subject {
  12. constructor(listr, task, options) {
  13. super();
  14. if (!task) {
  15. throw new TypeError('Expected a task');
  16. }
  17. if (typeof task.title !== 'string') {
  18. throw new TypeError(`Expected property \`title\` to be of type \`string\`, got \`${typeof task.title}\``);
  19. }
  20. if (typeof task.task !== 'function') {
  21. throw new TypeError(`Expected property \`task\` to be of type \`function\`, got \`${typeof task.task}\``);
  22. }
  23. if (task.skip && typeof task.skip !== 'function') {
  24. throw new TypeError(`Expected property \`skip\` to be of type \`function\`, got \`${typeof task.skip}\``);
  25. }
  26. if (task.enabled && typeof task.enabled !== 'function') {
  27. throw new TypeError(`Expected property \`enabled\` to be of type \`function\`, got \`${typeof task.enabled}\``);
  28. }
  29. this._listr = listr;
  30. this._options = options || {};
  31. this._subtasks = [];
  32. this._enabledFn = task.enabled;
  33. this._isEnabled = true;
  34. this.output = undefined;
  35. this.title = task.title;
  36. this.skip = task.skip || defaultSkipFn;
  37. this.task = task.task;
  38. }
  39. get subtasks() {
  40. return this._subtasks;
  41. }
  42. set state(state) {
  43. this._state = state;
  44. this.next({
  45. type: 'STATE'
  46. });
  47. }
  48. get state() {
  49. return state.toString(this._state);
  50. }
  51. check(ctx) {
  52. // Check if a task is enabled or disabled
  53. if (this._state === undefined && this._enabledFn) {
  54. const isEnabled = this._enabledFn(ctx);
  55. if (this._isEnabled !== isEnabled) {
  56. this._isEnabled = isEnabled;
  57. this.next({
  58. type: 'ENABLED',
  59. data: isEnabled
  60. });
  61. }
  62. }
  63. }
  64. hasSubtasks() {
  65. return this._subtasks.length > 0;
  66. }
  67. isPending() {
  68. return this._state === state.PENDING;
  69. }
  70. isSkipped() {
  71. return this._state === state.SKIPPED;
  72. }
  73. isCompleted() {
  74. return this._state === state.COMPLETED;
  75. }
  76. isEnabled() {
  77. return this._isEnabled;
  78. }
  79. hasFailed() {
  80. return this._state === state.FAILED;
  81. }
  82. run(context, wrapper) {
  83. const handleResult = result => {
  84. // Detect the subtask
  85. if (utils.isListr(result)) {
  86. result._options = Object.assign(this._options, result._options);
  87. result.exitOnError = result._options.exitOnError;
  88. result.setRenderer(renderer.getRenderer('silent'));
  89. this._subtasks = result.tasks;
  90. this.next({
  91. type: 'SUBTASKS'
  92. });
  93. return result.run(context);
  94. }
  95. // Detect stream
  96. if (isStream(result)) {
  97. result = streamToObservable(result);
  98. }
  99. // Detect Observable
  100. if (utils.isObservable(result)) {
  101. result = new Promise((resolve, reject) => {
  102. result.subscribe({
  103. next: data => {
  104. this.output = data;
  105. this.next({
  106. type: 'DATA',
  107. data
  108. });
  109. },
  110. error: reject,
  111. complete: resolve
  112. });
  113. });
  114. }
  115. // Detect promise
  116. if (isPromise(result)) {
  117. return result.then(handleResult);
  118. }
  119. return result;
  120. };
  121. return Promise.resolve()
  122. .then(() => {
  123. this.state = state.PENDING;
  124. return this.skip(context);
  125. })
  126. .then(skipped => {
  127. if (skipped) {
  128. if (typeof skipped === 'string') {
  129. this.output = skipped;
  130. }
  131. this.state = state.SKIPPED;
  132. return;
  133. }
  134. return handleResult(this.task(context, wrapper));
  135. })
  136. .then(() => {
  137. if (this.isPending()) {
  138. this.state = state.COMPLETED;
  139. }
  140. })
  141. .catch(err => {
  142. this.state = state.FAILED;
  143. if (err instanceof ListrError) {
  144. wrapper.report(err);
  145. return;
  146. }
  147. if (!this.hasSubtasks()) {
  148. // Do not show the message if we have subtasks as the error is already shown in the subtask
  149. this.output = err.message;
  150. }
  151. this.next({
  152. type: 'DATA',
  153. data: err.message
  154. });
  155. wrapper.report(err);
  156. if (this._listr.exitOnError !== false) {
  157. // Do not exit when explicitely set to `false`
  158. throw err;
  159. }
  160. })
  161. .then(() => {
  162. // Mark the Observable as completed
  163. this.complete();
  164. });
  165. }
  166. }
  167. module.exports = Task;