UNPKG

4.75 kBJavaScriptView Raw
1const Listr = require('listr');
2const { EventEmitter } = require('events');
3const { Observable } = require('rxjs');
4
5/**
6 * Reactive (rxjs) task class which serve as the middleware for Listr task registration
7 */
8class ListrReactiveTask {
9 /**
10 * Constructor method which will initiate an eventEmitter for each instance.
11 * @param {Funciton} taskHandle the task handle which will be executed later, in the interface of:
12 * (reporter, callback) => { // use reporter and callback in the actual logic }
13 * @param {String} taskId taskId used to track task result
14 */
15 constructor(taskHandle, taskId) {
16 this.taskHandle = taskHandle;
17 this.taskId = taskId;
18
19 this._eventEmitter = new EventEmitter();
20 }
21
22 /**
23 * Reporter getter function which returns methods used for task executor to send updates
24 * - reporter.updateStatus update "status" for a task
25 */
26 get reporter() {
27 return {
28 updateStatus: (status) => {
29 this._eventEmitter.emit('status', status);
30 }
31 };
32 }
33
34 /**
35 * Execute the task handle, and convert task callback result to managed event.
36 */
37 execute() {
38 this.taskHandle(this.reporter, (err, result) => {
39 if (err) {
40 this._eventEmitter.emit('error', err);
41 } else {
42 this._eventEmitter.emit('complete', result);
43 }
44 });
45 }
46
47 /**
48 * Connect EventEmitter to Rx.Observable by mapping the listened events to subscriber's action.
49 * Mapping is: event observable
50 * status subscriber.next
51 * error subscriber.error + record error.context to task context
52 * title task.next
53 * complete subscriber.complete + record result to task context
54 */
55 buildObservable() {
56 return (ctx, task) => new Observable((subscriber) => {
57 this._eventEmitter.on('status', (status) => {
58 subscriber.next(status);
59 });
60 this._eventEmitter.on('error', (error) => {
61 if (error && typeof error === 'string') {
62 subscriber.error(error);
63 } else if (error && error.resultMessage) {
64 subscriber.error(error.resultMessage);
65 ctx[this.taskId] = error;
66 }
67 });
68 this._eventEmitter.on('title', (title) => {
69 task.title = title;
70 });
71 this._eventEmitter.on('complete', (result) => {
72 subscriber.complete();
73 if (result) {
74 ctx[this.taskId] = result;
75 }
76 });
77 });
78 }
79}
80
81/**
82 * MultiTasksView wraps Listr to inject ListrReactiveTask as the interface of each registered task.
83 */
84class MultiTasksView {
85 /**
86 * Constructor using the same options definition for Listr.
87 * @param {Object} options
88 */
89 constructor(options) {
90 this.taskRunner = new Listr([], options);
91 this._listrTasks = [];
92 }
93
94 /**
95 * Load task as ListrReactiveTask instance.
96 * @param {Function} task the task handle which will be executed later, in the interface of:
97 * (reporter, callback) => { // use reporter and callback in the actual logic }
98 * @param {String} title the initial task title to be displayed
99 * @param {String} taskId the identifier for the task to be used to pass back result
100 */
101 loadTask(task, title, taskId) {
102 const newTask = new ListrReactiveTask(task, taskId);
103 this._listrTasks.push(newTask);
104 this.taskRunner.add({ title, task: newTask.buildObservable() });
105 }
106
107 /**
108 * Register the event listeners and start the multi-tasks.
109 * @param {Function} callback (error, context) context.${taskId} contains the result for each task
110 */
111 start(callback) {
112 if (this._listrTasks.length === 0) {
113 return callback({ error: 'No tasks in current multi-tasks runner.' });
114 }
115
116 this._listrTasks.forEach((task) => {
117 task.execute();
118 });
119 this.taskRunner.run().then((context) => {
120 callback(null, context);
121 }).catch((listrError) => {
122 // listError { errors: [], context } contains array of errors from tasks and all context
123 callback({
124 error: [...new Set(listrError.errors)].join('\n- '),
125 partialResult: listrError.context
126 });
127 });
128 }
129}
130
131module.exports = MultiTasksView;
132module.exports.ListrReactiveTask = ListrReactiveTask;