终端界面

终端界面

Ace 终端界面(Terminal UI)由 @poppinss/cliui 包提供支持。该包导出了用于显示日志、渲染表格、渲染带动画的任务 UI 等辅助函数。

终端 UI 原语在设计时就考虑了测试。在编写测试时,你可以开启 raw 模式来禁用颜色和格式化,并将所有日志收集到内存中,以便对其编写断言。

相关阅读:测试 Ace 命令

显示日志消息

你可以使用 CLI 日志记录器显示日志消息。以下是可用的日志方法列表。

import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
async run() {
this.logger.debug('Something just happened')
this.logger.info('This is an info message')
this.logger.success('Account created')
this.logger.warning('Running out of disk space')
// 写入 stderr
this.logger.error(new Error('Unable to write. Disk full'))
this.logger.fatal(new Error('Unable to write. Disk full'))
}
}

添加前缀和后缀

使用选项对象,你可以为日志消息定义 prefixsuffix。前缀和后缀会以较低的透明度显示。

this.logger.info('installing packages', {
suffix: 'npm i --production'
})
this.logger.info('installing packages', {
prefix: process.pid
})

加载动画

带有加载动画的日志消息会在消息后面追加动画三点(...)。你可以使用 animation.update 方法更新日志消息,并使用 animation.stop 方法停止动画。

const animation = this.logger.await('installing packages', {
suffix: 'npm i'
})
animation.start()
// 更新消息
animation.update('unpacking packages', {
suffix: undefined
})
// 停止动画
animation.stop()

日志记录器操作

日志记录器操作可以以一致的样式和颜色显示操作的状态。

你可以使用 logger.action 方法创建一个操作。该方法接受操作标题作为第一个参数。

const createFile = this.logger.action('creating config/auth.ts')
try {
await performTasks()
createFile.displayDuration().succeeded()
} catch (error) {
createFile.failed(error)
}

你可以将一个操作标记为 succeededfailedskipped

action.succeeded()
action.skipped('Skip reason')
action.failed(new Error())

使用 ANSI 颜色格式化文本

Ace 使用 kleur 来格式化带有 ANSI 颜色的文本。使用 this.colors 属性,你可以访问 kleur 的链式 API。

this.colors.red('[ERROR]')
this.colors.bgGreen().white(' CREATED ')

渲染表格

可以使用 this.ui.table 方法创建表格。该方法返回 Table 类的实例,你可以用它来定义表头和行。

import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
async run() {
const table = this.ui.table()
table
.head([
'Migration',
'Duration',
'Status',
])
.row([
'1590591892626_tenants.ts',
'2ms',
'DONE'
])
.row([
'1590595949171_entities.ts',
'2ms',
'DONE'
])
.render()
}
}

在渲染表格时,你可以对任意值应用颜色转换。例如:

table.row([
'1590595949171_entities.ts',
'2',
this.colors.green('DONE')
])

右对齐列

你可以通过将列定义为对象并使用 hAlign 属性来右对齐列。请确保同时对表头列进行右对齐。

table
.head([
'Migration',
'Batch'
{
content: 'Status',
hAlign: 'right'
},
])
table.row([
'1590595949171_entities.ts',
'2',
{
content: this.colors.green('DONE'),
hAlign: 'right'
}
])

全宽渲染

默认情况下,表格以 auto 宽度渲染,只占用每列内容所需的空间。

不过,你可以使用 fullWidth 方法以全宽(占用所有终端空间)渲染表格。在全宽模式下:

  • 我们会根据内容大小渲染所有列。
  • 但第一列除外,它会占用所有可用空间。
table.fullWidth().render()

你可以使用 table.fluidColumnIndex 方法更改流体列(占用所有空间的列)的列索引。

table
.fullWidth()
.fluidColumnIndex(1)

打印贴纸

贴纸(stickers)允许你在一个带边框的盒子中渲染内容。当你想引起用户对重要信息的注意时,它们很有用。

你可以使用 this.ui.sticker 方法创建贴纸实例。

import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
async run() {
const sticker = this.ui.sticker()
sticker
.add('Started HTTP server')
.add('')
.add(`Local address: ${this.colors.cyan('http://localhost:3333')}`)
.add(`Network address: ${this.colors.cyan('http://192.168.1.2:3333')}`)
.render()
}
}

如果你想在一个盒子中显示一组说明,可以使用 this.ui.instructions 方法。说明输出中的每一行都会以箭头符号 > 作为前缀。

渲染任务

任务组件提供了一个出色的 UI,用于展示多个耗时任务的进度。该组件有以下两种渲染模式:

  • minimal(精简)模式下,当前正在运行的任务的 UI 会展开,每次只显示一行日志。
  • verbose(详细)模式下,每条日志语句都在自己的行中渲染。verbose 渲染器必须用于调试任务。

你可以使用 this.ui.tasks 方法创建任务组件实例。

import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
async run() {
const tasks = this.ui.tasks()
// ... 其余代码见后
}
}

单个任务使用 tasks.add 方法添加。该方法接受任务标题作为第一个参数,实现回调作为第二个参数。

你必须从回调中返回任务的状态。所有返回值都被视为成功消息,直到你将它们包裹在 task.error 方法中,或在回调内抛出异常。

import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
async run() {
const tasks = this.ui.tasks()
await tasks
.add('clone repo', async (task) => {
return 'Completed'
})
.add('update package file', async (task) => {
return task.error('Unable to update package file')
})
.add('install dependencies', async (task) => {
return 'Installed'
})
.run()
}
}

报告任务进度

与其将任务进度消息写入控制台,建议使用 task.update 方法。

update 方法使用 minimal 渲染器显示最新的日志消息,并使用 verbose 渲染器记录所有消息。

const sleep = () => new Promise<void>((resolve) => setTimeout(resolve, 50))
const tasks = this.ui.tasks()
await tasks
.add('clone repo', async (task) => {
for (let i = 0; i <= 100; i = i + 2) {
await sleep()
task.update(`Downloaded ${i}%`)
}
return 'Completed'
})
.run()

切换到 verbose 渲染器

你可以在创建任务组件时切换到 verbose 渲染器。你可以考虑允许命令用户传递一个标志来开启 verbose 模式。

import { BaseCommand, flags } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
@flags.boolean()
declare verbose: boolean
async run() {
const tasks = this.ui.tasks({
verbose: this.verbose
})
}
}