207 lines
4.2 KiB
JavaScript
207 lines
4.2 KiB
JavaScript
|
'use strict';
|
||
|
const isPromise = require('is-promise');
|
||
|
const streamToObservable = require('@samverschueren/stream-to-observable');
|
||
|
const Subject = require('rxjs').Subject;
|
||
|
const renderer = require('./renderer');
|
||
|
const state = require('./state');
|
||
|
const utils = require('./utils');
|
||
|
const ListrError = require('./listr-error');
|
||
|
|
||
|
const defaultSkipFn = () => false;
|
||
|
|
||
|
class Task extends Subject {
|
||
|
constructor(listr, task, options) {
|
||
|
super();
|
||
|
|
||
|
if (!task) {
|
||
|
throw new TypeError('Expected a task');
|
||
|
}
|
||
|
|
||
|
if (typeof task.title !== 'string') {
|
||
|
throw new TypeError(`Expected property \`title\` to be of type \`string\`, got \`${typeof task.title}\``);
|
||
|
}
|
||
|
|
||
|
if (typeof task.task !== 'function') {
|
||
|
throw new TypeError(`Expected property \`task\` to be of type \`function\`, got \`${typeof task.task}\``);
|
||
|
}
|
||
|
|
||
|
if (task.skip && typeof task.skip !== 'function') {
|
||
|
throw new TypeError(`Expected property \`skip\` to be of type \`function\`, got \`${typeof task.skip}\``);
|
||
|
}
|
||
|
|
||
|
if (task.enabled && typeof task.enabled !== 'function') {
|
||
|
throw new TypeError(`Expected property \`enabled\` to be of type \`function\`, got \`${typeof task.enabled}\``);
|
||
|
}
|
||
|
|
||
|
this._listr = listr;
|
||
|
this._options = options || {};
|
||
|
this._subtasks = [];
|
||
|
this._enabledFn = task.enabled;
|
||
|
this._isEnabled = true;
|
||
|
|
||
|
this.output = undefined;
|
||
|
this.title = task.title;
|
||
|
this.skip = task.skip || defaultSkipFn;
|
||
|
this.task = task.task;
|
||
|
}
|
||
|
|
||
|
get subtasks() {
|
||
|
return this._subtasks;
|
||
|
}
|
||
|
|
||
|
set state(state) {
|
||
|
this._state = state;
|
||
|
|
||
|
this.next({
|
||
|
type: 'STATE'
|
||
|
});
|
||
|
}
|
||
|
|
||
|
get state() {
|
||
|
return state.toString(this._state);
|
||
|
}
|
||
|
|
||
|
check(ctx) {
|
||
|
// Check if a task is enabled or disabled
|
||
|
if (this._state === undefined && this._enabledFn) {
|
||
|
const isEnabled = this._enabledFn(ctx);
|
||
|
|
||
|
if (this._isEnabled !== isEnabled) {
|
||
|
this._isEnabled = isEnabled;
|
||
|
|
||
|
this.next({
|
||
|
type: 'ENABLED',
|
||
|
data: isEnabled
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hasSubtasks() {
|
||
|
return this._subtasks.length > 0;
|
||
|
}
|
||
|
|
||
|
isPending() {
|
||
|
return this._state === state.PENDING;
|
||
|
}
|
||
|
|
||
|
isSkipped() {
|
||
|
return this._state === state.SKIPPED;
|
||
|
}
|
||
|
|
||
|
isCompleted() {
|
||
|
return this._state === state.COMPLETED;
|
||
|
}
|
||
|
|
||
|
isEnabled() {
|
||
|
return this._isEnabled;
|
||
|
}
|
||
|
|
||
|
hasFailed() {
|
||
|
return this._state === state.FAILED;
|
||
|
}
|
||
|
|
||
|
run(context, wrapper) {
|
||
|
const handleResult = result => {
|
||
|
// Detect the subtask
|
||
|
if (utils.isListr(result)) {
|
||
|
result._options = Object.assign(this._options, result._options);
|
||
|
|
||
|
result.exitOnError = result._options.exitOnError;
|
||
|
|
||
|
result.setRenderer(renderer.getRenderer('silent'));
|
||
|
this._subtasks = result.tasks;
|
||
|
|
||
|
this.next({
|
||
|
type: 'SUBTASKS'
|
||
|
});
|
||
|
|
||
|
return result.run(context);
|
||
|
}
|
||
|
|
||
|
// Detect stream
|
||
|
if (utils.isStream(result)) {
|
||
|
result = streamToObservable(result);
|
||
|
}
|
||
|
|
||
|
// Detect Observable
|
||
|
if (utils.isObservable(result)) {
|
||
|
result = new Promise((resolve, reject) => {
|
||
|
result.subscribe({
|
||
|
next: data => {
|
||
|
this.output = data;
|
||
|
|
||
|
this.next({
|
||
|
type: 'DATA',
|
||
|
data
|
||
|
});
|
||
|
},
|
||
|
error: reject,
|
||
|
complete: resolve
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Detect promise
|
||
|
if (isPromise(result)) {
|
||
|
return result.then(handleResult);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
return Promise.resolve()
|
||
|
.then(() => {
|
||
|
this.state = state.PENDING;
|
||
|
return this.skip(context);
|
||
|
})
|
||
|
.then(skipped => {
|
||
|
if (skipped) {
|
||
|
if (typeof skipped === 'string') {
|
||
|
this.output = skipped;
|
||
|
}
|
||
|
this.state = state.SKIPPED;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
return handleResult(this.task(context, wrapper));
|
||
|
})
|
||
|
.then(() => {
|
||
|
if (this.isPending()) {
|
||
|
this.state = state.COMPLETED;
|
||
|
}
|
||
|
})
|
||
|
.catch(error => {
|
||
|
this.state = state.FAILED;
|
||
|
|
||
|
if (error instanceof ListrError) {
|
||
|
wrapper.report(error);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!this.hasSubtasks()) {
|
||
|
// Do not show the message if we have subtasks as the error is already shown in the subtask
|
||
|
this.output = error.message;
|
||
|
}
|
||
|
|
||
|
this.next({
|
||
|
type: 'DATA',
|
||
|
data: error.message
|
||
|
});
|
||
|
|
||
|
wrapper.report(error);
|
||
|
|
||
|
if (this._listr.exitOnError !== false) {
|
||
|
// Do not exit when explicitely set to `false`
|
||
|
throw error;
|
||
|
}
|
||
|
})
|
||
|
.then(() => {
|
||
|
// Mark the Observable as completed
|
||
|
this.complete();
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = Task;
|