Gathering detailed insights and metrics for @poppinss/cliui
Gathering detailed insights and metrics for @poppinss/cliui
npm install @poppinss/cliui
Typescript
Module System
Min. Node Version
Node Version
NPM Version
97.8
Supply Chain
99.6
Quality
88.1
Maintenance
100
Vulnerability
100
License
Fix types for TaskCallback method
Updated on Mar 12, 2025
Update dependencies
Updated on Dec 24, 2024
Update dependencies
Updated on Mar 28, 2024
Add logger.flushLogs to clear in memory logs
Updated on Feb 16, 2024
Allow creating conditional tasks and a silent spinner
Updated on Dec 27, 2023
Persist minimal renderer output after all tasks have finished
Updated on Dec 26, 2023
TypeScript (99.9%)
JavaScript (0.1%)
Verify real, reachable, and deliverable emails with instant MX records, SMTP checks, and disposable email detection.
Total Downloads
4,810,170
Last Day
12,215
Last Week
64,571
Last Month
278,406
Last Year
2,389,154
MIT License
125 Stars
133 Commits
5 Forks
6 Watchers
1 Branches
7 Contributors
Updated on Mar 12, 2025
Latest Version
6.4.3
Package Id
@poppinss/cliui@6.4.3
Unpacked Size
83.55 kB
Size
18.24 kB
File Count
24
NPM Version
10.8.2
Node Version
20.18.3
Published on
Mar 12, 2025
Cumulative downloads
Total Downloads
Last Day
0.1%
12,215
Compared to previous day
Last Week
1.4%
64,571
Compared to previous week
Last Month
0.7%
278,406
Compared to previous month
Last Year
110.5%
2,389,154
Compared to previous year
10
Opinionated UI KIT for Command Line apps
CLI UI is an opinionated UI Kit to log messages, render tables, display spinners, and much more. Following are some of the reasons for creating this package.
Install the package from the npm registry as follows:
1npm i @poppinss/cliui 2 3# yarn lovers 4yarn add @poppinss/cliui
Import the cliui
and create an instance of it.
1import { cliui } from '@poppinss/cliui' 2const ui = cliui() 3 4ui.logger.info('This is an info message') 5// [ info ] This is an info message
Now, let's say you are testing a command and want to assert that an info message is logged during the command's execution.
Usually, you will have to hack into the process.stdout
stream and collect messages within memory (for assertion) and strip any colors (aka ansi sequences) from the output.
However, with this package, you can turn on the raw
mode to collect the logger messages within memory and turn off all color transformations.
1// RAW MODE ON 2const ui = cliui({ raw: true }) 3 4ui.logger.info('This is an info message') 5 6const logs = ui.logger.getRenderer().getLogs() 7assert.deepEqual(logs, [ 8 { 9 stream: 'stdout', 10 message: '[ cyan(info) ] This is an info message' 11 } 12])
Similarly, you can assert that an error message is logged to stderr
.
1const ui = cliui({ raw: true }) 2 3ui.logger.error('Something went wrong') 4 5const logs = ui.logger.getRenderer().getLogs() 6assert.deepEqual(logs, [ 7 { 8 stream: 'stderr', 9 message: '[ red(error) ] Something went wrong' 10 } 11])
The logger displays all the log messages with consistent styling. Following are the available logging methods.
1import { cliui } from '@poppinss/cliui' 2const ui = cliui() 3 4// Writes to stdout 5logger.debug('Something just happened') 6logger.info('This is an info message') 7logger.success('Account created') 8logger.warning('Running out of disk space') 9 10// Writes to stderr 11logger.error(new Error('Unable to write. Disk full')) 12logger.fatal(new Error('Unable to write. Disk full'))
You can also define the prefix
and suffix
for the log message. The prefix and suffix are displayed with lower opacity (the dim color transformation is used).
1logger.info('Install packages', { suffix: 'npm i --production' })
You can display current time as a prefix using the %time%
keyword.
1logger.info('Message with time prefix', { prefix: '%time%' })
You can display a log message with a loading animation using the logger.await
method. The method accepts the initial message to display alongside an optional prefix
or suffix
.
1const loader = logger.await('installing packages', { suffix: 'npm i' }) 2 3// Start animation 4loader.start() 5 6// Update the message 7loader.update('unpacking packages', { suffix: undefined }) 8 9// Stop loader 10loader.stop()
If you writing conditionals around the spinning animation to start it in certain conditions, then you might want to create the spinner in silent mode. In silent mode, the spinner will not output any logs.
1const loader = logger.await('installing packages', { 2 suffix: 'npm i', 3 silent: true // 👈 4}) 5 6// Prints nothing 7loader.start() 8 9// Prints nothing 10loader.update('unpacking packages', { suffix: undefined }) 11 12// Prints nothing 13loader.stop()
You can also use the logger to just prepare the message (with colors and formatting) without writing it to the output stream. Just prefix the log message with prepare
and it will return a string value.
1const debugMessage = logger.prepareDebug('Something just happened')
2const infoMessage = logger.prepareInfo('This is an info message')
3const successMessage = logger.prepareSuccess('Account created')
4const warningMessage = logger.prepareWarning('Running out of disk space')
First, you must instantiate the cliui
in raw mode to collect all logs messages within memory. And then you can access the logs using logger.getRenderer()
to write assertions.
1const ui = cliui({ raw: true }) 2 3ui.logger.info('Hello world') 4 5const logs = ui.logger.getRenderer().getLogs() 6console.log(logs)
You can also flush all logs by calling flushLogs
.
1ui.logger.info('Hello world') 2 3const logs = ui.logger.getRenderer().getLogs() 4console.log(logs.length); // 1 5ui.logger.getRenderer().flushLogs(); 6console.log(logs.length); // 0
Logger actions are pre-styled logs to display the outcome of an action. For example, the action can be to create/update or delete a file.
You can create an action by calling the logger.action
method and pass the message to display. Once, done perfoming the underlying operation, you can either mark the action as succeeded
, skipped
, or failed
.
1logger 2 .action('Creating config/auth.ts') 3 .displayDuration() 4 .succeeded() 5 6logger 7 .action('Updating .tsconfig.json') 8 .succeeded() 9 10logger 11 .action('Creating app/Models/User.ts') 12 .skipped('File already exists') 13 14logger 15 .action('Creating server.ts') 16 .failed(new Error('File already exists'))
You can create a table using the ui.table
method. Under the hood, we are using cli-table3 but only expose some of its configuration options for consistency.
1const ui = cliui() 2const table = ui.table() 3 4table 5 .head([ 6 'Migration', 7 'Duration', 8 'Status', 9 ]) 10 .row([ 11 '1590591892626_tenants.ts', 12 '2ms', 13 'DONE' 14 ]) 15 .row([ 16 '1590595949171_entities.ts', 17 '2ms', 18 'DONE' 19 ]) 20 .render()
You can apply color transforms to any value when rendering the table. For example:
1table.row([ 2 '1590595949171_entities.ts', 3 '2', 4 ui.colors.green('DONE') 5])
You can right-align the columns by defining them as objects and using the hAlign
property. Also, make sure to align the header column right as well.
1table 2 .head([ 3 'Migration', 4 'Batch' 5 { 6 content: 'Status', 7 hAlign: 'right' 8 }, 9 ]) 10 11table.row([ 12 '1590595949171_entities.ts', 13 '2', 14 { 15 content: ui.colors.green('DONE'), 16 hAlign: 'right' 17 } 18])
You can render tables in full width (taking all the space of the terminal) by calling the table.fullWidth
method. In full-width mode:
1table.fullWidth()
You can also change the column index for the fluid column (the one that takes all the space) by calling the table.fluidColumnIndex
method.
1table 2 .fullWidth() 3 .fluidColumnIndex(1)
First, you must instantiate the cliui
in raw mode to collect all logs messages within memory. And then you can access the table output using logger.getRenderer()
to write assertions.
1const ui = cliui({ raw: true }) 2const table = ui.table() 3 4table 5 .head(['Migration','Duration', 'Status']) 6 .row([ '1590591892626_tenants.ts', '2ms', 'DONE']) 7 .render() 8 9const logs = table.getRenderer().getLogs() 10console.log(logs)
The instructions widget allows you to render a box with steps. Each step gets prefixed with an arrow >
.
1const ui = cliui() 2const instructions = ui.instructions() 3 4instructions 5 .add(`cd ${colors.cyan('hello-world')}`) 6 .add(`Run ${colors.cyan('node ace serve --watch')} to start the server`) 7 .render()
The sticker widget is the same as the instructions
widget. But it does not prefix each line with an arrow >
.
1const ui = cliui() 2const sticker = ui.sticker() 3 4sticker 5 .add('Started HTTP server') 6 .add('') 7 .add(`Local address: ${colors.cyan('http://localhost:3333')}`) 8 .add(`Network address: ${colors.cyan('http://192.168.1.2:3333')}`) 9 .render()
First, you must instantiate the cliui
in raw mode to collect all logs messages within memory. And then you can access the instructions/sticker output using logger.getRenderer()
to write assertions.
1const ui = cliui({ raw: true }) 2const instructions = ui.instructions() 3 4instructions 5 .add(`cd ${colors.cyan('hello-world')}`) 6 .add(`Run ${colors.cyan('node ace serve --watch')} to start the server`) 7 .render() 8 9const logs = instructions.getRenderer().getLogs() 10console.log(logs)
The tasks widget allows rendering a list of tasks to perform. Each task has an associated async callback to perform the task, report its progress, and also mark it as succeeded or failed.
task.error
method to prepare an error from a string value.1const ui = cliui() 2const tasks = ui.tasks() 3 4await tasks 5 .add('clone repo', async (task) => { 6 return 'Completed' 7 }) 8 .add('update package file', async (task) => { 9 return task.error('Unable to update package file') 10 }) 11 .add('install dependencies', async (task) => { 12 return 'Installed' 13 }) 14 .run()
Instead of writing the task progress messages to the console directly, you recommend you call the task.update
method.
The method ensures to display of the latest log message only when using the minimal
renderer and logs all messages when using the verbose
renderer.
1const sleep = () => new Promise<void>((resolve) => setTimeout(resolve, 50)) 2 3tasks 4 .add('clone repo', async (task) => { 5 for (let i = 0; i <= 100; i = i + 2) { 6 await sleep() 7 task.update(`Downloaded ${i}%`) 8 } 9 10 return 'Completed' 11 })
The verbose
renderer displays all the log messages instead of just the latest one. Also, the task output is rendered differently from the minimal renderer. Please, check the example file for the same.
You can create the tasks instance with a verbose
renderer as follows. The rest of the API is the same.
1const tasks = ui.tasks({ verbose: true })
First, you must instantiate the cliui
in raw mode to collect all logs messages within memory. And then you can access the tasks output using logger.getRenderer()
to write assertions.
1const ui = cliui({ raw: true }) 2const tasks = ui.tasks() 3 4await tasks 5 .add('clone repo', async (task) => { 6 return 'Completed' 7 }) 8 .add('update package file', async (task) => { 9 return task.error('Unable to update package file') 10 }) 11 .add('install dependencies', async (task) => { 12 return 'Installed' 13 }) 14 .run() 15 16const logs = tasks.getRenderer().getLogs() 17console.log(logs)
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
0 existing vulnerabilities detected
Reason
license file detected
Details
Reason
security policy file detected
Details
Reason
7 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 7
Reason
Found 3/30 approved changesets -- score normalized to 1
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
project is not fuzzed
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
branch protection not enabled on development/release branches
Details
Score
Last Scanned on 2025-03-10
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More