task.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Task = void 0;
  4. const rxjs_1 = require("rxjs");
  5. const stream_1 = require("stream");
  6. const event_constants_1 = require("../constants/event.constants");
  7. const state_constants_1 = require("../constants/state.constants");
  8. const listr_error_interface_1 = require("../interfaces/listr-error.interface");
  9. const listr_1 = require("../listr");
  10. const assert_1 = require("../utils/assert");
  11. const renderer_1 = require("../utils/renderer");
  12. const uuid_1 = require("../utils/uuid");
  13. /**
  14. * Create a task from the given set of variables and make it runnable.
  15. */
  16. class Task extends rxjs_1.Subject {
  17. constructor(listr, tasks, options, rendererOptions) {
  18. var _a, _b, _c, _d, _e, _f;
  19. super();
  20. this.listr = listr;
  21. this.tasks = tasks;
  22. this.options = options;
  23. this.rendererOptions = rendererOptions;
  24. /**
  25. * A channel for messages.
  26. *
  27. * This requires a separate channel for messages like error, skip or runtime messages to further utilize in the renderers.
  28. */
  29. this.message = {};
  30. // this kind of randomness is enough for task ids
  31. this.id = (0, uuid_1.generateUUID)();
  32. this.title = (_a = this.tasks) === null || _a === void 0 ? void 0 : _a.title;
  33. this.initialTitle = (_b = this.tasks) === null || _b === void 0 ? void 0 : _b.title;
  34. this.task = this.tasks.task;
  35. // parse functions
  36. this.skip = (_d = (_c = this.tasks) === null || _c === void 0 ? void 0 : _c.skip) !== null && _d !== void 0 ? _d : false;
  37. this.enabledFn = (_f = (_e = this.tasks) === null || _e === void 0 ? void 0 : _e.enabled) !== null && _f !== void 0 ? _f : true;
  38. // task options
  39. this.rendererTaskOptions = this.tasks.options;
  40. this.renderHook$ = this.listr.renderHook$;
  41. this.subscribe(() => {
  42. this.renderHook$.next();
  43. });
  44. }
  45. set state$(state) {
  46. this.state = state;
  47. this.next({
  48. type: event_constants_1.ListrEventType.STATE,
  49. data: state
  50. });
  51. // cancel the subtasks if this has already failed
  52. if (this.hasSubtasks() && this.hasFailed()) {
  53. for (const subtask of this.subtasks) {
  54. if (subtask.state === state_constants_1.ListrTaskState.PENDING) {
  55. subtask.state$ = state_constants_1.ListrTaskState.FAILED;
  56. }
  57. }
  58. }
  59. }
  60. set output$(data) {
  61. this.output = data;
  62. this.next({
  63. type: event_constants_1.ListrEventType.DATA,
  64. data
  65. });
  66. }
  67. set message$(data) {
  68. this.message = { ...this.message, ...data };
  69. this.next({
  70. type: event_constants_1.ListrEventType.MESSAGE,
  71. data
  72. });
  73. }
  74. set title$(title) {
  75. this.title = title;
  76. this.next({
  77. type: event_constants_1.ListrEventType.TITLE,
  78. data: title
  79. });
  80. }
  81. /**
  82. * A function to check whether this task should run at all via enable.
  83. */
  84. async check(ctx) {
  85. // Check if a task is enabled or disabled
  86. if (this.state === undefined) {
  87. this.enabled = await (0, assert_1.assertFunctionOrSelf)(this.enabledFn, ctx);
  88. this.next({
  89. type: event_constants_1.ListrEventType.ENABLED,
  90. data: this.enabled
  91. });
  92. }
  93. }
  94. /** Returns whether this task has subtasks. */
  95. hasSubtasks() {
  96. var _a;
  97. return ((_a = this.subtasks) === null || _a === void 0 ? void 0 : _a.length) > 0;
  98. }
  99. /** Returns whether this task is in progress. */
  100. isPending() {
  101. return this.state === state_constants_1.ListrTaskState.PENDING;
  102. }
  103. /** Returns whether this task is skipped. */
  104. isSkipped() {
  105. return this.state === state_constants_1.ListrTaskState.SKIPPED;
  106. }
  107. /** Returns whether this task has been completed. */
  108. isCompleted() {
  109. return this.state === state_constants_1.ListrTaskState.COMPLETED;
  110. }
  111. /** Returns whether this task has been failed. */
  112. hasFailed() {
  113. return this.state === state_constants_1.ListrTaskState.FAILED;
  114. }
  115. /** Returns whether this task has an active rollback task going on. */
  116. isRollingBack() {
  117. return this.state === state_constants_1.ListrTaskState.ROLLING_BACK;
  118. }
  119. /** Returns whether the rollback action was successful. */
  120. hasRolledBack() {
  121. return this.state === state_constants_1.ListrTaskState.ROLLED_BACK;
  122. }
  123. /** Returns whether this task has an actively retrying task going on. */
  124. isRetrying() {
  125. return this.state === state_constants_1.ListrTaskState.RETRY;
  126. }
  127. /** Returns whether enabled function resolves to true. */
  128. isEnabled() {
  129. return this.enabled;
  130. }
  131. /** Returns whether this task actually has a title. */
  132. hasTitle() {
  133. return typeof (this === null || this === void 0 ? void 0 : this.title) === 'string';
  134. }
  135. /** Returns whether this task has a prompt inside. */
  136. isPrompt() {
  137. return !!this.prompt;
  138. }
  139. /** Run the current task. */
  140. async run(context, wrapper) {
  141. var _a, _b, _c, _d, _e;
  142. const handleResult = (result) => {
  143. if (result instanceof listr_1.Listr) {
  144. // Detect the subtask
  145. // assign options
  146. result.options = { ...this.options, ...result.options };
  147. // switch to silent renderer since already rendering
  148. result.rendererClass = (0, renderer_1.getRenderer)('silent').renderer;
  149. result.renderHook$.subscribe(() => {
  150. this.renderHook$.next();
  151. });
  152. // assign subtasks
  153. this.subtasks = result.tasks;
  154. result.err = this.listr.err;
  155. this.next({ type: event_constants_1.ListrEventType.SUBTASK });
  156. result = result.run(context);
  157. }
  158. else if (this.isPrompt()) {
  159. // do nothing, it is already being handled
  160. }
  161. else if (result instanceof Promise) {
  162. // Detect promise
  163. result = result.then(handleResult);
  164. }
  165. else if (result instanceof stream_1.Readable) {
  166. // Detect stream
  167. result = new Promise((resolve, reject) => {
  168. result.on('data', (data) => {
  169. this.output$ = data.toString();
  170. });
  171. result.on('error', (error) => reject(error));
  172. result.on('end', () => resolve(null));
  173. });
  174. }
  175. else if (result instanceof rxjs_1.Observable) {
  176. // Detect Observable
  177. result = new Promise((resolve, reject) => {
  178. result.subscribe({
  179. next: (data) => {
  180. this.output$ = data;
  181. },
  182. error: reject,
  183. complete: resolve
  184. });
  185. });
  186. }
  187. return result;
  188. };
  189. const startTime = Date.now();
  190. // finish the task first
  191. this.state$ = state_constants_1.ListrTaskState.PENDING;
  192. // check if this function wants to be skipped
  193. const skipped = await (0, assert_1.assertFunctionOrSelf)(this.skip, context);
  194. if (skipped) {
  195. if (typeof skipped === 'string') {
  196. this.message$ = { skip: skipped };
  197. }
  198. else if (this.hasTitle()) {
  199. this.message$ = { skip: this.title };
  200. }
  201. else {
  202. this.message$ = { skip: 'Skipped task without a title.' };
  203. }
  204. this.state$ = state_constants_1.ListrTaskState.SKIPPED;
  205. return;
  206. }
  207. try {
  208. // add retry functionality
  209. const retryCount = ((_a = this.tasks) === null || _a === void 0 ? void 0 : _a.retry) && ((_b = this.tasks) === null || _b === void 0 ? void 0 : _b.retry) > 0 ? this.tasks.retry + 1 : 1;
  210. for (let retries = 1; retries <= retryCount; retries++) {
  211. try {
  212. // handle the results
  213. await handleResult(this.task(context, wrapper));
  214. break;
  215. }
  216. catch (err) {
  217. if (retries !== retryCount) {
  218. this.retry = { count: retries, withError: err };
  219. this.message$ = { retry: this.retry };
  220. this.title$ = this.initialTitle;
  221. this.output = undefined;
  222. wrapper.report(err, listr_error_interface_1.ListrErrorTypes.WILL_RETRY);
  223. this.state$ = state_constants_1.ListrTaskState.RETRY;
  224. }
  225. else {
  226. throw err;
  227. }
  228. }
  229. }
  230. if (this.isPending() || this.isRetrying()) {
  231. this.message$ = { duration: Date.now() - startTime };
  232. this.state$ = state_constants_1.ListrTaskState.COMPLETED;
  233. }
  234. }
  235. catch (error) {
  236. // catch prompt error, this was the best i could do without going crazy
  237. if (this.prompt instanceof listr_error_interface_1.PromptError) {
  238. // eslint-disable-next-line no-ex-assign
  239. error = new Error(this.prompt.message);
  240. }
  241. // execute the task on error function
  242. if ((_c = this.tasks) === null || _c === void 0 ? void 0 : _c.rollback) {
  243. wrapper.report(error, listr_error_interface_1.ListrErrorTypes.WILL_ROLLBACK);
  244. try {
  245. this.state$ = state_constants_1.ListrTaskState.ROLLING_BACK;
  246. await this.tasks.rollback(context, wrapper);
  247. this.state$ = state_constants_1.ListrTaskState.ROLLED_BACK;
  248. this.message$ = { rollback: this.title };
  249. }
  250. catch (err) {
  251. this.state$ = state_constants_1.ListrTaskState.FAILED;
  252. wrapper.report(err, listr_error_interface_1.ListrErrorTypes.HAS_FAILED_TO_ROLLBACK);
  253. throw err;
  254. }
  255. if (((_d = this.listr.options) === null || _d === void 0 ? void 0 : _d.exitAfterRollback) !== false) {
  256. // Do not exit when explicitly set to `false`
  257. throw new Error(this.title);
  258. }
  259. }
  260. else {
  261. // mark task as failed
  262. this.state$ = state_constants_1.ListrTaskState.FAILED;
  263. if (this.listr.options.exitOnError !== false && await (0, assert_1.assertFunctionOrSelf)((_e = this.tasks) === null || _e === void 0 ? void 0 : _e.exitOnError, context) !== false) {
  264. // Do not exit when explicitly set to `false`
  265. // report error
  266. wrapper.report(error, listr_error_interface_1.ListrErrorTypes.HAS_FAILED);
  267. throw error;
  268. }
  269. else if (!this.hasSubtasks()) {
  270. // subtasks will handle and report their own errors
  271. wrapper.report(error, listr_error_interface_1.ListrErrorTypes.HAS_FAILED_WITHOUT_ERROR);
  272. }
  273. }
  274. }
  275. finally {
  276. // Mark the observable as completed
  277. this.complete();
  278. }
  279. }
  280. }
  281. exports.Task = Task;