Files
dify/cli/src/framework/output.ts
Yunlu Wen a728e0ac69 feat: adding dify cli (#36348)
Co-authored-by: GareArc <garethcxy@dify.ai>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: L1nSn0w <l1nsn0w@qq.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: gigglewang <gigglewang@dify.ai>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com>
2026-05-26 01:12:36 +00:00

196 lines
5.3 KiB
TypeScript

import yaml from 'js-yaml'
export type RawOutput = {
readonly kind: 'raw'
readonly data: string
}
export type TableCell = string | number | boolean | null | undefined
export type TableColumn = {
readonly name: string
readonly priority: number
}
export type TablePrintable = {
readonly tableColumns: () => readonly TableColumn[]
readonly tableRows: () => readonly (readonly TableCell[])[]
readonly json: () => unknown
}
export type FormattedPrintable = {
readonly text: () => string
readonly json: () => unknown
}
export type NamePrintable = {
readonly name: () => string
}
export type JsonPrintable = {
readonly json: () => unknown
}
export type TableOutput<TRow extends TablePrintable> = {
readonly kind: 'table'
readonly format: string
readonly data: TRow
}
export type FormattedOutput<TData extends FormattedPrintable> = {
readonly kind: 'formatted'
readonly format: string
readonly data: TData
}
export type CommandOutput = RawOutput | TableOutput<TablePrintable> | FormattedOutput<FormattedPrintable>
export function raw(data: string): RawOutput {
return { kind: 'raw', data }
}
export function table<TRow extends TablePrintable>(opts: {
readonly format: string
readonly data: TRow
}): TableOutput<TRow> {
return { kind: 'table', ...opts }
}
export function formatted<TData extends FormattedPrintable>(opts: {
readonly format: string
readonly data: TData
}): FormattedOutput<TData> {
return { kind: 'formatted', ...opts }
}
export function stringifyOutput(output: CommandOutput): string {
switch (output.kind) {
case 'raw':
return output.data
case 'table':
return stringifyTableOutput(output)
case 'formatted':
return stringifyFormattedOutput(output)
}
}
function stringifyFormattedOutput(output: FormattedOutput<FormattedPrintable>): string {
switch (output.format) {
case '':
case 'text':
return output.data.text()
case 'json':
return `${JSON.stringify(output.data.json(), null, 2)}\n`
case 'yaml':
return yaml.dump(output.data.json(), { indent: 2, lineWidth: -1 })
case 'name':
return `${toName(output.data)}\n`
default:
throw new Error(`output format ${JSON.stringify(output.format)} not supported, allowed: json, name, text, yaml`)
}
}
function stringifyTableOutput(output: TableOutput<TablePrintable>): string {
switch (output.format) {
case '':
case 'wide':
return renderTable(output)
case 'json':
return `${JSON.stringify(output.data.json(), null, 2)}\n`
case 'yaml':
return yaml.dump(output.data.json(), { indent: 2, lineWidth: -1 })
case 'name':
return `${toName(output.data)}\n`
default:
throw new Error(`output format ${JSON.stringify(output.format)} not supported, allowed: json, name, wide, yaml`)
}
}
function renderTable(output: TableOutput<TablePrintable>): string {
const wide = output.format === 'wide'
const columns = output.data.tableColumns()
const keep: number[] = []
for (let i = 0; i < columns.length; i++) {
const column = columns[i]
if (column !== undefined && (column.priority === 0 || wide))
keep.push(i)
}
const rows = [
keep.map(i => columns[i]?.name ?? ''),
...output.data.tableRows().map(row => keep.map((idx) => {
const cell = row[idx]
return cell === null || cell === undefined ? '' : String(cell)
})),
]
return formatTable(rows)
}
function isWideCodePoint(cp: number): boolean {
return (
(cp >= 0x1100 && cp <= 0x115F)
|| cp === 0x2329 || cp === 0x232A
|| (cp >= 0x2E80 && cp <= 0x3247)
|| (cp >= 0x3250 && cp <= 0x4DBF)
|| (cp >= 0x4E00 && cp <= 0xA4C6)
|| (cp >= 0xA960 && cp <= 0xA97C)
|| (cp >= 0xAC00 && cp <= 0xD7A3)
|| (cp >= 0xF900 && cp <= 0xFAFF)
|| (cp >= 0xFE10 && cp <= 0xFE19)
|| (cp >= 0xFE30 && cp <= 0xFE6B)
|| (cp >= 0xFF01 && cp <= 0xFF60)
|| (cp >= 0xFFE0 && cp <= 0xFFE6)
|| (cp >= 0x1B000 && cp <= 0x1B001)
|| (cp >= 0x1F200 && cp <= 0x1F251)
|| (cp >= 0x20000 && cp <= 0x3FFFD)
)
}
function displayWidth(s: string): number {
let w = 0
for (const ch of s)
w += isWideCodePoint(ch.codePointAt(0) ?? 0) ? 2 : 1
return w
}
function formatTable(rows: readonly (readonly string[])[]): string {
if (rows.length === 0)
return ''
const colCount = rows[0]?.length ?? 0
const widths: number[] = Array.from({ length: colCount }, () => 0)
for (const row of rows) {
for (let i = 0; i < colCount; i++) {
const cell = row[i] ?? ''
const w = displayWidth(cell)
if (w > (widths[i] ?? 0))
widths[i] = w
}
}
const lines = rows.map((row) => {
const cells: string[] = []
for (let i = 0; i < colCount; i++) {
const cell = row[i] ?? ''
const isLast = i === colCount - 1
if (isLast) {
cells.push(cell)
}
else {
const pad = (widths[i] ?? 0) - displayWidth(cell) + 2
cells.push(cell + ' '.repeat(pad))
}
}
return cells.join('')
})
return `${lines.join('\n')}\n`
}
function toName(data: TablePrintable | FormattedPrintable): string {
if (!isNamePrintable(data))
throw new Error('name output requires data.name()')
return data.name()
}
function isNamePrintable(data: TablePrintable | FormattedPrintable): data is (TablePrintable | FormattedPrintable) & NamePrintable {
return typeof (data as { name?: unknown }).name === 'function'
}