From 3ff5f6357a1157aa402ca580babb05eb13e7a249 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Mon, 16 Oct 2023 08:49:28 -0600 Subject: [PATCH] feat: no longer use wrapped process.stdout and process.stderr --- src/cli-ux/action/base.ts | 21 ++++++++++----------- src/cli-ux/flush.ts | 5 ++--- src/cli-ux/index.ts | 20 +++++++++++++------- src/cli-ux/prompt.ts | 7 +++---- src/cli-ux/stream.ts | 6 ++++++ src/cli-ux/styled/table.ts | 8 ++++---- src/cli-ux/write.ts | 12 ++++++++++++ src/command.ts | 9 ++++----- src/config/config.ts | 5 ++--- src/help/index.ts | 6 +++--- src/index.ts | 5 +++-- src/main.ts | 10 ++-------- src/screen.ts | 5 ++--- test/cli-ux/styled/header.test.ts | 5 ++--- test/command/main.test.ts | 5 +++-- 15 files changed, 71 insertions(+), 58 deletions(-) create mode 100644 src/cli-ux/write.ts diff --git a/src/cli-ux/action/base.ts b/src/cli-ux/action/base.ts index 8e6c2e3e2..9b738dd8b 100644 --- a/src/cli-ux/action/base.ts +++ b/src/cli-ux/action/base.ts @@ -1,7 +1,6 @@ import {inspect} from 'node:util' import {castArray} from '../../util/util' -import {stderr, stdout} from '../stream' import {Options} from './types' export interface ITask { @@ -20,8 +19,8 @@ export class ActionBase { type!: ActionType private stdmockOrigs = { - stderr: stderr.write, - stdout: stdout.write, + stderr: process.stderr.write, + stdout: process.stdout.write, } protected get output(): string | undefined { @@ -158,17 +157,17 @@ export class ActionBase { if (toggle) { if (this.stdmocks) return this.stdmockOrigs = { - stderr: stderr.write, - stdout: stdout.write, + stderr: process.stderr.write, + stdout: process.stdout.write, } this.stdmocks = [] - stdout.write = (...args: any[]) => { + process.stdout.write = (...args: any[]) => { this.stdmocks!.push(['stdout', args] as ['stdout', string[]]) return true } - stderr.write = (...args: any[]) => { + process.stderr.write = (...args: any[]) => { this.stdmocks!.push(['stderr', args] as ['stderr', string[]]) return true } @@ -176,8 +175,8 @@ export class ActionBase { if (!this.stdmocks) return // this._write('stderr', '\nresetstdmock\n\n\n') delete this.stdmocks - stdout.write = this.stdmockOrigs.stdout - stderr.write = this.stdmockOrigs.stderr + process.stdout.write = this.stdmockOrigs.stdout + process.stderr.write = this.stdmockOrigs.stderr } } catch (error) { this._write('stderr', inspect(error)) @@ -196,12 +195,12 @@ export class ActionBase { protected _write(std: 'stderr' | 'stdout', s: string | string[]): void { switch (std) { case 'stdout': { - this.stdmockOrigs.stdout.apply(stdout, castArray(s) as [string]) + this.stdmockOrigs.stdout.apply(process.stdout, castArray(s) as [string]) break } case 'stderr': { - this.stdmockOrigs.stderr.apply(stderr, castArray(s) as [string]) + this.stdmockOrigs.stderr.apply(process.stderr, castArray(s) as [string]) break } diff --git a/src/cli-ux/flush.ts b/src/cli-ux/flush.ts index 63713eea1..2e15540fb 100644 --- a/src/cli-ux/flush.ts +++ b/src/cli-ux/flush.ts @@ -1,5 +1,4 @@ import {error} from '../errors' -import {stdout} from './stream' function timeout(p: Promise, ms: number) { function wait(ms: number, unref = false) { @@ -14,9 +13,9 @@ function timeout(p: Promise, ms: number) { async function _flush() { const p = new Promise((resolve) => { - stdout.once('drain', () => resolve(null)) + process.stdout.once('drain', () => resolve(null)) }) - const flushed = stdout.write('') + const flushed = process.stdout.write('') if (flushed) return diff --git a/src/cli-ux/index.ts b/src/cli-ux/index.ts index 7bb083a1e..089c83f5c 100644 --- a/src/cli-ux/index.ts +++ b/src/cli-ux/index.ts @@ -3,17 +3,17 @@ import {format as utilFormat} from 'node:util' import * as Errors from '../errors' import {ActionBase} from './action/base' -import {Config, config} from './config' +import {config} from './config' import {flush as _flush} from './flush' import * as uxPrompt from './prompt' -import {stdout} from './stream' import * as styled from './styled' import uxWait from './wait' +import write from './write' const hyperlinker = require('hyperlinker') export class ux { - public static config: Config = config + public static config = config public static get action(): ActionBase { return config.action @@ -42,7 +42,7 @@ export class ux { public static debug(format: string, ...args: string[]): void { if (['debug', 'trace'].includes(this.config.outputLevel)) { - stdout.write(utilFormat(format, ...args) + '\n') + this.info(utilFormat(format, ...args) + '\n') } } @@ -55,13 +55,17 @@ export class ux { } public static info(format: string, ...args: string[]): void { - stdout.write(utilFormat(format, ...args) + '\n') + write.stdout(utilFormat(format, ...args) + '\n') } public static log(format?: string, ...args: string[]): void { this.info(format || '', ...args) } + public static logToStderr(format?: string, ...args: string[]): void { + write.stderr(utilFormat(format, ...args) + '\n') + } + public static get progress(): typeof styled.progress { return styled.progress } @@ -77,7 +81,7 @@ export class ux { public static styledJSON(obj: unknown): void { const json = JSON.stringify(obj, null, 2) if (!chalk.level) { - info(json) + this.info(json) return } @@ -96,7 +100,7 @@ export class ux { public static trace(format: string, ...args: string[]): void { if (this.config.outputLevel === 'trace') { - stdout.write(utilFormat(format, ...args) + '\n') + this.info(utilFormat(format, ...args) + '\n') } } @@ -128,6 +132,7 @@ const { flush, info, log, + logToStderr, progress, prompt, styledHeader, @@ -153,6 +158,7 @@ export { flush, info, log, + logToStderr, progress, prompt, styledHeader, diff --git a/src/cli-ux/prompt.ts b/src/cli-ux/prompt.ts index b72d232b6..84f25dc21 100644 --- a/src/cli-ux/prompt.ts +++ b/src/cli-ux/prompt.ts @@ -2,7 +2,6 @@ import chalk from 'chalk' import * as Errors from '../errors' import {config} from './config' -import {stderr} from './stream' export interface IPromptOptions { default?: string @@ -38,7 +37,7 @@ function normal(options: IPromptConfig, retries = 100): Promise { } process.stdin.setEncoding('utf8') - stderr.write(options.prompt) + process.stderr.write(options.prompt) process.stdin.resume() process.stdin.once('data', (b) => { if (timer) clearTimeout(timer) @@ -77,7 +76,7 @@ async function single(options: IPromptConfig): Promise { function replacePrompt(prompt: string) { const ansiEscapes = require('ansi-escapes') - stderr.write( + process.stderr.write( ansiEscapes.cursorHide + ansiEscapes.cursorUp(1) + ansiEscapes.cursorLeft + @@ -178,7 +177,7 @@ export async function anykey(message?: string): Promise { } const char = await prompt(message, {required: false, type: 'single'}) - if (tty) stderr.write('\n') + if (tty) process.stderr.write('\n') if (char === 'q') Errors.error('quit') if (char === '\u0003') Errors.error('ctrl-c') return char diff --git a/src/cli-ux/stream.ts b/src/cli-ux/stream.ts index cff276d15..1172c8b9d 100644 --- a/src/cli-ux/stream.ts +++ b/src/cli-ux/stream.ts @@ -35,5 +35,11 @@ class Stream { } } +/** + * @deprecated Use process.stdout directly. This will be removed in the next major version + */ export const stdout = new Stream('stdout') +/** + * @deprecated Use process.stderr directly. This will be removed in the next major version + */ export const stderr = new Stream('stderr') diff --git a/src/cli-ux/styled/table.ts b/src/cli-ux/styled/table.ts index af97a3454..1db3ba04a 100644 --- a/src/cli-ux/styled/table.ts +++ b/src/cli-ux/styled/table.ts @@ -9,7 +9,7 @@ import * as F from '../../flags' import * as Interfaces from '../../interfaces' import {stdtermwidth} from '../../screen' import {capitalize, sumBy} from '../../util/util' -import {stdout} from '../stream' +import write from '../write' class Table> { columns: (table.Column & {key: string; maxWidth?: number; width?: number})[] @@ -48,7 +48,7 @@ class Table> { 'no-header': options['no-header'] ?? false, 'no-truncate': options['no-truncate'] ?? false, output: csv ? 'csv' : output, - printLine: printLine ?? ((s: any) => stdout.write(s + '\n')), + printLine: printLine ?? ((s: any) => write.stdout(s + '\n')), rowStart: ' ', sort, title, @@ -97,7 +97,7 @@ class Table> { const filters = this.options.columns!.split(',') this.columns = this.filterColumnsFromHeaders(filters) } else if (!this.options.extended) { - // show extented columns/properties + // show extended columns/properties this.columns = this.columns.filter((c) => !c.extended) } @@ -189,7 +189,7 @@ class Table> { // truncation logic const shouldShorten = () => { // don't shorten if full mode - if (options['no-truncate'] || (!stdout.isTTY && !process.env.CLI_UX_SKIP_TTY_CHECK)) return + if (options['no-truncate'] || (!process.stdout.isTTY && !process.env.CLI_UX_SKIP_TTY_CHECK)) return // don't shorten if there is enough screen width const dataMaxWidth = sumBy(columns, (c) => c.width!) diff --git a/src/cli-ux/write.ts b/src/cli-ux/write.ts new file mode 100644 index 000000000..aa85d2b0f --- /dev/null +++ b/src/cli-ux/write.ts @@ -0,0 +1,12 @@ +const stdout = (msg: string): void => { + process.stdout.write(msg) +} + +const stderr = (msg: string): void => { + process.stderr.write(msg) +} + +export default { + stderr, + stdout, +} diff --git a/src/command.ts b/src/command.ts index bef7ea9f0..b93afb8b0 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1,9 +1,8 @@ import chalk from 'chalk' import {fileURLToPath} from 'node:url' -import {format, inspect} from 'node:util' +import {inspect} from 'node:util' import {ux} from './cli-ux' -import {stderr, stdout} from './cli-ux/stream' import {Config} from './config' import * as Errors from './errors' import {PrettyPrintableError} from './errors' @@ -37,7 +36,7 @@ const pjson = requireJson(__dirname, '..', 'package.json') * swallows stdout epipe errors * this occurs when stdout closes such as when piping to head */ -stdout.on('error', (err: any) => { +process.stdout.on('error', (err: any) => { if (err && err.code === 'EPIPE') return throw err }) @@ -258,7 +257,7 @@ export abstract class Command { public log(message = '', ...args: any[]): void { if (!this.jsonEnabled()) { message = typeof message === 'string' ? message : inspect(message) - stdout.write(format(message, ...args) + '\n') + ux.info(message, ...args) } } @@ -269,7 +268,7 @@ export abstract class Command { public logToStderr(message = '', ...args: any[]): void { if (!this.jsonEnabled()) { message = typeof message === 'string' ? message : inspect(message) - stderr.write(format(message, ...args) + '\n') + ux.logToStderr(message, ...args) } } diff --git a/src/config/config.ts b/src/config/config.ts index a3fcef2b7..4715b6004 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -3,9 +3,8 @@ import WSL from 'is-wsl' import {arch, userInfo as osUserInfo, release, tmpdir, type} from 'node:os' import {join, sep} from 'node:path' import {URL, fileURLToPath} from 'node:url' -import {format} from 'node:util' -import {stdout} from '../cli-ux/stream' +import {ux} from '../cli-ux' import {Command} from '../command' import {CLIError, error, exit, warn} from '../errors' import {getHelpFlagAdditions} from '../help/util' @@ -499,7 +498,7 @@ export class Config implements IConfig { exit(code) }, log(message?: any, ...args: any[]) { - stdout.write(format(message, ...args) + '\n') + ux.info(message, ...args) }, warn(message: string) { warn(message) diff --git a/src/help/index.ts b/src/help/index.ts index c1f6ea7d0..aeac1f6ef 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -1,7 +1,7 @@ import {format} from 'node:util' import stripAnsi from 'strip-ansi' -import {stdout} from '../cli-ux/stream' +import write from '../cli-ux/write' import {Command} from '../command' import {error} from '../errors' import * as Interfaces from '../interfaces' @@ -155,8 +155,8 @@ export class Help extends HelpBase { return new this.CommandHelpClass(command, this.config, this.opts) } - protected log(...args: string[]): void { - stdout.write(format.apply(this, args) + '\n') + protected log(...args: string[]) { + write.stdout(format.apply(this, args) + '\n') } public async showCommandHelp(command: Command.Loadable): Promise { diff --git a/src/index.ts b/src/index.ts index d70be059e..d6c0c6982 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,11 @@ -import {stderr} from './cli-ux/stream' +import write from './cli-ux/write' function checkCWD() { try { process.cwd() } catch (error: any) { if (error.code === 'ENOENT') { - stderr.write('WARNING: current directory does not exist\n') + write.stderr('WARNING: current directory does not exist\n') } } } @@ -15,6 +15,7 @@ checkCWD() export * as Args from './args' export * as ux from './cli-ux' export {flush} from './cli-ux/flush' +// Remove these in the next major version export {stderr, stdout} from './cli-ux/stream' export {Command} from './command' export {Config, Plugin} from './config' diff --git a/src/main.ts b/src/main.ts index 169d6e3f9..e4c6ce519 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,6 @@ import {URL, fileURLToPath} from 'node:url' -import {format, inspect} from 'node:util' -import {stdout} from './cli-ux/stream' +import {ux} from './cli-ux' import {Config} from './config' import {getHelpFlagAdditions, loadHelpClass, normalizeArgv} from './help' import * as Interfaces from './interfaces' @@ -9,11 +8,6 @@ import {OCLIF_MARKER_OWNER, Performance} from './performance' const debug = require('debug')('oclif:main') -const log = (message = '', ...args: any[]) => { - message = typeof message === 'string' ? message : inspect(message) - stdout.write(format(message, ...args) + '\n') -} - export const helpAddition = (argv: string[], config: Interfaces.Config): boolean => { if (argv.length === 0 && !config.pjson.oclif.default) return true const mergedHelpFlags = getHelpFlagAdditions(config) @@ -62,7 +56,7 @@ export async function run(argv?: string[], options?: Interfaces.LoadOptions): Pr // display version if applicable if (versionAddition(argv, config)) { - log(config.userAgent) + ux.log(config.userAgent) await collectPerf() return } diff --git a/src/screen.ts b/src/screen.ts index 529513d9c..3d889e192 100644 --- a/src/screen.ts +++ b/src/screen.ts @@ -1,4 +1,3 @@ -import {stderr, stdout} from './cli-ux/stream' import {settings} from './settings' function termwidth(stream: any): number { @@ -20,5 +19,5 @@ function termwidth(stream: any): number { const columns = Number.parseInt(process.env.OCLIF_COLUMNS!, 10) || settings.columns -export const stdtermwidth = columns || termwidth(stdout) -export const errtermwidth = columns || termwidth(stderr) +export const stdtermwidth = columns || termwidth(process.stdout) +export const errtermwidth = columns || termwidth(process.stderr) diff --git a/test/cli-ux/styled/header.test.ts b/test/cli-ux/styled/header.test.ts index 9175f45cf..c093ece4e 100644 --- a/test/cli-ux/styled/header.test.ts +++ b/test/cli-ux/styled/header.test.ts @@ -1,14 +1,13 @@ import {expect} from 'chai' import {SinonStub, stub} from 'sinon' -import {stdout} from '../../../src' import {ux} from '../../../src/cli-ux' describe('styled/header', () => { let writeStub: SinonStub beforeEach(() => { - writeStub = stub(stdout, 'write') + writeStub = stub(ux, 'info') }) afterEach(() => { @@ -17,6 +16,6 @@ describe('styled/header', () => { it('shows a styled header', () => { ux.styledHeader('A styled header') - expect(writeStub.firstCall.firstArg).to.equal('=== A styled header\n\n') + expect(writeStub.firstCall.firstArg).to.include('=== A styled header\n') }) }) diff --git a/test/command/main.test.ts b/test/command/main.test.ts index 581d95f3a..7997d9ab4 100644 --- a/test/command/main.test.ts +++ b/test/command/main.test.ts @@ -3,7 +3,8 @@ import {resolve} from 'node:path' import {SinonSandbox, SinonStub, createSandbox} from 'sinon' import stripAnsi from 'strip-ansi' -import {Interfaces, stdout} from '../../src/index' +import write from '../../src/cli-ux/write' +import {Interfaces} from '../../src/index' import {run} from '../../src/main' import {requireJson} from '../../src/util/fs' @@ -16,7 +17,7 @@ describe('main', () => { beforeEach(() => { sandbox = createSandbox() - stdoutStub = sandbox.stub(stdout, 'write').callsFake(() => true) + stdoutStub = sandbox.stub(write, 'stdout').callsFake(() => true) }) afterEach(() => {