From 3ff5f6357a1157aa402ca580babb05eb13e7a249 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Mon, 16 Oct 2023 08:49:28 -0600 Subject: [PATCH 1/5] 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(() => { From 4b7cddafef4e0f4a494b8be273f09575081a93c8 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Mon, 16 Oct 2023 12:06:52 -0600 Subject: [PATCH 2/5] feat: export ux stubs --- src/cli-ux/index.ts | 2 ++ src/cli-ux/stub.ts | 40 +++++++++++++++++++++++++++++++ test/cli-ux/styled/header.test.ts | 14 ++++++----- test/command/main.test.ts | 17 +++++++------ 4 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 src/cli-ux/stub.ts diff --git a/src/cli-ux/index.ts b/src/cli-ux/index.ts index 089c83f5c..2ccb6ea8d 100644 --- a/src/cli-ux/index.ts +++ b/src/cli-ux/index.ts @@ -192,4 +192,6 @@ export {ActionBase} from './action/base' export {Config, config} from './config' export {ExitError} from './exit' export {IPromptOptions} from './prompt' +export {makeStubs} from './stub' + export {Table} from './styled' diff --git a/src/cli-ux/stub.ts b/src/cli-ux/stub.ts new file mode 100644 index 000000000..653f2f9f4 --- /dev/null +++ b/src/cli-ux/stub.ts @@ -0,0 +1,40 @@ +import type {SinonSandbox, SinonStub} from 'sinon' + +import write from './write' + +type Stubs = { + stderr: SinonStub + stdout: SinonStub +} + +/** + * Create sinon stubs for writing to stdout and stderr. + * + * @example + * import {ux} from '@oclif/core' + * + * describe('example', () => { + * let sandbox: SinonSandbox + * let stubs: ReturnType + * + * beforeEach(() => { + * sandbox = createSandbox() + * stubs = ux.makeStubs(sandbox) + * }) + * + * afterEach(() => { + * sandbox.restore() + * }) + * + * it('should log text to the console', () => { + * ux.log('Hello, world!') + * expect(stubs.stdout.firstCall.firstArg).to.equal('Hello, world!\n') + * }) + * }) + */ +export function makeStubs(sandbox: SinonSandbox): Stubs { + return { + stderr: sandbox.stub(write, 'stderr'), + stdout: sandbox.stub(write, 'stdout'), + } +} diff --git a/test/cli-ux/styled/header.test.ts b/test/cli-ux/styled/header.test.ts index c093ece4e..597abea70 100644 --- a/test/cli-ux/styled/header.test.ts +++ b/test/cli-ux/styled/header.test.ts @@ -1,21 +1,23 @@ import {expect} from 'chai' -import {SinonStub, stub} from 'sinon' +import {SinonSandbox, createSandbox} from 'sinon' -import {ux} from '../../../src/cli-ux' +import {ux} from '../../../src' describe('styled/header', () => { - let writeStub: SinonStub + let sandbox: SinonSandbox + let stubs: ReturnType beforeEach(() => { - writeStub = stub(ux, 'info') + sandbox = createSandbox() + stubs = ux.makeStubs(sandbox) }) afterEach(() => { - writeStub.restore() + sandbox.restore() }) it('shows a styled header', () => { ux.styledHeader('A styled header') - expect(writeStub.firstCall.firstArg).to.include('=== A styled header\n') + expect(stubs.stdout.firstCall.firstArg).to.include('=== A styled header\n') }) }) diff --git a/test/command/main.test.ts b/test/command/main.test.ts index 7997d9ab4..208ba9897 100644 --- a/test/command/main.test.ts +++ b/test/command/main.test.ts @@ -1,10 +1,9 @@ import {expect} from 'chai' import {resolve} from 'node:path' -import {SinonSandbox, SinonStub, createSandbox} from 'sinon' +import {SinonSandbox, createSandbox} from 'sinon' import stripAnsi from 'strip-ansi' -import write from '../../src/cli-ux/write' -import {Interfaces} from '../../src/index' +import {Interfaces, ux} from '../../src/index' import {run} from '../../src/main' import {requireJson} from '../../src/util/fs' @@ -13,11 +12,11 @@ const version = `@oclif/core/${pjson.version} ${process.platform}-${process.arch describe('main', () => { let sandbox: SinonSandbox - let stdoutStub: SinonStub + let stubs: ReturnType beforeEach(() => { sandbox = createSandbox() - stdoutStub = sandbox.stub(write, 'stdout').callsFake(() => true) + stubs = ux.makeStubs(sandbox) }) afterEach(() => { @@ -31,12 +30,12 @@ describe('main', () => { it('should run version', async () => { await run(['--version'], resolve(__dirname, '../../package.json')) - expect(stdoutStub.firstCall.firstArg).to.equal(`${version}\n`) + expect(stubs.stdout.firstCall.firstArg).to.equal(`${version}\n`) }) it('should run help', async () => { await run(['--help'], resolve(__dirname, '../../package.json')) - expect(stdoutStub.args.map((a) => stripAnsi(a[0])).join('')).to.equal(`base library for oclif CLIs + expect(stubs.stdout.args.map((a) => stripAnsi(a[0])).join('')).to.equal(`base library for oclif CLIs VERSION ${version} @@ -56,7 +55,7 @@ COMMANDS it('should show help for topics with spaces', async () => { await run(['--help', 'foo'], resolve(__dirname, 'fixtures/typescript/package.json')) - expect(stdoutStub.args.map((a) => stripAnsi(a[0])).join('')).to.equal(`foo topic description + expect(stubs.stdout.args.map((a) => stripAnsi(a[0])).join('')).to.equal(`foo topic description USAGE $ oclif foo COMMAND @@ -72,7 +71,7 @@ COMMANDS it('should run spaced topic help v2', async () => { await run(['foo', 'bar', '--help'], resolve(__dirname, 'fixtures/typescript/package.json')) - expect(stdoutStub.args.map((a) => stripAnsi(a[0])).join('')).to.equal(`foo bar topic description + expect(stubs.stdout.args.map((a) => stripAnsi(a[0])).join('')).to.equal(`foo bar topic description USAGE $ oclif foo bar COMMAND From be4d319af0c5f8d7c45d198a67eceede1d626df1 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Tue, 17 Oct 2023 14:50:36 -0600 Subject: [PATCH 3/5] chore: update sinon --- package.json | 2 +- src/config/ts-node.ts | 3 +- src/module-loader.ts | 3 +- src/util/fs.ts | 6 +- test/config/ts-node.test.ts | 3 +- yarn.lock | 119 ++++++++++++------------------------ 6 files changed, 50 insertions(+), 86 deletions(-) diff --git a/package.json b/package.json index 0cc1b31a7..240f29376 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "nyc": "^15.1.0", "prettier": "^3.0.3", "shx": "^0.3.4", - "sinon": "^11.1.2", + "sinon": "^16.1.0", "ts-node": "^10.9.1", "tsd": "^0.29.0", "typescript": "^5" diff --git a/src/config/ts-node.ts b/src/config/ts-node.ts index 7a0328350..ee61c2a79 100644 --- a/src/config/ts-node.ts +++ b/src/config/ts-node.ts @@ -1,11 +1,10 @@ -import {existsSync} from 'node:fs' import {join, relative as pathRelative, sep} from 'node:path' import * as TSNode from 'ts-node' import {memoizedWarn} from '../errors' import {Plugin, TSConfig} from '../interfaces' import {settings} from '../settings' -import {readJsonSync} from '../util/fs' +import {existsSync, readJsonSync} from '../util/fs' import {isProd} from '../util/util' import Cache from './cache' import {Debug} from './util' diff --git a/src/module-loader.ts b/src/module-loader.ts index 7fecc887f..553f6dfcc 100644 --- a/src/module-loader.ts +++ b/src/module-loader.ts @@ -1,4 +1,4 @@ -import {existsSync, lstatSync} from 'node:fs' +import {lstatSync} from 'node:fs' import {extname, join, sep} from 'node:path' import {pathToFileURL} from 'node:url' @@ -6,6 +6,7 @@ import {Command} from './command' import {tsPath} from './config/ts-node' import {ModuleLoadError} from './errors' import {Config as IConfig, Plugin as IPlugin} from './interfaces' +import {existsSync} from './util/fs' const getPackageType = require('get-package-type') diff --git a/src/util/fs.ts b/src/util/fs.ts index ac6346a9d..ff78464cc 100644 --- a/src/util/fs.ts +++ b/src/util/fs.ts @@ -1,4 +1,4 @@ -import {Stats, readFileSync} from 'node:fs' +import {Stats, existsSync as fsExistsSync, readFileSync} from 'node:fs' import {readFile, stat} from 'node:fs/promises' import {join} from 'node:path' @@ -68,3 +68,7 @@ export async function safeReadJson(path: string): Promise { return await readJson(path) } catch {} } + +export function existsSync(path: string): boolean { + return fsExistsSync(path) +} diff --git a/test/config/ts-node.test.ts b/test/config/ts-node.test.ts index 68b0a02a0..2ab8e44a2 100644 --- a/test/config/ts-node.test.ts +++ b/test/config/ts-node.test.ts @@ -1,5 +1,4 @@ import {expect} from 'chai' -import * as fs from 'node:fs' import {join, resolve} from 'node:path' import {SinonSandbox, createSandbox} from 'sinon' import * as tsNode from 'ts-node' @@ -28,7 +27,7 @@ describe('tsPath', () => { beforeEach(() => { sandbox = createSandbox() - sandbox.stub(fs, 'existsSync').returns(true) + sandbox.stub(util, 'existsSync').returns(true) sandbox.stub(tsNode, 'register') }) diff --git a/yarn.lock b/yarn.lock index c273a36ef..88fd204b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -191,9 +191,9 @@ "@babel/types" "^7.22.15" "@babel/traverse@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53" - integrity sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw== + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== dependencies: "@babel/code-frame" "^7.22.13" "@babel/generator" "^7.23.0" @@ -897,13 +897,6 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.3": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== - dependencies: - type-detect "4.0.8" - "@sinonjs/commons@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" @@ -925,22 +918,6 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@sinonjs/fake-timers@^7.0.4", "@sinonjs/fake-timers@^7.1.0", "@sinonjs/fake-timers@^7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" - integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg== - dependencies: - "@sinonjs/commons" "^1.7.0" - -"@sinonjs/samsam@^6.0.2": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-6.0.2.tgz#a0117d823260f282c04bff5f8704bdc2ac6910bb" - integrity sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ== - dependencies: - "@sinonjs/commons" "^1.6.0" - lodash.get "^4.4.2" - type-detect "^4.0.8" - "@sinonjs/samsam@^8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60" @@ -951,9 +928,9 @@ type-detect "^4.0.8" "@sinonjs/text-encoding@^0.7.1": - version "0.7.1" - resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" - integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + version "0.7.2" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918" + integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== "@tootallnate/once@2": version "2.0.0" @@ -1017,12 +994,7 @@ dependencies: "@types/chai" "*" -"@types/chai@*": - version "4.3.4" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.4.tgz#e913e8175db8307d78b4e8fa690408ba6b65dee4" - integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw== - -"@types/chai@^4.3.8": +"@types/chai@*", "@types/chai@^4.3.8": version "4.3.8" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.8.tgz#aa200a264a3bc78ccfc1718eedbd3b9d5a591270" integrity sha512-yW/qTM4mRBBcsA9Xw9FbcImYtFPY7sgr+G/O5RDYVmxiy9a+pE5FyoFUi8JYCZY5nicj8atrr1pcfPiYpeNGOA== @@ -1092,9 +1064,9 @@ integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/lodash@*": - version "4.14.182" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" - integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== + version "4.14.199" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.199.tgz#c3edb5650149d847a277a8961a7ad360c474e9bf" + integrity sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg== "@types/minimist@^1.2.0": version "1.2.2" @@ -1114,9 +1086,11 @@ "@types/node" "*" "@types/node@*": - version "16.18.31" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.31.tgz#7de39c2b9363f0d95b129cc969fcbf98e870251c" - integrity sha512-KPXltf4z4g517OlVJO9XQ2357CYw7fvuJ3ZuBynjXC5Jos9i+K7LvFb7bUIwtJXSZj0vTp9Q6NJBSQpkwwO8Zw== + version "20.8.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.6.tgz#0dbd4ebcc82ad0128df05d0e6f57e05359ee47fa" + integrity sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ== + dependencies: + undici-types "~5.25.1" "@types/node@20.5.1": version "20.5.1" @@ -1139,11 +1113,16 @@ integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw== "@types/sinon@*": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.2.tgz#f360d2f189c0fd433d14aeb97b9d705d7e4cc0e4" - integrity sha512-BHn8Bpkapj8Wdfxvh2jWIUoaYB/9/XhsL0oOvBfRagJtKlSl9NWPcFOz2lRukI9szwGxFtYZCTejJSqsGDbdmw== + version "10.0.19" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.19.tgz#752b752bc40bb5af0bb1aec29bde49b139b91d35" + integrity sha512-MWZNGPSchIdDfb5FL+VFi4zHsHbNOTQEgjqFQk7HazXSXwUU9PAX3z9XBqb3AJGYr9YwrtCtaSMsT3brYsN/jQ== dependencies: - "@sinonjs/fake-timers" "^7.1.0" + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "8.1.3" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.3.tgz#575789c5cf6d410cb288b0b4affaf7e6da44ca51" + integrity sha512-4g+2YyWe0Ve+LBh+WUm1697PD0Kdi6coG1eU0YjQbwx61AZ8XbEpL1zIT6WjuUKrCMCROpEaYQPDjBnDouBVAQ== "@types/slice-ansi@^4.0.0": version "4.0.0" @@ -2523,7 +2502,7 @@ diff-sequences@^29.6.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== -diff@5.0.0, diff@^5.0.0: +diff@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== @@ -4112,7 +4091,7 @@ is-wsl@^2.2.0: isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== isarray@^2.0.5: version "2.0.5" @@ -4291,7 +4270,7 @@ json-stringify-nice@^1.1.4: json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== json5@^1.0.2: version "1.0.2" @@ -4536,7 +4515,7 @@ lodash.flattendeep@^4.4.0: lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== lodash.isfunction@^3.0.9: version "3.0.9" @@ -5025,17 +5004,6 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -nise@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.0.tgz#713ef3ed138252daef20ec035ab62b7a28be645c" - integrity sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ== - dependencies: - "@sinonjs/commons" "^1.7.0" - "@sinonjs/fake-timers" "^7.0.4" - "@sinonjs/text-encoding" "^0.7.1" - just-extend "^4.0.2" - path-to-regexp "^1.7.0" - nise@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.4.tgz#491ce7e7307d4ec546f5a659b2efe94a18b4bbc0" @@ -5048,9 +5016,9 @@ nise@^5.1.4: path-to-regexp "^1.7.0" nock@^13.3.3: - version "13.3.3" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.3.3.tgz#179759c07d3f88ad3e794ace885629c1adfd3fe7" - integrity sha512-z+KUlILy9SK/RjpeXDiDUEAq4T94ADPHE3qaRkf66mpEhzc/ytOMm3Bwdrbq6k1tMWkbdujiKim3G2tfQARuJw== + version "13.3.4" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.3.4.tgz#4ed3ed1465a75c87833044a881dbdd6546337e8d" + integrity sha512-DDpmn5oLEdCTclEqweOT4U7bEpuoifBMFUXem9sA4turDAZ5tlbrEoWqCorwXey8CaAw44mst5JOQeVNiwtkhw== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" @@ -6304,22 +6272,10 @@ sigstore@^1.3.0, sigstore@^1.4.0, sigstore@^1.7.0: "@sigstore/tuf" "^1.0.3" make-fetch-happen "^11.0.1" -sinon@^11.1.2: - version "11.1.2" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-11.1.2.tgz#9e78850c747241d5c59d1614d8f9cbe8840e8674" - integrity sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw== - dependencies: - "@sinonjs/commons" "^1.8.3" - "@sinonjs/fake-timers" "^7.1.2" - "@sinonjs/samsam" "^6.0.2" - diff "^5.0.0" - nise "^5.1.0" - supports-color "^7.2.0" - -sinon@^16.0.0: - version "16.0.0" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-16.0.0.tgz#06da4e63624b946c9d7e67cce21c2f67f40f23a9" - integrity sha512-B8AaZZm9CT5pqe4l4uWJztfD/mOTa7dL8Qo0W4+s+t74xECOgSZDDQCBjNgIK3+n4kyxQrSTv2V5ul8K25qkiQ== +sinon@^16.0.0, sinon@^16.1.0: + version "16.1.0" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-16.1.0.tgz#645b836563c9bedb21defdbe48831cb2afb687f2" + integrity sha512-ZSgzF0vwmoa8pq0GEynqfdnpEDyP1PkYmEChnkjW0Vyh8IDlyFEJ+fkMhCP0il6d5cJjPl2PUsnUSAuP5sttOQ== dependencies: "@sinonjs/commons" "^3.0.0" "@sinonjs/fake-timers" "^10.3.0" @@ -6908,6 +6864,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~5.25.1: + version "5.25.3" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" + integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== + uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" From f14181e071f8c5d7f93646eef2e8d35b39dde0a7 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 18 Oct 2023 13:29:57 -0600 Subject: [PATCH 4/5] fix: add pluginPrefix type --- src/interfaces/pjson.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/interfaces/pjson.ts b/src/interfaces/pjson.ts index 976c5bfc4..204356acc 100644 --- a/src/interfaces/pjson.ts +++ b/src/interfaces/pjson.ts @@ -99,6 +99,7 @@ export namespace PJSON { nsisCustomization?: string schema?: number scope?: string + pluginPrefix?: string 'warn-if-update-available'?: { authorization: string message: string From 3059f5d24199f8927c3f46961dcf4c34e1f31483 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 18 Oct 2023 15:17:28 -0600 Subject: [PATCH 5/5] chore: update plugin interface --- src/interfaces/plugin.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/interfaces/plugin.ts b/src/interfaces/plugin.ts index d374e0fd9..7917429b7 100644 --- a/src/interfaces/plugin.ts +++ b/src/interfaces/plugin.ts @@ -56,6 +56,7 @@ export interface Plugin { * name from package.json */ name: string + parent?: Plugin /** * full package.json * @@ -71,8 +72,8 @@ export interface Plugin { * only used for user plugins */ tag?: string - readonly topics: Topic[] + readonly topics: Topic[] /** * used to tell the user how the plugin was installed * examples: core, link, user, dev @@ -82,6 +83,7 @@ export interface Plugin { * if it appears to be an npm package but does not look like it's really a CLI plugin, this is set to false */ valid: boolean + /** * version from package.json *