Research
Security News
Threat Actor Exposes Playbook for Exploiting npm to Build Blockchain-Powered Botnets
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
The listr2 npm package is a powerful utility for creating beautiful and interactive command-line interfaces (CLI) with tasks lists. It allows developers to manage and execute multiple tasks in a structured and visually appealing way. It supports concurrent tasks, nested tasks, and customizable rendering, making it suitable for complex CLI applications.
Basic Task Execution
This feature allows for the execution of a series of tasks, each represented by a title and a task function. The tasks are executed sequentially, and the progress is visually represented in the CLI.
const { Listr } = require('listr2');
const tasks = new Listr([
{
title: 'Task 1',
task: () => Promise.resolve('First task result')
},
{
title: 'Task 2',
task: () => Promise.resolve('Second task result')
}
]);
tasks.run().catch(err => console.error(err));
Concurrent Tasks
This feature demonstrates how to run tasks concurrently. By setting the 'concurrent' option to true, tasks within the same group are executed in parallel, improving performance for independent tasks.
const { Listr } = require('listr2');
const tasks = new Listr([
{
title: 'Concurrent Tasks',
task: () => {
return new Listr([
{
title: 'Task 1',
task: () => Promise.resolve('First concurrent task result')
},
{
title: 'Task 2',
task: () => Promise.resolve('Second concurrent task result')
}
], { concurrent: true });
}
}
]);
tasks.run().catch(err => console.error(err));
Nested Tasks
This feature showcases the ability to nest tasks within other tasks. It is useful for structuring complex workflows where tasks have sub-tasks, allowing for better organization and readability.
const { Listr } = require('listr2');
const tasks = new Listr([
{
title: 'Parent Task',
task: () => {
return new Listr([
{
title: 'Nested Task 1',
task: () => Promise.resolve('First nested task result')
},
{
title: 'Nested Task 2',
task: () => Promise.resolve('Second nested task result')
}
]);
}
}
]);
tasks.run().catch(err => console.error(err));
Ora is a terminal spinner library that provides a similar visual feedback mechanism as listr2 but focuses solely on indicating the progress of ongoing tasks without the structured task list approach. It's simpler for use cases that require only a spinner.
Inquirer.js is a comprehensive library for creating interactive CLI prompts. While it doesn't manage tasks like listr2, it complements it by handling user inputs in a structured way, making it suitable for CLI applications that require both user interaction and task management.
Chalk is a styling library that allows developers to customize the terminal text appearance. Although it doesn't offer task management functionalities like listr2, it's often used alongside listr2 to enhance the visual output of tasks by adding colors and styles.
This is the expanded and re-written in Typescript version of the beautiful plugin by Sam Verschueren called Listr. Fully backwards compatible with the Listr itself but with more features.
Check out example.ts
in the root of the repository for the code in demo or follow through with the readme.
import { Listr } from 'listr2'
interface ListrCtx {
injectedContext: boolean
}
const tasks = new Listr<ListrCtx>(
[
{
// a title
title: 'Hello I am a title',
// an sync, async or observable function
task: async (ctx, task): Promise<void> => {}
}
],
{
// throw in some options, all have a default
concurrent: false
exitOnError: true
renderer: ListrRendererValue<Ctx>
nonTTYRenderer: ListrRendererValue<Ctx>
showSubtasks: true
collapse: false
clearOutput: false
ctx: Ctx
}
)
// and done!
const ctx = await tasks.run()
I have added a task manager kind of thing that is utilizing the Listr to the mix since I usually use it this kind of way. This task manager consists of three execution steps as described below.
import { Manager } from 'listr2'
This enables user to push the tasks in a queue and execute them when needed. In my opinion this provides a more clean code interface, just to add everything to queue and execute at the end of the script, thats why I included it.
private manager: Manager<Ctx> = new Manager<Ctx>()
manager.add([<--TASK-->])
const ctx = await manager.runAll<Ctx>({ concurrent: true })
To wrap a task around a new listr object with task manager, you can use manager.indent([], {...options})
. This way you can change the concurrency in a one big task lists.
const ctx = await manager.runAll<Ctx>({ concurrent: true })
manager.add<Ctx>([
{
title: 'Some task.',
task: (): void => {}
},
// this is equal to wrapping it inside task:(): Listr => new Listr(<-SOMETASK->)
manager.indent<Ctx>([
{
title: 'Some task.',
task: (): void => {}
},
], { concurrent: true })
], { concurrent: false })
You can also use the method below to directly create a Listr class. This will just return a Listr class, which you need to run after.
const myListr = manager.newListr<Ctx>([
{
title: 'Some task.',
task: (): void => {}
}
], { concurrent: false })
// then you can perform any class actions
myListr.run()
Input module uses the beautiful enquirer.
So with running a task.prompt
function, you first select which kind of prompt that you will use and second one is the enquirer object which you can see more in detail in the designated npm page.
To get a input you can assign the task a new prompt in an async function and write the response to the context.
It is not advisable to run prompts in a concurrent task because they will class and overwrite each others console output and when you do keyboard movements it will apply to the both.
It will render multiple times in verbose renderers, because the enquirer
's output is passed through the Listr itself as data. It will work anyway, but will not look that nice.
Prompts can either have a title or not but they will always be rendered at the end of the current console while using the default renderer.
new Listr<ListrCtx>([
{
task: async (ctx, task): Promise<any> => ctx.testInput = await task.prompt('Input', { message: 'test' })
},
{
title: 'Dump prompt.',
task: (ctx,task): void => {
task.output = ctx.testInput
}
}
])
If you want to use enquirer in your project, outside of the Listr, you can do it as follows.
import { newPrompt, PromptTypes, PromptOptionsType } from 'listr2'
export async function promptUser <T extends PromptTypes> (type: T, options: PromptOptionsType<T>): Promise<any>{
try {
return newPrompt(type, options).on('cancel', () => {
console.error('Cancelled prompt. Quitting.')
process.exit(20)
}).run()
} catch (e) {
console.error('There was a problem getting the answer of the last question. Quitting.')
console.debug(e.trace)
process.exit(20)
}
}
await promptUser('Input', { message: 'Hey what is that?' })
If you want to directly run it, and do not want to create a jumper function you can do as follows.
import { createPrompt } from 'listr2'
await createPrompt('Input', { message: 'Hey what is that?' })
Context which is the object that is being used while executing the actions in the Listr can now be enjected to the next Listr through using the custom options.
If all tasks are in a one big Listr class you dont have to inject context to the childs, since it is automatically injected as in the original.
// get the context from other listr object
const ctx = manager.runAll()
// and inject it to next via options
new Listr<ListrCtx>([
{
title: 'Dump prompt.',
task: (ctx,task): void => {
task.output = ctx.testInput
}
}
], {ctx})
Default renderer now supports a bottom bar to dump the output if desired. The output lenght can be limited through options of the task.
If title has no title, and generates output it will be pushed to the bottom bar instead.
Else you have to specicify explicitly to the dump the output to the bottom bar. Bottom bar output from the particular task will be cleared after the task finished, but with persistentOutput: true
option it can be persistent.
new Listr<ListrCtx>([
{
task: async (ctx, task): Promise<any> => {
task.output = 'Something'
},
bottomBar: Infinity, // bottom bar items to keep by this particular task, number | boolean. boolean true will set it to 1.
persistentOutput: true // defaults to false, has to be set explicitly if desired
},
], {ctx})
Tasks can be created without titles, and if any output is dumped it will be dumped to the bottom bar instead. If a task with no title is returns a new Listr task, it can be used to change the parallel task count and execute those particular tasks in order or in parallel. The subtasks of the untitled tasks will drop down one indentation level to be consistent. In the verbose renderer tasks with no-title will render as 'Task with no title.'
new Listr<ListrCtx>([
{
task: async (ctx, task): Promise<any> => {
task.output = 'Something'
}
},
], {})
The default update renderer now supports multi-line rendering. Therefore implementations like pushing through multi-line data now works properly.
Types are exported from the root.
FAQs
Terminal task list reborn! Create beautiful CLI interfaces via easy and logical to implement task lists that feel alive and interactive.
The npm package listr2 receives a total of 14,695,856 weekly downloads. As such, listr2 popularity was classified as popular.
We found that listr2 demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
Security News
NVD’s backlog surpasses 20,000 CVEs as analysis slows and NIST announces new system updates to address ongoing delays.
Security News
Research
A malicious npm package disguised as a WhatsApp client is exploiting authentication flows with a remote kill switch to exfiltrate data and destroy files.