@poppinss/cliui

Highly opinionated command line UI KIT

Usage no npm install needed!

<script type="module">
  import poppinssCliui from 'https://cdn.skypack.dev/@poppinss/cliui';
</script>

README

CLI UI

Command line UI Kit used by AdonisJS

This repo is a command line UI Kit used by the AdonisJS framework to design its command line interfaces.

The kit is highly opinionated and we will not allow configurable settings in the near future. We want to be consistent with our UI's without worrying about the configuration.

gh-workflow-image typescript-image npm-image license-image synk-image

Table of contents

Installation

Install the package from the npm registry by running following command.

npm i @poppinss/cliui

# Yarn users
yarn add @poppinss/cliui

Usage

Import the components you want to use from the package.

import { logger, instructions, sticker, tasks, table } from '@poppinss/cliui'
logger.info('hello world')

const spinner = logger.await('downloading')

await someTimeConsumingTask()
spinner.stop()

Logger

The logger exposes the following methods.

success(message, prefix?, suffix?)

Log success message. The message is printed to stdout.

logger.success('Account created')

// [ success ]  Account created

Optional prefix

logger.success('Account created', 'ap-south-1')

// [ap-south-1] [ success ]  Account created

Optional suffix

logger.success('Account created', undefined, 'ap-south-1')

// [ success ]  Account created (ap-south-1)

The prefix and suffix are support on all logger methods except logger.action

error(message, prefix?, suffix?)

Log an error message. The message is printed to stderr.

logger.error('Unable to write. Disk full')

// Or log error object
logger.error(new Error('Unable to write. Disk full'))

// [ error ]  Unable to write. Disk full

fatal(message, prefix?, suffix?)

The logger.error does not print the error stack. You must use logger.fatal to print the error stack.

logger.fatal(new Error('Unable to write. Disk full'))

warning(message, prefix?, suffix?)

Print a warning message. Message is written to stdout.

logger.warning('Running out of disk space')

// [ warn ]  Running out of disk space

info(message, prefix?, suffix?)

Print an info message. Message is again written to stdout.

logger.info('Your account is has been updated')

// [ info ]  Your account is has been updated

debug(message, prefix?, suffix?)

Print a debug message. Message is printed to stdout.

logger.debug('Something just happened')

// [ debug ]  Something just happened

log(message)

Similar to console.log, but instead uses the Logger renderer.

We will talk about renderers later in this document, since they make testing of log message little bit easier.

logger.log('hello world')

logError(message)

Similar to console.error, but instead use the Logger renderer.

log.logError('this is an error message')

logUpdate(message)

Log a message that overwrites the previously logged message. The method is helpful for building progress bars or animations.

logger.logUpdate(`downloading ${i}%`)

// Once completed, persist the message on console
logger.logUpdatePersist()

Here is a complete example of showing the downloading progress.

const sleep = () => new Promise((resolve) => setTimeout(resolve, 50))

async function run() {
  for (let i = 0; i <= 100; i = i + 2) {
    await sleep()
    logger.logUpdate(`downloading ${i}%`)
  }

  logger.logUpdatePersist()
}

run()

Action

In order to log results of an action/task, we make use of the action method.

const action = logger.action('create')
action.succeeded('config/auth.ts')

An action can end in one of the following states.

action.succeeded(message)

Action completed successfully

const action = logger.action('create')
action.succeeded('config/auth.ts')

action.‌skipped(message)

Skipped action

const action = logger.action('create')
action.skipped('app/Models/User.ts')

action.failed(message, errorMessage)

Action failed, an error message is required to share more context

const action = logger.action('create')
action.failed('server.ts', 'File already exists')

Instructions

Instructions are mainly the steps we want someone to perform in order to achieve something. For example:

  • Display instructions to start the development
  • Or display instructions to bundle the code for production
import { instructions, logger } from '@poppinss/cliui'

instructions()
  .add(`cd ${logger.colors.cyan('hello-world')}`)
  .add(`Run ${logger.colors.cyan('node ace serve --watch')} to start the server`)
  .render()
  • Calling the instructions() begins a new instructions block
  • Next, you can add new lines by using the .add() method.
  • Finally, call the render() method to render it on the console.

Sticker

Similar to the instructions, but a sticker does not prefix the lines with a pointer > arrow. Rest is all same.

It is helpful for displaying a message that needs the most attention. For example:

  • Update the CLI version
  • Or, the address to access the local server
import { sticker, logger } from '@poppinss/cliui'

sticker()
  .add('Started HTTP server')
  .add('')
  .add(`Local address:    ${logger.colors.cyan('http://localhost:3333')}`)
  .add(`Network address:  ${logger.colors.cyan('http://localhost:3333')}`)
  .render()

Tasks

We make use of tasks when performing multiple actions in respond to a command. For example:

  • Create a new AdonisJS app
  • Or, Setup packages after installation

The UI for the tasks is designed to only handle tasks running in sequence.

Task Renderers

Task has two renderers minimal and verbose. The minimal renderer is the default choice and switch to verbose in one of the following situations.

  • Command line is not interactive (no tty)
  • Or someone has explicitly opted for verbose output.

Running tasks

Following is a very simple example of creating and running multiple tasks.

import { tasks } from '@poppinss/cliui'

await tasks()
  .add('clone repo', async (logger, task) => {
    logger.info(`cloning ${someRepoUrl}`)

    await performClone()
    await task.complete()
  })
  .add('install dependencies', async (logger, task) => {
    const spinner = logger.await('running npm install')

    await performInstall()
    spinner.stop()

    await task.complete()
  })
  .run()
  • The add method accepts the task title and the callback function to invoke in order to perform the task
  • Once, you are done with the task jobs, you must call await task.complete() to complete the task. The await is important here.
  • In order to mark task as failed, you can call the task.fail method. All upcoming tasks will be stopped in case of a failure. ts await task.fail(new Error('Network error'))

By default, the minimal renderer is used and pivots to the verbose renderer only when terminal is not interactive.

Verbose renderer

In order to run tasks explicitly in the verbose mode, you can create the tasks instance using tasks.verbose() method.

tasks.verbose().add().add().run()