samverschueren / listr Goto Github PK
View Code? Open in Web Editor NEWTerminal task list
License: MIT License
Terminal task list
License: MIT License
Great lib! I'm running into a few cases where I would like to execute tasks in parallel. Would be great if there was support for this. One idea is options object to the Listr
constructor function such as { parallel: true }
. I'm working right now to see if I can hack this together or drop down to ora
and do it just with that lib. Will report back here if I find a good solution.
Due to an issue in is-observable, we have to fall back to duck typing as isObservable()
always returns false
.
The current implementation.
const isObservable = obj => Boolean(obj && typeof obj.subscribe === 'function' && obj.constructor.name === 'Observable');
Apparently it's not entirely correct
From @TylorS
This actually breaks the stuff that I'm working on because of the changes to isObservable. The stream library I'm using is most.js and it's constructor.name is Stream and not Observable. However it does implement Observable interfaces.
From @andywer
So how about that: We check with the current custom logic and fallback to is-observable or vice versa. If one of both is true we can be almost certain it is an Observable.
PS: I can also open an issue on zen-observable to use the symbol-observable so is-observable will work with the zen-observable as well.
In the following example:
new Listr(tasks, { concurrent })
.run({
errors: []
})
.then((res) => {
console.log(res)
})
.catch((error) => {
console.error(error)
process.exit(1)
})
the res
is always undefined
even then I resolve internal tasks with some value.
Or alternatively add context
to the then
so it can be accessed.
I'm trying to incorporate this module to lint-staged: lint-staged/lint-staged#32
and running into a few issues. I'd like to have all errors from subsequent tasks (i.e. linters) be outputted to the console "as is".
So, the "right" way of doing it ATM is like:
return new Promise((resolve, reject) => {
execa(res.bin, res.args)
.then(() => {
resolve(`${linter} passed!`)
})
.catch(err => {
reject(`
๐จ ${linter} found some errors. Please fix them and try committing again.
${err.stdout}
`
)
})
})
This works fine, but the output looses all the colors. This sucks!
To preserve colors, it seems there is no other way as spawning a process with stdio: "inherit"
option passed in. This makes output colored, but at the same time breaks the output of this module. It now appears twice and (worst thing) it cuts some of the stdout output from the task.
I'm wondering what would be a better work around this.
See also #16
From sindresorhus/np#114 (comment)
It should be possible to skip a task from the main function. Not sure about the implementation details though. We can't use this.skip('message')
inside the task
function because they don't play nice with fat-arrow functions. So maybe a second 'task' object as argument?
const tasks = new Listr([
{
title: 'Install with yarn',
task: (ctx, task) => execa('yarn')
.catch(() => {
task.skip('Yarn is not available on your system');
});
},
]);
Feedback much appreciated!
Maybe I do it wrong, but if I open Windows command line with cmd
and run the following JS script with node script.js
, I got instead of success status icons only V.
const execa = require('execa');
const listr = require('listr');
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);
});
Allow tasks to be labelled as "skipped" instead of completed, with an optional message explaining why.
See sindresorhus/np#64 (comment)
The explanation message should probably persist instead of being swallowed by log-update
.
It would be cool if you could return a Listr
instance from a task, and have all the subtasks display temporarily, then collapse when complete.
Suggestion :: Add parameters for tasks to use alternate icons
eg. if one of my tasks fails, I am in need of showing a warning icon, instead of an 'x', as the rest of the tasks can finish successfully, even with the other task failing.
running
const Listr = require('listr');
const tasks = new Listr([
{
title: 'Task1',
task: function() {
return new Promise(function(resolve) {
setTimeout(resolve, 1000);
});
}
},
{
title: 'Task Two',
task: function() {
return new Promise(function(resolve) {
setTimeout(resolve, 700);
});
}
},
{
title: 'Task Next',
task: function() {
return new Promise(function(resolve) {
setTimeout(resolve, 700);
}).then(function() {
return new Promise(function(resolve) {
console.log('running ...');
setTimeout(resolve, 700);
});
});
}
}
]);
tasks.run();
outputs:
node index
โ Task1
โ Task1
โ Task Two
โ Task Next
even though Task1 is set only once. Also, running ...
does not gets printed at all
EDIT:
$ node -v
v6.3.0
Hey!
For https://github.com/andywer/npm-launch I was thinking about a plain-text fallback in case !require('tty').isatty(process.stdout)
. Right now you get all those control characters in the output when piping to a file or similar. In my particular case I have Jenkins/Travis builds in mind.
But maybe you are interested to solve that edge case directly in Listr?
Is there any possibility to use and wait for a stdin input while executing a list? I couldn't find anything in the docs.
Hello,
I use zen-observable
, and the .next()
to update the progress. At the end of the task I would either like to
.next()
.complete()
)My use case is going through a number of files looking for a 'match', printing the current status, and it'd be nice if I could print on the console what the match is for each task.
From @okonet
I've just tried to integrate it and can't wrap my head on how do I display all errors when all tasks complete. When using default renderer, it just continues silently now, so I was expecting the error argumemt in the catch to contain all my errors. Looking at the code it is not the case. Is there a way of accessing it somehow after the execution? Should it be some internal state I have to take care of? Please point me in the right direction.
Because exitOnError
is set to false
, at the end, the .catch
function is not being invoked and you end up in then
. Because the result of the promise is the context object, we could add an errors
array and push all the errors into that list. A downside of this approach though is that we should make it a read-only property so that people can't overwrite that property.
@okonet @frederickfogerty thoughts?
The entire render stuff should be refactored to easier allow multiple different and even custom outputs.
You are currently using observable.subscribe(onNext, onError, onComplete)
, but that seems to be an RxJx specific thing.
You should use an observer object instead. That way people can still supply zen-observable
observables if they wish. https://github.com/zenparsing/zen-observable#observablesubscribe--observer-
If people are reporting visual bugs, we really need to know which terminal emulator they are using.
For the longest time, I was debugging an issue which was finally fixed by replacing:
new Listr({
title, task
}, {
title, task
})
with
new Listr([{
title, task
}, {
title, task
}])
Notice the difference? Listr wasn't doing anything in the first instance, I couldn't think of anything better to say than "it doesn't work", no output, no errors, nothing, just a no-op... and so it took so long to debug.
Currently a task is defined by message
and task
. We should change message
to title
it seems more appropriate.
When using collapse
option on parent task and returning Promise (via run
method) from subtasks, there are some weird rendering issues happening:
Returning Promise
Returning instance
Is this known issue? What Iโm trying to achieve is to have callback after list of subtasks is completed. Thenable seems like simplest solution for this.
Task returning subtask code which produces error:
task: ( ctx, task ) => {
const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
return new Listr(values.map(( value ) => {
return {
title: `Task ${value}`,
task: () => {
return new Promise(( resolve ) => {
setTimeout(() => {
resolve(value);
}, 300);
});
}
};
})).run();
}
From sindresorhus/np#114 (comment)
It would be nice if we could dynamically make tasks visible or invisible. If invisible, they shouldn't be executed. So maybe the property should be called enabled
instead? I had something in mind like
const tasks = new Listr([
{
title: 'Install with yarn',
task: ctx => execa('yarn')
.catch(() => {
ctx.yarn = false;
});
},
{
title: 'Install with npm',
visible: ctx => ctx.yarn === false,
task: () => execa('npm', ['test'])
}
]);
The function should be synchronous because otherwise we could end up in a state where it looks like the tasks aren't doing any work for 5 seconds for instance.
While I'm typing this, I might be more in favour if enabled
. As visible
might look like the task is invisible but it will still be executed.
Any remarks, improvements or things I overlooked? Feel free to provide feedback.
Sometimes tasks can require some user input from the terminal. Right now, it doesn't seem to be possible to pause the render that will always eat the user input. I'm not sure if this is even possible with a custom renderer. Either way, I think user input should be supported.
Can you point me in a right direction here?
In #17, the idea came up for passing a context
object as argument of a task function. This would allow you to pass data from one task to another allowing you to create composable tasks.
Do we have to take something special into account when doing this? Because we are targetting Node 4+, are there any reasons why we should pass in a regular Object.create(null)
object instead of Map
?
Swallowing all output may encourage users to ignore warnings which maybe they shouldn't (linter warnings, etc).
It would be nice if tasks were able to push any number of warnings to the output, that were not swallowed by log-update
I'm using listr with vorpal to make a CLI app, and after running the tasks, the cursor is not being redrawn.
I checked the CLIRenderer
that's based on log-update
and i realized that function done
is not being called after on end
function.
Maybe the idea is to change the render fn, adding a done condition:
const render = (tasks, done) => {
const output = tasks.map(task => task.render());
logUpdate(output.join('\n'));
if (done) {
logUpdate.done();
}
};
Hey guys,
I want to know if there is a way to show the task only when was run or is running, instead of display all the task list from the beginning.
Hi,
I'm having a slightly different use case: I would like to add task to Listr while it's already running (my task list is unknown when the program start).
On my first tries, the 2nd task is rendered but the spinner never shows and the list complete before the return of the second promise. My guess is that it use a reduce
to run the tasks and it won't accept new entries while reducing.
I'm working on a recursive approach that would shift
the array until null
. Do you think it can work? Any advice? Thanks
I really like that listr
produce by default useful output when redirected to file:
[05:30:10] Upgrading last deals [started]
[05:30:13] Upgrading last deals [completed]
[05:30:13] Find unsaved measures [started]
[05:30:35] Find unsaved measures [completed]
[05:30:35] Saving measures [started]
[05:30:35] Saving measures [completed]
[05:30:35] All measures saved [started]
[05:30:35] All measures saved [completed]
But since I append it to a log file, I would need at least the date to appear before the hour. If we could customize the format with an option or something it would be beautiful...
The case where a promise returns an observable is not handled at the moment which means that something like this
const task = {
title: 'Clean & Install',
task: () => del('node_modules').then(() => exec('npm', ['install'])))
};
Where exec
returns an execa
Observable
ends the task after removing the node_modules
.
Currently subtasks are hidden once they're done. It'd be nice to leave them on once they're done, as an outline of the process. For example instead of:
- Prerequisite check
- Git
- Installing dependencies
- Publishing package
Done
It could be a more explicative:
- Prerequisite check
- Validate version
- Check for pre-release version
- Check npm version
- Check git tag existence
- Git
- Check current branch
- Check local working tree
- Check remote history
- Installing dependencies
- Bumping version
- Publishing package
Done
The Observable interface as defined here
No longer contains the method forEach()
which is used to determine if an object returned by a task is an observable here.
I'd propose adjusting that line to something like
const isObservable = obj => obj && typeof obj.subscribe === 'function' && typeof obj.constructor.from === 'function'
I don't forsee the from()
static method being removed since it's the basis for converting to and from different observable types.
I'd be happy to PR this if it would be accepted.
Thank you for you time ๐
From #34 (comment)
This really is an edge case though but we should support it. The issue was introduced with #46 but we decided to land that without this fix.
The issue occurs when we have a task that looks like this
const tasks = new Listr([
{
title: 'Foo',
task: () => Promise.reject(new Error('Foo bar'))
},
{
title: 'Bar',
task: () => {
return new Listr([
{
title: 'Foo Bar Subtask 1',
task: () => Promise.reject(new Error('Foo bar baz'))
},
{
title: 'Foo Bar Subtask 2',
task: () => execa('npm', ['install'])
}
], {concurrent: true, exitOnError: true});
}
},
{
title: 'Foo',
task: () => delay(5000)
}
], {
exitOnError: false
});
The root list is configured to not exit on failure. The inner list however is set to fail on error. Because of that, when the inner list is being executed, the first task of that list throws an error. However, because of the combination concurrent
and exitOnError
, the second task of the inner list is still being executed entirely which means that npm install
will still install ALL dependencies instead of aborting.
In order to fix this, we should introduce cancelation. It should work with Observables (not tested yet) because they have built in cancellation. However, promises don't. But thanks to @sindresorhus, who had [some fun with promises(https://github.com/sindresorhus/promise-fun), we can use something like p-cancelable to introduce cancelable promises as well.
We would attach an extra onCancel(fn: Function)
method to the task object which is being passed as second argument to each task. The function passed into the onCancel
method will then be executed when the promise should be cancelled.
const tasks = new Listr([
{
title: 'Foo',
task: () => Promise.reject(new Error('Foo bar'))
},
{
title: 'Bar',
task: () => {
return new Listr([
{
title: 'Foo Bar Subtask 1',
task: () => Promise.reject(new Error('Foo bar baz'))
},
{
title: 'npm install',
task: (ctx, task) => {
const cp = execa('npm', ['install']);
task.onCancel(() => {
cp.kill();
});
return cp;
}
}
], {exitOnError: true});
}
},
{
title: 'Foo',
task: () => delay(5000)
},
], {
exitOnError: false
});
Feedback is more then welcome!
I am stoked about this module. I think it has potential to be the way to represent any non-trivial CLI behavior. I can imagine it being used in Yeoman, test runners, etc.
One thing I think we will need is to address some of what @jamestalmage says in sindresorhus/np#30 (comment).
when I'm installing something that has a long build step as part of it's install ... I want to see what's causing the hold up
That is to say, tasks have an associated status that is meaningful in some cases. For npm, a progress bar may be useful, but even just seeing which dependency is currently being installed would help explain long installs at a quick glance.
To achieve this, I imagine a method that would allow me to update the task description in place. Or maybe just characters to the right of the task description if you want to be opinionated about it and protect end users from crazy mutations to the task list.
listr
will throw an error when an observable task outputs something without a .trim()
method, like a number or object. Test case:
const tasks = new Listr([{
title: 'Count',
task: () => {
title: 'Count up',
task: () => rx.Observable.create(obj => {
var i = 0;
var id = setInterval(() => obj.next(i++), 100);
setTimeout(() => {clearInterval(id); obj.complete(i)}, 1050);
}}]);
When running np
on https://github.com/sindresorhus/sindre-playground the list jumps up when "Running tests" is showing output. It should rather push the list down.
In lint-staged I'm rejecting a promise with a String as docs says here https://github.com/SamVerschueren/listr#promises
But when doing so I get the following output:
{ TypeError: Cannot create property 'context' on string '
๐ซ pwd found some errors. Please fix them and try committing again.
'
at tasks.then.catch.err (/Users/okonet/Projects/OSS/sort-staged-example/front/node_modules/listr/index.js:74:17)
at process._tickCallback (internal/process/next_tick.js:103:7) context: {} }
Note that before it was displaying and this is the correct behavior:
๐ซ pwd found some errors. Please fix them and try committing again.
Fantastic package! I just want to propose a feature change which involves specifying a concurrent tasks limit rather than simply passing in true.
This feature would be beneficial to large tasks as they hog memory. Limiting them into chunks saves this memory allowing large tasks to run uninterrupted.
Much like in bluebird's map function
{ concurrency: 3 } // Limit the async chain to run 3 at a time
Hey @SamVerschueren, I am glad for the new renderers!
But I just experienced an issue when trying to use 0.6.0 for npm-launch. Seems like it doesn't render much anymore when using the default renderer. Maybe it's rendering the listr instance as completed
before it actually is?
{
title: 'Lint',
task: () =>
execa('npm', ['run', 'lint']).catch(error => {
// console.log(error)
throw new Error(`123\n456`)
}),
skip: () => !pkg.scripts.lint && 'missing script lint'
}
it's only show 456
,need i add custom renderers ?
See #11 (comment)
It would be good to have a way to access all the log data listr
hides.
Maybe an environment variable?
I'm running a set of tasks concurrently. At the moment, if one of the tasks fails, the whole execution is stopped.
Is it possible to have them all continue to completion, whether failed or passed?
Thanks
A renderer that only shows the current active task. Not sure what to do with subtasks though. Maybe render them like Parent > Child
.
Being able to skip a task is great and this piece of code works fine:
{
title: 'Creating some-directory folder',
task: (ctx, task) => {
if (existsSync(someDirectory)) {
task.skip('The some-directory folder already exists');
return;
}
mkdirSync(someDirectory);
},
},
However, say you want to iterate over folders inside of the some-directory
folder once it's created, it doesn't seem to be possible right now (please let me know if I'm mistaken) to do something like this:
{
title: 'Creating folders',
task: (ctx, task) => {
const tasks = [];
// imagine some list of folders to iterate over
folders.forEach((folder) => {
const name = folder.name;
tasks.push({
title: name,
task: () => { // passing the ctx and task here doesn't seem to have an effect below
if (existsSync(`${some-directory}/${name}`)) {
task.skip(`The ${name} folder already exists`); // this skips the entire main task and using throw new Error seems to work (unless there's a task below this one) but that fails the main task as well.
return;
}
mkdirSync(`${some-directory}/${name}`;
},
});
});
return new Listr(tasks, {concurrent: true, exitOnError: false});
},
},
Preferably, say you have a list of 10 folders to check and iterate over, if the first two exist but the rest don't, I'd want to gracefully skip over the first two and create the others, without failing the main task.
This has been playing in my head for quite a while now. So we have support for custom (3rd party) renderers like the current embedded update-renderer and the verbose-renderer. The default renderer is the update renderer, also in a non-TTY environment which doesn't work well with ANSI escape codes.
This means that you will have to write something like this.
new Listr([
// tasks go here
], {
renderer: process.isTTY ? 'update': 'verbose'
});
I want to get rid of this and automatically fall back to the verbose
renderer if Listr internally detects that it is not running in a TTY. This is perfectly possible and I believe that we should really do this. It doesn't make sense to render fancy stuff with log-update
for instance of it doesn't work correctly.
But what should be done with custom renderers then? Let's first recap what a custom renderer is and how it can be used. If you look into the custom renderers section of the readme, you can see that the basic structure looks like this.
class CustomRenderer {
constructor(tasks, options) { }
render() { }
end(err) { }
}
We can then use it and pass it in as renderer
.
const custom = require('custom-renderer');
new Listr([
// tasks go here
], {
renderer: custom
});
What should we do now when executed in a non-TTY environment?
verbose
rendererWe could always use the verbose
renderer by default. The drawback is that you will have to live with it that the output always looks the same.
The custom renderer could set a property indicating if it supports non-TTY environments.
class CustomRenderer {
constructor(tasks, options) { }
get nonTTY() {
return true;
}
render() { }
end(err) { }
}
If Listr detects that it isn't running in a TTY, it will look check if nonTTY
returns true
, if that is the case it could safely use that renderer. If it returns a falsy value, it will fall back to the verbose renderer.
If you pass in a custom renderer, it will automatically use that renderer in all environments. Except if you pass in another renderer as fallback renderer.
new Listr([
// tasks go here
], {
renderer: custom,
fallbackRenderer: 'verbose'
});
Options to be decided. Could as well be something like
new Listr([
// tasks go here
], {
renderer: {
default: custom,
noTTY: 'verbose'
}
});
Any feedback or input on these ideas would be more then appreciated!
I can't tell if this is broken or intended to work this way. If I have a task with two sub-tasks, and the first task has a skip
function which returns a skip message to be displayed, and the the second task succeeds, it would appear that the parent task collapses and โsucceedsโ.
Is that expected? I would expect it to stay uncollapsed so the skip message is visible.
In np
we have a Git
task with some subtasks. Those subtasks finish quickly so the whole thing feels very jumpy. It would be nice if we could just not show them at all, but show in case of a failure.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.