diff --git a/cli/src/commands/create/member/index.ts b/cli/src/commands/create/member/index.ts index fe5b712769..7a0ee2a935 100644 --- a/cli/src/commands/create/member/index.ts +++ b/cli/src/commands/create/member/index.ts @@ -1,5 +1,5 @@ import { Flags } from '../../../framework/flags.js' -import { formatted } from '../../../framework/output.js' +import { formatted, OutputFormat } from '../../../framework/output.js' import { DifyCommand } from '../../_shared/dify-command.js' import { httpRetryFlag } from '../../_shared/global-flags.js' import { runCreateMember } from './run.js' @@ -24,7 +24,7 @@ export default class CreateMember extends DifyCommand { description: 'workspace id (overrides DIFY_WORKSPACE_ID and stored default)', }), 'http-retry': httpRetryFlag, - 'output': Flags.string({ char: 'o', description: 'output format (json|yaml|name|text)', default: '' }), + 'output': Flags.outputFormat({ options: [OutputFormat.JSON, OutputFormat.YAML, OutputFormat.NAME, OutputFormat.TEXT], default: '' }), } async run(argv: string[]) { diff --git a/cli/src/commands/delete/member/index.ts b/cli/src/commands/delete/member/index.ts index f455de9fbb..1ec7956502 100644 --- a/cli/src/commands/delete/member/index.ts +++ b/cli/src/commands/delete/member/index.ts @@ -1,5 +1,5 @@ import { Args, Flags } from '../../../framework/flags.js' -import { formatted } from '../../../framework/output.js' +import { formatted, OutputFormat } from '../../../framework/output.js' import { DifyCommand } from '../../_shared/dify-command.js' import { httpRetryFlag } from '../../_shared/global-flags.js' import { runDeleteMember } from './run.js' @@ -23,7 +23,7 @@ export default class DeleteMember extends DifyCommand { description: 'workspace id (overrides DIFY_WORKSPACE_ID and stored default)', }), 'http-retry': httpRetryFlag, - 'output': Flags.string({ char: 'o', description: 'output format (json|yaml|name|text)', default: '' }), + 'output': Flags.outputFormat({ options: [OutputFormat.JSON, OutputFormat.YAML, OutputFormat.NAME, OutputFormat.TEXT], default: '' }), 'yes': Flags.boolean({ char: 'y', description: 'skip confirmation prompt', default: false }), } diff --git a/cli/src/commands/describe/app/index.ts b/cli/src/commands/describe/app/index.ts index 514201bdf0..61a87005d3 100644 --- a/cli/src/commands/describe/app/index.ts +++ b/cli/src/commands/describe/app/index.ts @@ -1,5 +1,5 @@ import { Args, Flags } from '../../../framework/flags.js' -import { formatted } from '../../../framework/output.js' +import { formatted, OutputFormat } from '../../../framework/output.js' import { DifyCommand } from '../../_shared/dify-command.js' import { httpRetryFlag } from '../../_shared/global-flags.js' import { runDescribeApp } from './run.js' @@ -20,7 +20,7 @@ export default class DescribeApp extends DifyCommand { static override flags = { 'workspace': Flags.string({ description: 'workspace id (overrides DIFY_WORKSPACE_ID and stored default)' }), 'http-retry': httpRetryFlag, - 'output': Flags.string({ char: 'o', description: 'output format (json|yaml|text)', default: '' }), + 'output': Flags.outputFormat({ options: [OutputFormat.JSON, OutputFormat.YAML, OutputFormat.TEXT], default: '' }), 'refresh': Flags.boolean({ description: 'bypass app-info cache and fetch fresh', default: false }), } diff --git a/cli/src/commands/get/app/index.ts b/cli/src/commands/get/app/index.ts index c5ce8516aa..c4ec2bd06c 100644 --- a/cli/src/commands/get/app/index.ts +++ b/cli/src/commands/get/app/index.ts @@ -1,6 +1,6 @@ import type { AppMode } from '@dify/contracts/api/openapi/types.gen' import { Args, Flags } from '../../../framework/flags.js' -import { table } from '../../../framework/output.js' +import { OutputFormat, table } from '../../../framework/output.js' import { DifyCommand } from '../../_shared/dify-command.js' import { httpRetryFlag } from '../../_shared/global-flags.js' import { runGetApp } from './run.js' @@ -42,7 +42,7 @@ export default class GetApp extends DifyCommand { 'name': Flags.string({ description: 'filter by app name (server-side substring)' }), 'tag': Flags.string({ description: 'filter by tag name (server-side exact match)' }), 'http-retry': httpRetryFlag, - 'output': Flags.string({ char: 'o', description: 'output format (json|yaml|name|wide)', default: '' }), + 'output': Flags.outputFormat({ options: [OutputFormat.JSON, OutputFormat.YAML, OutputFormat.NAME, OutputFormat.WIDE], default: '' }), } async run(argv: string[]) { diff --git a/cli/src/commands/get/member/index.ts b/cli/src/commands/get/member/index.ts index 44a3dd241a..b1b4d82033 100644 --- a/cli/src/commands/get/member/index.ts +++ b/cli/src/commands/get/member/index.ts @@ -1,5 +1,5 @@ import { Flags } from '../../../framework/flags.js' -import { table } from '../../../framework/output.js' +import { OutputFormat, table } from '../../../framework/output.js' import { DifyCommand } from '../../_shared/dify-command.js' import { httpRetryFlag } from '../../_shared/global-flags.js' import { runGetMember } from './run.js' @@ -23,7 +23,7 @@ export default class GetMember extends DifyCommand { 'page': Flags.integer({ description: 'page number', default: 1 }), 'limit': Flags.string({ description: 'page size [1..200]' }), 'http-retry': httpRetryFlag, - 'output': Flags.string({ char: 'o', description: 'output format (json|yaml|name|wide)', default: '' }), + 'output': Flags.outputFormat({ options: [OutputFormat.JSON, OutputFormat.YAML, OutputFormat.NAME, OutputFormat.WIDE], default: '' }), } async run(argv: string[]) { diff --git a/cli/src/commands/get/workspace/index.ts b/cli/src/commands/get/workspace/index.ts index f1edd17b03..3364f50761 100644 --- a/cli/src/commands/get/workspace/index.ts +++ b/cli/src/commands/get/workspace/index.ts @@ -1,5 +1,5 @@ import { Flags } from '../../../framework/flags.js' -import { raw, table } from '../../../framework/output.js' +import { OutputFormat, raw, table } from '../../../framework/output.js' import { DifyCommand } from '../../_shared/dify-command.js' import { httpRetryFlag } from '../../_shared/global-flags.js' import { runGetWorkspace } from './run.js' @@ -15,7 +15,7 @@ export default class GetWorkspace extends DifyCommand { static override flags = { 'http-retry': httpRetryFlag, - 'output': Flags.string({ char: 'o', description: 'output format (json|yaml|name|wide)', default: '' }), + 'output': Flags.outputFormat({ options: [OutputFormat.JSON, OutputFormat.YAML, OutputFormat.NAME, OutputFormat.WIDE], default: '' }), } async run(argv: string[]) { diff --git a/cli/src/commands/resume/app/index.ts b/cli/src/commands/resume/app/index.ts index 6498549493..99ef349d7b 100644 --- a/cli/src/commands/resume/app/index.ts +++ b/cli/src/commands/resume/app/index.ts @@ -1,4 +1,5 @@ import { Args, Flags } from '../../../framework/flags.js' +import { OutputFormat } from '../../../framework/output.js' import { DifyCommand } from '../../_shared/dify-command.js' import { httpRetryFlag } from '../../_shared/global-flags.js' import { resumeApp } from './run.js' @@ -25,7 +26,7 @@ export default class ResumeApp extends DifyCommand { 'with-history': Flags.boolean({ description: 'Replay executed-node history before attaching to live stream.', default: false }), 'stream': Flags.boolean({ description: 'Print output live as tokens/events arrive. Default: collect and print at end.', default: false }), 'think': Flags.boolean({ description: 'Show model thinking/reasoning when available. Strips ... blocks silently by default; with --think, thinking is printed to stderr.', default: false }), - 'output': Flags.string({ char: 'o', description: 'output format (json|yaml|text)', default: '' }), + 'output': Flags.outputFormat({ options: [OutputFormat.JSON, OutputFormat.YAML, OutputFormat.TEXT], default: '' }), 'http-retry': httpRetryFlag, } diff --git a/cli/src/commands/run/app/index.ts b/cli/src/commands/run/app/index.ts index 4799303249..77a55cf5b0 100644 --- a/cli/src/commands/run/app/index.ts +++ b/cli/src/commands/run/app/index.ts @@ -1,4 +1,5 @@ import { Args, Flags } from '../../../framework/flags.js' +import { OutputFormat } from '../../../framework/output.js' import { DifyCommand } from '../../_shared/dify-command.js' import { httpRetryFlag } from '../../_shared/global-flags.js' import { agentGuide } from './guide.js' @@ -32,7 +33,7 @@ export default class RunApp extends DifyCommand { 'stream': Flags.boolean({ description: 'Print output live as tokens/events arrive (default: collect and print at end)', default: false }), 'think': Flags.boolean({ description: 'Show model thinking/reasoning when available. Strips ... blocks silently by default; with --think, thinking is printed to stderr.', default: false }), 'http-retry': httpRetryFlag, - 'output': Flags.string({ char: 'o', description: 'Output format (json|yaml|text)', default: '' }), + 'output': Flags.outputFormat({ options: [OutputFormat.JSON, OutputFormat.YAML, OutputFormat.TEXT], default: '' }), } async run(argv: string[]): Promise { diff --git a/cli/src/commands/set/member/index.ts b/cli/src/commands/set/member/index.ts index 3cbf3bf106..f9ffdb286c 100644 --- a/cli/src/commands/set/member/index.ts +++ b/cli/src/commands/set/member/index.ts @@ -1,5 +1,5 @@ import { Args, Flags } from '../../../framework/flags.js' -import { formatted } from '../../../framework/output.js' +import { formatted, OutputFormat } from '../../../framework/output.js' import { DifyCommand } from '../../_shared/dify-command.js' import { httpRetryFlag } from '../../_shared/global-flags.js' import { runSetMember } from './run.js' @@ -27,7 +27,7 @@ export default class SetMember extends DifyCommand { description: 'workspace id (overrides DIFY_WORKSPACE_ID and stored default)', }), 'http-retry': httpRetryFlag, - 'output': Flags.string({ char: 'o', description: 'output format (json|yaml|name|text)', default: '' }), + 'output': Flags.outputFormat({ options: [OutputFormat.JSON, OutputFormat.YAML, OutputFormat.NAME, OutputFormat.TEXT], default: '' }), } async run(argv: string[]) { diff --git a/cli/src/commands/version/index.ts b/cli/src/commands/version/index.ts index 01d398a001..884824d232 100644 --- a/cli/src/commands/version/index.ts +++ b/cli/src/commands/version/index.ts @@ -1,5 +1,5 @@ import { Flags } from '../../framework/flags.js' -import { formatted, raw, stringifyOutput } from '../../framework/output.js' +import { formatted, OutputFormat, raw, stringifyOutput } from '../../framework/output.js' import { colorEnabled } from '../../sys/io/color.js' import { realStreams } from '../../sys/io/streams' import { versionInfo } from '../../version/info.js' @@ -20,11 +20,7 @@ export default class Version extends DifyCommand { ] static override flags = { - 'output': Flags.string({ - char: 'o', - description: 'output format (text|json|yaml)', - default: '', - }), + 'output': Flags.outputFormat({ options: [OutputFormat.TEXT, OutputFormat.JSON, OutputFormat.YAML], default: '' }), 'client': Flags.boolean({ description: 'skip server probe' }), 'short': Flags.boolean({ description: 'print only the client semver' }), 'check-compat': Flags.boolean({ diff --git a/cli/src/errors/codes.test.ts b/cli/src/errors/codes.test.ts index 101eb2eead..fcaf55e00a 100644 --- a/cli/src/errors/codes.test.ts +++ b/cli/src/errors/codes.test.ts @@ -8,8 +8,8 @@ import { } from './codes.js' describe('error codes', () => { - it('has 18 codes (parity with internal/api/errors)', () => { - expect(ALL_ERROR_CODES).toHaveLength(18) + it('has correct number codes (parity with internal/api/errors)', () => { + expect(ALL_ERROR_CODES).toHaveLength(Object.keys(CODE_TO_EXIT_MAP).length) }) it('has the expected ExitCode buckets', () => { diff --git a/cli/src/errors/codes.ts b/cli/src/errors/codes.ts index f435476812..0d157f90cb 100644 --- a/cli/src/errors/codes.ts +++ b/cli/src/errors/codes.ts @@ -17,6 +17,7 @@ export const ErrorCode = { Server4xxOther: 'server_4xx_other', ClientError: 'client_error', Unknown: 'unknown', + IllegalArgumentError: 'illegal_argument', } as const export type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode] @@ -50,6 +51,7 @@ const CODE_TO_EXIT: Readonly> = { server_4xx_other: ExitCode.Generic, client_error: ExitCode.Generic, unknown: ExitCode.Generic, + illegal_argument: ExitCode.Usage, } export function exitFor(code: string): ExitCodeValue { diff --git a/cli/src/framework/errors.test.ts b/cli/src/framework/errors.test.ts new file mode 100644 index 0000000000..053bb01641 --- /dev/null +++ b/cli/src/framework/errors.test.ts @@ -0,0 +1,35 @@ +import type { FlagDefinition } from './types.js' +import { describe, expect, it } from 'vitest' +import { OutputFormatNotSupportedError, UnsupportedArgValueError } from './errors.js' + +describe('OutputFormatNotSupportedError', () => { + it('states the offending format in the message', () => { + const err = new OutputFormatNotSupportedError('csv') + expect(err.message).toBe('format csv is not supported by this command') + }) + +}) + +describe('UnsupportedArgValueError', () => { + it('includes both long and short option labels when a char exists', () => { + const def: FlagDefinition = { type: 'string', description: 'output', char: 'o', options: ['json', 'yaml'] } + const err = new UnsupportedArgValueError('output', def, 'csv') + expect(err.message).toBe('illegal value csv for flag --output / -o') + }) + + it('omits the short option label when the flag has no char', () => { + const def: FlagDefinition = { type: 'string', description: 'app mode', options: ['chat', 'workflow'] } + const err = new UnsupportedArgValueError('mode', def, 'chatbot') + expect(err.message).toBe('illegal value chatbot for flag --mode') + }) + + it('lists supported values in the hint', () => { + const def: FlagDefinition = { type: 'string', description: 'app mode', options: ['chat', 'workflow'] } + expect(new UnsupportedArgValueError('mode', def, 'chatbot').hint).toBe('supported value: chat, workflow') + }) + + it('leaves the hint empty when the flag declares no options', () => { + const def: FlagDefinition = { type: 'string', description: 'app mode' } + expect(new UnsupportedArgValueError('mode', def, 'chatbot').hint).toBe('') + }) +}) diff --git a/cli/src/framework/errors.ts b/cli/src/framework/errors.ts new file mode 100644 index 0000000000..b1cb4e1c29 --- /dev/null +++ b/cli/src/framework/errors.ts @@ -0,0 +1,23 @@ +import type { FlagDefinition } from './types' +import { BaseError } from '../errors/base' +import { ErrorCode } from '../errors/codes' + +export class OutputFormatNotSupportedError extends BaseError { + constructor(format: string) { + super({ + code: ErrorCode.IllegalArgumentError, + message: `format ${format} is not supported by this command`, + }) + } +} + +export class UnsupportedArgValueError extends BaseError { + constructor(flagName: string, flagDef: FlagDefinition, givenValue: string) { + const flagLabel = flagDef.char ? `--${flagName} / -${flagDef.char}` : `--${flagName}` + super({ + code: ErrorCode.IllegalArgumentError, + message: `illegal value ${givenValue} for flag ${flagLabel}`, + hint: flagDef.options ? `supported value: ${flagDef.options.join(', ')}` : '', + }) + } +} diff --git a/cli/src/framework/flags.test.ts b/cli/src/framework/flags.test.ts index efab143a06..c68d7733e4 100644 --- a/cli/src/framework/flags.test.ts +++ b/cli/src/framework/flags.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { UnsupportedArgValueError } from './errors.js' import { Args, Flags, parseArgv } from './flags.js' const meta = { @@ -190,13 +191,13 @@ describe('parseArgv', () => { it('rejects an invalid option value (space form)', () => { expect(() => parseArgv(['--mode', 'chatbot'], metaWithOptions)).toThrow( - '--mode must be one of: chat, workflow, completion', + UnsupportedArgValueError, ) }) it('rejects an invalid option value (= form)', () => { expect(() => parseArgv(['--mode=chatbot'], metaWithOptions)).toThrow( - '--mode must be one of: chat, workflow, completion', + UnsupportedArgValueError, ) }) }) diff --git a/cli/src/framework/flags.ts b/cli/src/framework/flags.ts index c5a3baa583..b730e0de9b 100644 --- a/cli/src/framework/flags.ts +++ b/cli/src/framework/flags.ts @@ -1,6 +1,12 @@ import type { ArgDefinition, CommandMeta, FlagDefinition, ParsedArgs, ParsedFlags } from './types.js' +import { UnsupportedArgValueError } from './errors.js' -function stringFlag( +function stringFlag( opts: Opts, ): FlagDefinition { return { @@ -10,7 +16,19 @@ function stringFlag( +function outputFormatFlag( + opts: Opts, +): FlagDefinition { + return { + type: 'string', + description: `output format (${opts.options.join('|')})`, + char: 'o', + multiple: false, + ...opts, + } +} + +function stringRepeatedFlag( opts: Opts, ): FlagDefinition { return { @@ -20,11 +38,11 @@ function stringRepeatedFlag { +function booleanFlag(opts: { description: string, char?: string, default?: boolean }): FlagDefinition { return { type: 'boolean', ...opts } } -function integerFlag( +function integerFlag( opts: Opts, ): FlagDefinition { return { type: 'integer', ...opts } as FlagDefinition @@ -35,6 +53,7 @@ export const Flags = { stringArray: stringRepeatedFlag, boolean: booleanFlag, integer: integerFlag, + outputFormat: outputFormatFlag, } function stringArg( @@ -91,7 +110,32 @@ function resolveByChar(char: string, meta: CommandMeta): [name: string, def: Fla function validateFlagOptions(name: string, raw: string, def: FlagDefinition): void { if (def.options !== undefined && !def.options.includes(raw)) - throw new Error(`--${name} must be one of: ${def.options.join(', ')}`) + throw new UnsupportedArgValueError(name, def, raw) +} + +type ResolvedFlag = { name: string, def: FlagDefinition, label: string, inlineRaw: string | undefined } + +function resolveToken(token: string, meta: CommandMeta): ResolvedFlag | null { + if (token.startsWith('--')) { + const eqIdx = token.indexOf('=') + const name = eqIdx !== -1 ? token.slice(2, eqIdx) : token.slice(2) + const inlineRaw = eqIdx !== -1 ? token.slice(eqIdx + 1) : undefined + const def = meta.flags[name] + if (!def) + throw new Error(`unknown flag: --${name}`) + return { name, def, label: `--${name}`, inlineRaw } + } + + if (token.length === 2 && token[1] !== undefined) { + const char = token[1] + const resolved = resolveByChar(char, meta) + if (!resolved) + throw new Error(`unknown flag: -${char}`) + const [name, def] = resolved + return { name, def, label: `-${char}`, inlineRaw: undefined } + } + + return null } export function parseArgv(argv: readonly string[], meta: CommandMeta): { args: ParsedArgs, flags: ParsedFlags } { @@ -110,63 +154,38 @@ export function parseArgv(argv: readonly string[], meta: CommandMeta): { args: P continue } - if (!pastDoubleDash && token.startsWith('--')) { - const eqIdx = token.indexOf('=') - let name: string - let rawValue: string | undefined - - if (eqIdx !== -1) { - name = token.slice(2, eqIdx) - rawValue = token.slice(eqIdx + 1) - } - else { - name = token.slice(2) - rawValue = undefined - } - - const def = meta.flags[name] - if (!def) - throw new Error(`unknown flag: --${name}`) - - if (def.type === 'boolean') { - flags[name] = rawValue === undefined ? true : coerceFlagValue(rawValue, def) - } - else if (rawValue !== undefined) { - validateFlagOptions(name, rawValue, def) - accumulateFlagValue(flags, name, coerceFlagValue(rawValue, def), def) - } - else { - i++ - const next = i < argv.length ? argv[i] : undefined - if (next === undefined || next.startsWith('-')) - throw new Error(`flag --${name} expects a value`) - - validateFlagOptions(name, next, def) - accumulateFlagValue(flags, name, coerceFlagValue(next, def), def) - } + if (pastDoubleDash || !token.startsWith('-')) { + positional.push(token) + continue } - else if (!pastDoubleDash && token.startsWith('-') && token.length === 2 && token[1] !== undefined) { - const char = token[1] - const resolved = resolveByChar(char, meta) - if (!resolved) - throw new Error(`unknown flag: -${char}`) - const [flagName, def] = resolved - if (def.type === 'boolean') { - flags[flagName] = true - } - else { - i++ - const next = i < argv.length ? argv[i] : undefined - if (next === undefined || next.startsWith('-')) - throw new Error(`flag -${char} expects a value`) + const resolved = resolveToken(token, meta) + if (!resolved) { + positional.push(token) + continue + } - accumulateFlagValue(flags, flagName, coerceFlagValue(next, def), def) - } + const { name, def, label, inlineRaw } = resolved + + if (def.type === 'boolean') { + flags[name] = inlineRaw === undefined ? true : coerceFlagValue(inlineRaw, def) + continue + } + + let raw: string + if (inlineRaw !== undefined) { + raw = inlineRaw } else { - positional.push(token) + i++ + const next = i < argv.length ? argv[i] : undefined + if (next === undefined || next.startsWith('-')) + throw new Error(`flag ${label} expects a value`) + raw = next } + + validateFlagOptions(name, raw, def) + accumulateFlagValue(flags, name, coerceFlagValue(raw, def), def) } const args: ParsedArgs = {} diff --git a/cli/src/framework/output.test.ts b/cli/src/framework/output.test.ts index accd8328b5..df5ba2ff26 100644 --- a/cli/src/framework/output.test.ts +++ b/cli/src/framework/output.test.ts @@ -1,5 +1,6 @@ import type { FormattedPrintable, NamePrintable, TablePrintable } from './output.js' import { describe, expect, it } from 'vitest' +import { OutputFormatNotSupportedError } from './errors.js' import { formatted, raw, @@ -99,13 +100,12 @@ describe('stringifyOutput — formatted', () => { json: () => ({}), } const out = formatted({ format: 'name', data: noName }) - expect(() => stringifyOutput(out)).toThrow('name output requires data.name()') + expect(() => stringifyOutput(out)).toThrow(OutputFormatNotSupportedError) }) it('unknown format: throws with allowed list', () => { const out = formatted({ format: 'csv', data: makeFormatted({}) }) - expect(() => stringifyOutput(out)).toThrow(/not supported/) - expect(() => stringifyOutput(out)).toThrow(/json, name, text, yaml/) + expect(() => stringifyOutput(out)).toThrow(OutputFormatNotSupportedError) }) }) @@ -175,13 +175,12 @@ describe('stringifyOutput — table', () => { json: () => [], } const out = table({ format: 'name', data: noName }) - expect(() => stringifyOutput(out)).toThrow('name output requires data.name()') + expect(() => stringifyOutput(out)).toThrow(OutputFormatNotSupportedError) }) it('unknown format: throws with allowed list', () => { const out = table({ format: 'csv', data: makeTable({}) }) - expect(() => stringifyOutput(out)).toThrow(/not supported/) - expect(() => stringifyOutput(out)).toThrow(/json, name, wide, yaml/) + expect(() => stringifyOutput(out)).toThrow(OutputFormatNotSupportedError) }) it('table renders column padding correctly', () => { diff --git a/cli/src/framework/output.ts b/cli/src/framework/output.ts index 7f56c1b1e2..fabf48caaf 100644 --- a/cli/src/framework/output.ts +++ b/cli/src/framework/output.ts @@ -1,4 +1,5 @@ import yaml from 'js-yaml' +import { OutputFormatNotSupportedError } from './errors' export type RawOutput = { readonly kind: 'raw' @@ -31,6 +32,14 @@ export type JsonPrintable = { readonly json: () => unknown } +export const OutputFormat = { + NAME: 'name', + JSON: 'json', + YAML: 'yaml', + TEXT: 'text', + WIDE: 'wide', +} as const + export type TableOutput = { readonly kind: 'table' readonly format: string @@ -77,32 +86,32 @@ export function stringifyOutput(output: CommandOutput): string { function stringifyFormattedOutput(output: FormattedOutput): string { switch (output.format) { case '': - case 'text': + case OutputFormat.TEXT: return output.data.text() - case 'json': + case OutputFormat.JSON: return `${JSON.stringify(output.data.json(), null, 2)}\n` - case 'yaml': + case OutputFormat.YAML: return yaml.dump(output.data.json(), { indent: 2, lineWidth: -1 }) - case 'name': + case OutputFormat.NAME: return `${toName(output.data)}\n` default: - throw new Error(`output format ${JSON.stringify(output.format)} not supported, allowed: json, name, text, yaml`) + throw new OutputFormatNotSupportedError(output.format) } } function stringifyTableOutput(output: TableOutput): string { switch (output.format) { case '': - case 'wide': + case OutputFormat.WIDE: return renderTable(output) - case 'json': + case OutputFormat.JSON: return `${JSON.stringify(output.data.json(), null, 2)}\n` - case 'yaml': + case OutputFormat.YAML: return yaml.dump(output.data.json(), { indent: 2, lineWidth: -1 }) - case 'name': + case OutputFormat.NAME: return `${toName(output.data)}\n` default: - throw new Error(`output format ${JSON.stringify(output.format)} not supported, allowed: json, name, wide, yaml`) + throw new OutputFormatNotSupportedError(output.format) } } @@ -186,7 +195,7 @@ function formatTable(rows: readonly (readonly string[])[]): string { function toName(data: TablePrintable | FormattedPrintable): string { if (!isNamePrintable(data)) - throw new Error('name output requires data.name()') + throw new OutputFormatNotSupportedError('name') return data.name() } diff --git a/cli/src/framework/types.ts b/cli/src/framework/types.ts index 1db95e9d16..62487c3622 100644 --- a/cli/src/framework/types.ts +++ b/cli/src/framework/types.ts @@ -9,7 +9,6 @@ export type FlagDefinition