1 | const Listr = require('listr');
|
2 | const { EventEmitter } = require('events');
|
3 | const { Observable } = require('rxjs');
|
4 |
|
5 | /**
|
6 | * Reactive (rxjs) task class which serve as the middleware for Listr task registration
|
7 | */
|
8 | class 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 | */
|
84 | class 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 |
|
131 | module.exports = MultiTasksView;
|
132 | module.exports.ListrReactiveTask = ListrReactiveTask;
|