|
|
@@ -1,13 +1,10 @@ |
|
|
|
import tty from 'tty'; |
|
|
|
import yargs, {Argv} from 'yargs'; |
|
|
|
import yargs, { Argv } from 'yargs'; |
|
|
|
import * as clack from '@clack/prompts'; |
|
|
|
import { DummyWriteStream } from './write-stream'; |
|
|
|
import pino, {LogFn} from 'pino'; |
|
|
|
import * as util from 'util'; |
|
|
|
|
|
|
|
export interface Logger { |
|
|
|
log: (message: unknown) => void; |
|
|
|
error: (message: unknown) => void; |
|
|
|
warn: (message: unknown) => void; |
|
|
|
debug: (message: unknown) => void; |
|
|
|
} |
|
|
|
export interface Logger extends pino.BaseLogger {} |
|
|
|
|
|
|
|
export interface CommandHandlerArgs { |
|
|
|
self: any; |
|
|
@@ -33,24 +30,6 @@ type PromptValueArgs = [Record<string, string>, ...never[]] |
|
|
|
| [[string, string], ...never[]] |
|
|
|
| [[string, string][], ...never[]]; |
|
|
|
|
|
|
|
class DummyWriteStream extends tty.WriteStream { |
|
|
|
private bufferInternal = Buffer.from(''); |
|
|
|
|
|
|
|
constructor() { |
|
|
|
super(0); |
|
|
|
} |
|
|
|
|
|
|
|
write(input: Uint8Array | string, _encoding?: BufferEncoding | ((err?: Error) => void), _cb?: (err?: Error) => void): boolean { |
|
|
|
this.bufferInternal = Buffer.concat([this.bufferInternal, Buffer.from(input)]); |
|
|
|
// noop |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
get buffer(): Buffer { |
|
|
|
return this.bufferInternal; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
export interface TestModeResult { |
|
|
|
exitCode?: number; |
|
|
|
stdout: DummyWriteStream; |
|
|
@@ -69,6 +48,7 @@ interface InteractiveModeOptions { |
|
|
|
export interface CliOptions { |
|
|
|
name: string; |
|
|
|
interactiveMode?: Partial<InteractiveModeOptions>; |
|
|
|
logger?: Logger | boolean; |
|
|
|
} |
|
|
|
|
|
|
|
export class Cli { |
|
|
@@ -171,27 +151,6 @@ export class Cli { |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
private generateLogger() { |
|
|
|
return { |
|
|
|
log: (message: unknown) => { |
|
|
|
const stdout = this.testMode ? this.testModeResult.stdout : process.stdout; |
|
|
|
stdout.write(`${message?.toString()}\n`); |
|
|
|
}, |
|
|
|
warn: (message: unknown) => { |
|
|
|
const stdout = this.testMode ? this.testModeResult.stdout : process.stdout; |
|
|
|
stdout.write(`WARN: ${message?.toString()}\n`); |
|
|
|
}, |
|
|
|
debug: (message: unknown) => { |
|
|
|
const stdout = this.testMode ? this.testModeResult.stdout : process.stdout; |
|
|
|
stdout.write(`DEBUG: ${message?.toString()}\n`); |
|
|
|
}, |
|
|
|
error: (message: unknown) => { |
|
|
|
const stderr = this.testMode ? this.testModeResult.stderr : process.stderr; |
|
|
|
stderr.write(`${message?.toString()}\n`); |
|
|
|
}, |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
private buildHandler(handlerFn: Function, interactiveModeOptions: InteractiveModeOptions) { |
|
|
|
const thisCli = this; |
|
|
|
return async function handler(this: any, commandArgs: Record<string, unknown>) { |
|
|
@@ -207,11 +166,78 @@ export class Cli { |
|
|
|
interactiveModeOptions, |
|
|
|
); |
|
|
|
|
|
|
|
const stdoutLogFn: LogFn = (...args: unknown[]) => { |
|
|
|
const stream = thisCli.testMode ? thisCli.testModeResult.stdout : process.stdout; |
|
|
|
const [arg0, arg1, ...etcArgs] = args; |
|
|
|
if (typeof arg0 === 'string' || typeof arg0 === 'number') { |
|
|
|
if (arg1) { |
|
|
|
if (etcArgs.length > 0) { |
|
|
|
stream.write(util.format(`${arg0}\n`, arg1, ...etcArgs)); |
|
|
|
} else { |
|
|
|
stream.write(util.format(`${arg0}\n`, arg1)); |
|
|
|
} |
|
|
|
} else { |
|
|
|
stream.write(util.format(`${arg0}\n`)); |
|
|
|
} |
|
|
|
} else if (typeof arg0 === 'object' && (typeof arg1 === 'string' || typeof arg1 === 'number')) { |
|
|
|
if (etcArgs.length > 0) { |
|
|
|
stream.write(util.format(`${arg1}\n`, ...etcArgs)); |
|
|
|
} else { |
|
|
|
stream.write(util.format(`${arg1}\n`)); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const stderrLogFn: LogFn = (...args: unknown[]) => { |
|
|
|
const stream = thisCli.testMode ? thisCli.testModeResult.stderr : process.stderr; |
|
|
|
const [arg0, arg1, ...etcArgs] = args; |
|
|
|
if (typeof arg0 === 'string' || typeof arg0 === 'number') { |
|
|
|
if (arg1) { |
|
|
|
if (etcArgs.length > 0) { |
|
|
|
stream.write(util.format(`${arg0}\n`, arg1, ...etcArgs)); |
|
|
|
} else { |
|
|
|
stream.write(util.format(`${arg0}\n`, arg1)); |
|
|
|
} |
|
|
|
} else { |
|
|
|
stream.write(util.format(`${arg0}\n`)); |
|
|
|
} |
|
|
|
} else if (typeof arg0 === 'object' && (typeof arg1 === 'string' || typeof arg1 === 'number')) { |
|
|
|
if (etcArgs.length > 0) { |
|
|
|
stream.write(util.format(`${arg1}\n`, ...etcArgs)); |
|
|
|
} else { |
|
|
|
stream.write(util.format(`${arg1}\n`)); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const loggerFn = thisCli.options.logger; |
|
|
|
const defaultLogger = { |
|
|
|
level: 'info', |
|
|
|
debug: stdoutLogFn, |
|
|
|
info: stdoutLogFn, |
|
|
|
warn: stdoutLogFn, |
|
|
|
error: stderrLogFn, |
|
|
|
fatal: stderrLogFn, |
|
|
|
trace: stdoutLogFn, |
|
|
|
silent: () => {}, |
|
|
|
} as Logger; |
|
|
|
const loggerBooleanFn = typeof loggerFn === 'boolean' && loggerFn ? defaultLogger : { |
|
|
|
level: 'silent', |
|
|
|
debug: () => {}, |
|
|
|
info: () => {}, |
|
|
|
warn: () => {}, |
|
|
|
error: () => {}, |
|
|
|
fatal: () => {}, |
|
|
|
trace: () => {}, |
|
|
|
silent: () => {}, |
|
|
|
} as Logger; |
|
|
|
const logger = typeof loggerFn === 'undefined' ? defaultLogger : (typeof loggerFn === 'function' ? loggerFn : loggerBooleanFn); |
|
|
|
|
|
|
|
let exited = false; |
|
|
|
const returnCode = await handlerFn({ |
|
|
|
self, |
|
|
|
interactive, |
|
|
|
logger: thisCli.generateLogger(), |
|
|
|
logger, |
|
|
|
send: (code: number) => { |
|
|
|
exited = true; |
|
|
|
thisCli.exit(code); |
|
|
|