listr

21 2月

listr可以方便开发者打包一连串task。

  • task属性
  • skip属性
  • enabled属性
  • context
  • 参数task
  • 自定义renderers
  • 实例方法(add,run)

Listr构造函数参数是个任务数组,只要中间任意任务失败,整个流程将中止。

task属性

每个任务的task属性支持同步返回,也支持返回Promise:

const Listr = require('listr');

const tasks = new Listr([
    {
        title: 'Success',
        task: () => 'Foo'
        // task: () => Promise.resolve('Foo')             // 这样写效果一样
    },
    {
        title: 'Failure',
        task: () => { throw new Error('Bar') }
        // task: () => Promise.reject(new Error('Bar'))   // 这样写效果一样
    }
]);

tasks.run().catch(err => {
    console.error(err);
});

task属性也支持返回Observable对象:

const Listr = require('listr');
const { Observable } = require('rxjs');

const tasks = new Listr([
    {
        title: 'Success',
        task: () => {
            return new Observable(observer => {
                observer.next('Foo');

                setTimeout(() => {
                    observer.next('Bar');
                }, 2000);

                setTimeout(() => {
                    observer.complete();
                }, 4000);
            });
        }
    },
    {
        title: 'Failure',
        task: () => Promise.reject(new Error('Bar'))
    }
]);

tasks.run().catch(err => {
    console.error(err);
});

task属性也支持返回 ReadableStream:

const Listr = require('listr');
const fs = require('fs');
const split = require('split');

const tasks = new Listr([
    {
        title: 'File',
        task: () => fs.createReadStream('./package.json', 'utf8')
            .pipe(split(/\r?\n/, null, {trailing: false}))
    }
]);

tasks.run().catch(err => {
    console.error(err);
});

skip属性

任务有个可选属性skip,用于定义是否跳过task属性。

如果skip返回true,就跳过task属性。如果skip返回string,将展示这个string作为跳过task属性的理由。

如果skip返回resolve为true的promise,就跳过task属性。

如果skip返回resolve为false的promise,就继续执行task属性。

如果skip返回reject的promise,整个任务流程都会失败。

const tasks = new Listr([
    {
        title: 'Task 1',
        task: () => Promise.resolve('Foo')
    },
    {
        title: 'Can be skipped',
        skip: () => {
            if (Math.random() > 0.5) {
                return 'Reason for skipping';
            }
        },
        task: () => 'Bar'
    },
    {
        title: 'Task 3',
        task: () => Promise.resolve('Bar')
    }
]);

tasks.run().catch(err => {
    console.error(err);
});

enabled属性

默认情况下,每个任务都将被执行。可以用一个是否执行的函数来控制。例如先尝试运行yarn,失败就在 context 里记录一下。npm任务会读取 context 里记录判断一下,决定是否需要执行。

const tasks = new Listr([
    {
        title: 'Install package dependencies with Yarn',
        task: (ctx, task) => execa('yarn')
            .catch(() => {
                ctx.yarn = false;               // 执行失败的话,在 context 里保存一下
                task.skip('Yarn not available, install it via `npm install -g yarn`');
            })
    },
    {
        title: 'Install package dependencies with npm',
        enabled: ctx => ctx.yarn === false,     // 如果 yarn 失败,再执行 npm
        task: () => execa('npm', ['install'])
    }
]);

context

context 可以作为参数传递给skip和task。这样就可以创建复合任务,并根据前一个的执行结果更改后续任务。

const tasks = new Listr([
    {
        title: 'Task 1',
        skip: ctx => ctx.foo === 'bar',         // 判断 context 参数
        task: () => Promise.resolve('Foo')
    },
    {
        title: 'Can be skipped',
        skip: () => {
            if (Math.random() > 0.5) {
                return 'Reason for skipping';
            }
        },
        task: ctx => {
            ctx.unicorn = 'rainbow';            // 设置 context 参数
        }
    },
    {
        title: 'Task 3',
        task: ctx => Promise.resolve(`${ctx.foo} ${ctx.bar}`)
    }
]);

tasks.run({ foo: 'bar' }).then(ctx => {         // run 方法中传递 context 参数
    console.log(ctx);                           // {foo: 'bar', unicorn: 'rainbow'}
});

参数task

task方法的第二个参数是task对象,可以让你在运行任务时更改标题,或根据某些结果跳过标题,也可以更新任务的输出。

const tasks = new Listr([
    {
        title: 'Install package dependencies with Yarn',
        task: (ctx, task) => execa('yarn')
            .catch(() => {
                ctx.yarn = false;
                task.title = `${task.title} (or not)`;
                task.skip('Yarn not available');
            })
    },
    {
        title: 'Install package dependencies with npm',
        skip: ctx => ctx.yarn !== false && 'Dependencies already installed with Yarn',
        task: (ctx, task) => {
            task.output = 'Installing dependencies...';
            return execa('npm', ['install'])
        }
    }
]);

tasks.run();

自定义 renderers

如果觉得默认 render 不好看,可以自定义,需要有 render 和 end 方法,分别控制正常展示和出异常时的展示。

class CustomRenderer {
	constructor(tasks, options) { ... }
	static get nonTTY() {
                ... // nonTTY属性返回bool值,表示展示是否支持非tty环境。如果不实现该属性,默认为false
		return false;
	}
	render() { ... }
	end(err) { ... }
}

module.exports = CustomRenderer;

实例方法

add(task):Returns the instance.

run([context]):Start executing the tasks. Returns a Promise for the context object.

综合例子

const tasks = new Listr([
    {
        title: 'Git',
        task: () => {
            return new Listr([
                {
                    title: 'Checking git status',
                    task: () => execa.stdout('git', ['status', '--porcelain']).then(result => {
                        if (result !== '') {
                            throw new Error('Unclean working tree. Commit or stash changes first.');
                        }
                    })
                },
                {
                    title: 'Checking remote history',
                    task: () => execa.stdout('git', ['rev-list', '--count', '--left-only', '@{u}...HEAD']).then(result => {
                        if (result !== '0') {
                            throw new Error('Remote history differ. Please pull changes.');
                        }
                    })
                }
            ], {concurrent: true});
        }
    },
    {
        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'])
    },
    {
        title: 'Run tests',
        task: () => execa('npm', ['test'])
    },
    {
        title: 'release package',
        task: () => execa('npm', ['run', 'release'])
    }
]);

tasks.run().catch(err => {
    console.error(err);
});

发表评论

电子邮件地址不会被公开。 必填项已用*标注