From 477964d37e77ed6122fc58423eee2627f7577daa Mon Sep 17 00:00:00 2001 From: Sam Verschueren Date: Fri, 27 Jan 2017 21:57:28 +0100 Subject: [PATCH] add enabled function to a task - fixes #42 --- example.js | 4 ++-- index.js | 22 ++++++++++++++++-- lib/task.js | 26 +++++++++++++++++++++ readme.md | 37 ++++++++++++++++++++++++++++-- test/enabled.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 test/enabled.js diff --git a/example.js b/example.js index 9673d31..8bdb4c0 100644 --- a/example.js +++ b/example.js @@ -49,8 +49,8 @@ const tasks = new Listr([ }, { title: 'Install dependencies with npm', - skip: ctx => ctx.yarn !== false && 'Already installed with Yarn', - task: () => delay(300) + enabled: ctx => ctx.yarn === false, + task: () => delay(3000) }, { title: 'Run tests', diff --git a/index.js b/index.js index b4aa30d..bfda21c 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,13 @@ const Task = require('./lib/task'); const TaskWrapper = require('./lib/task-wrapper'); const renderer = require('./lib/renderer'); -const runTask = (task, context) => new TaskWrapper(task).run(context); +const runTask = (task, context) => { + if (!task.isEnabled()) { + return Promise.resolve(); + } + + return new TaskWrapper(task).run(context); +}; class Listr { @@ -31,6 +37,12 @@ class Listr { this.add(tasks || []); } + _checkAll(context) { + for (const task of this._tasks) { + task.check(context); + } + } + get tasks() { return this._tasks; } @@ -62,11 +74,17 @@ class Listr { context = context || Object.create(null); + this._checkAll(context); + let tasks; if (this._options.concurrent === true) { tasks = Promise.all(this._tasks.map(task => runTask(task, context))); } else { - tasks = this._tasks.reduce((promise, task) => promise.then(() => runTask(task, context)), Promise.resolve()); + tasks = this._tasks.reduce((promise, task) => promise.then(() => { + this._checkAll(context); + + return runTask(task, context); + }), Promise.resolve()); } return tasks diff --git a/lib/task.js b/lib/task.js index ec191ff..adcb385 100644 --- a/lib/task.js +++ b/lib/task.js @@ -30,10 +30,16 @@ class Task extends Subject { throw new TypeError(`Expected property \`skip\` of type \`function\` but got \`${typeof task.skip}\``); } + if (task.enabled && typeof task.enabled !== 'function') { + throw new TypeError(`Expected property \`enabled\` of type \`function\` but got \`${typeof task.enabled}\``); + } + this._listr = listr; this._options = options || {}; this._subtasks = []; this._output = undefined; + this._enabledFn = task.enabled; + this._isEnabled = true; this.title = task.title; this.skip = task.skip || defaultSkipFn; @@ -60,6 +66,22 @@ class Task extends Subject { 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; } @@ -76,6 +98,10 @@ class Task extends Subject { return this._state === state.COMPLETED; } + isEnabled() { + return this._isEnabled; + } + hasFailed() { return this._state === state.FAILED; } diff --git a/readme.md b/readme.md index d760fdf..1ad1f6a 100644 --- a/readme.md +++ b/readme.md @@ -47,11 +47,16 @@ const tasks = new Listr([ title: 'Install package dependencies with Yarn', task: (ctx, task) => execa('yarn') .catch(() => { - task.title = 'Install package dependencies with npm (Yarn not available)'; + ctx.yarn === false; - return execa('npm', ['install']); + task.skip('Yarn not available, install it via `npm install -g yarn`'); }) }, + { + title: 'Install package dependencies with npm', + enabled: ctx => ctx.yarn === false, + task: () => execa('npm', ['install']) + }, { title: 'Run tests', task: () => execa('npm', ['test']) @@ -179,6 +184,33 @@ const tasks = new Listr([ > Tip: You can still skip a task while already executing the `task` function with the [task object](#task-object). +## Enabling tasks + +By default, every task is enabled which means that every task will be executed. However, it's also possible to provide an `enabled` function that returns wheter the task should be executed or not. + +```js +const tasks = new Listr([ + { + title: 'Install package dependencies with Yarn', + task: (ctx, task) => execa('yarn') + .catch(() => { + ctx.yarn === false; + + task.skip('Yarn not available, install it via `npm install -g yarn`'); + }) + }, + { + title: 'Install package dependencies with npm', + enabled: ctx => ctx.yarn === false, + task: () => execa('npm', ['install']) + } +]); +``` + +In the above example, we try to run `yarn` first, if that fails we will fall back to `npm`. However, at first only the Yarn task will be visible. Because we set the `yarn` flag of the [context](https://github.com/SamVerschueren/listr#context) object to `false`, the second task will automatically be enabled and will be executed. + +> Note: This does not work in combination with [concurrent](https://github.com/SamVerschueren/listr#concurrent) tasks. + ## Context @@ -277,6 +309,7 @@ Every task is an observable. The task emits three different events and every eve 2. The task outputted data (`DATA`). 3. The task returns a subtask list (`SUBTASKS`). 4. The task's title changed (`TITLE`). +5. The task became enabled or disabled (`ENABLED`). This allows you to flexibly build up your UI. Let's render every task that starts executing. diff --git a/test/enabled.js b/test/enabled.js new file mode 100644 index 0000000..47c45fb --- /dev/null +++ b/test/enabled.js @@ -0,0 +1,60 @@ +import {serial as test} from 'ava'; +import Listr from '../'; +import SimpleRenderer from './fixtures/simple-renderer'; +import {testOutput} from './fixtures/utils'; + +test('do not run disabled tasks', async t => { + const list = new Listr([ + { + title: 'foo', + task: () => Promise.resolve() + }, + { + title: 'bar', + enabled: ctx => ctx.run === true, + task: () => Promise.resolve() + } + ], { + renderer: SimpleRenderer + }); + + testOutput(t, [ + 'foo [started]', + 'foo [completed]', + 'done' + ]); + + await list.run(); +}); + +test('run enabled task', async t => { + const list = new Listr([ + { + title: 'foo', + task: (ctx, task) => Promise.reject(new Error('Something went wrong')) + .catch(() => { + task.skip('It failed'); + + ctx.failed = true; + }) + }, + { + title: 'bar', + enabled: ctx => ctx.failed === true, + task: () => Promise.resolve() + } + ], { + renderer: SimpleRenderer + }); + + testOutput(t, [ + 'foo [started]', + 'foo [skipped]', + '> It failed', + 'bar [started]', + 'bar [completed]', + 'done' + ]); + + await list.run(); +});