From d1dcc8a60ec39879b30f23c036e66802f9a11488 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 15 May 2024 10:31:46 -0600 Subject: [PATCH 01/14] chore: bump major version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89dfc9e..cc58cfe 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/test", "description": "test helpers for oclif components", - "version": "3.2.15", + "version": "4.0.0", "author": "Salesforce", "bugs": "https://github.com/oclif/test/issues", "dependencies": { From 006c90c33c1a6891148189227bdc27a0a271dc6f Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 15 May 2024 10:39:29 -0600 Subject: [PATCH 02/14] feat: new oclif test utilities --- package.json | 12 +- src/command.ts | 30 ---- src/exit.ts | 22 --- src/hook.ts | 32 ---- src/index.ts | 173 +++++++++++++++--- src/load-config.ts | 26 --- yarn.lock | 423 ++++++--------------------------------------- 7 files changed, 207 insertions(+), 511 deletions(-) delete mode 100644 src/command.ts delete mode 100644 src/exit.ts delete mode 100644 src/hook.ts delete mode 100644 src/load-config.ts diff --git a/package.json b/package.json index cc58cfe..e594b28 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,17 @@ "author": "Salesforce", "bugs": "https://github.com/oclif/test/issues", "dependencies": { - "@oclif/core": "^3.26.6", - "chai": "^4.4.1", - "fancy-test": "^3.0.15" + "ansis": "^3.2.0", + "debug": "^4.3.4" + }, + "peerDependencies": { + "@oclif/core": "^4.0.0-beta.6" }, "devDependencies": { "@commitlint/config-conventional": "^18.6.3", + "@oclif/core": "^4.0.0-beta.6", "@oclif/prettier-config": "^0.2.1", - "@types/cli-progress": "^3.11.5", + "@types/debug": "^4.1.12", "@types/mocha": "^10", "@types/node": "^18", "commitlint": "^18.6.1", @@ -23,7 +26,6 @@ "husky": "^9.0.3", "lint-staged": "^15.2.2", "mocha": "^10", - "nock": "^13.5.4", "prettier": "^3.2.5", "shx": "^0.3.3", "ts-node": "^10.9.2", diff --git a/src/command.ts b/src/command.ts deleted file mode 100644 index 34cc19a..0000000 --- a/src/command.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {Interfaces, toStandardizedId} from '@oclif/core' - -import {loadConfig} from './load-config' - -const castArray = (input?: T | T[]): T[] => { - if (input === undefined) return [] - return Array.isArray(input) ? input : [input] -} - -type Context = {config: Interfaces.Config; expectation: string; returned: unknown} - -export function command( - args: string | string[], - opts: loadConfig.Options = {}, -): { - run(ctx: Context): Promise -} { - return { - async run(ctx: Context) { - if (!ctx.config || opts.reset) ctx.config = await loadConfig(opts).run({} as Context) - args = castArray(args) - const [id, ...extra] = args - let cmdId = toStandardizedId(id, ctx.config) - if (cmdId === '.') cmdId = Symbol('SINGLE_COMMAND_CLI').toString() - ctx.expectation ||= `runs ${args.join(' ')}` - await ctx.config.runHook('init', {argv: extra, id: cmdId}) - ctx.returned = await ctx.config.runCommand(cmdId, extra) - }, - } -} diff --git a/src/exit.ts b/src/exit.ts deleted file mode 100644 index 3a9145d..0000000 --- a/src/exit.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {Errors} from '@oclif/core' -import {expect} from 'chai' - -// eslint-disable-next-line valid-jsdoc -/** - * ensures that a oclif command or hook exits - */ -export default ( - code = 0, -): { - catch(ctx: {error: Errors.CLIError}): void - run(): never -} => ({ - catch(ctx: {error: Errors.CLIError}) { - if (!ctx.error.oclif || ctx.error.oclif.exit === undefined) throw ctx.error - expect(ctx.error.oclif.exit).to.equal(code) - }, - run() { - expect(process.exitCode).to.equal(code) - throw new Error(`Expected to exit with code ${code} but it ran without exiting`) - }, -}) diff --git a/src/hook.ts b/src/hook.ts deleted file mode 100644 index 7539983..0000000 --- a/src/hook.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {Interfaces} from '@oclif/core' - -import {loadConfig} from './load-config' - -type Context = {config: Interfaces.Config; expectation: string; returned: unknown} - -// eslint-disable-next-line valid-jsdoc -/** - * tests a oclif hook - * - * @example check that when the 'init' hook is ran it outputs "this output" - * testHook('init', {id: 'mycommand'}, {stdout: true}, output => { - * expect(output.stdout).to.contain('this output') - * }) - * - * @param {string} event hook to run - * @param {object} hookOpts options to pass to hook. Config object will be passed automatically. - */ -export default ( - event: string, - hookOpts: Record = {}, - options: loadConfig.Options = {}, -): { - run(ctx: Context): Promise -} => ({ - async run(ctx: Context) { - if (!event) throw new Error('no hook provided') - ctx.config ||= await loadConfig(options).run({} as Context) - ctx.expectation ||= `runs ${event} hook` - ctx.returned = await ctx.config.runHook(event, hookOpts || {}) - }, -}) diff --git a/src/index.ts b/src/index.ts index 123a665..f4d2328 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,78 @@ -import * as fancyTest from 'fancy-test' +import {Config, Errors, Interfaces, run} from '@oclif/core' +import ansis from 'ansis' +import makeDebug from 'debug' import {dirname} from 'node:path' -import {command} from './command' -import exit from './exit' -import hook from './hook' -import {loadConfig} from './load-config' +const debug = makeDebug('test') + +type CaptureOptions = { + print?: boolean + stripAnsi?: boolean +} + +const RECORD_OPTIONS: CaptureOptions = { + print: false, + stripAnsi: true, +} + +const originals = { + stderr: process.stderr.write, + stdout: process.stdout.write, +} + +const output: Record<'stderr' | 'stdout', Array> = { + stderr: [], + stdout: [], +} + +function mockedStdout(str: Uint8Array | string, cb?: (err?: Error) => void): boolean +function mockedStdout(str: Uint8Array | string, encoding?: BufferEncoding, cb?: (err?: Error) => void): boolean +function mockedStdout( + str: Uint8Array | string, + encoding?: ((err?: Error) => void) | BufferEncoding, + cb?: (err?: Error) => void, +): boolean { + output.stdout.push(str) + if (!RECORD_OPTIONS.print) return true + + if (typeof encoding === 'string') { + return originals.stdout.bind(process.stdout)(str, encoding, cb) + } + + return originals.stdout.bind(process.stdout)(str, cb) +} + +function mockedStderr(str: Uint8Array | string, cb?: (err?: Error) => void): boolean +function mockedStderr(str: Uint8Array | string, encoding?: BufferEncoding, cb?: (err?: Error) => void): boolean +function mockedStderr( + str: Uint8Array | string, + encoding?: ((err?: Error) => void) | BufferEncoding, + cb?: (err?: Error) => void, +): boolean { + output.stderr.push(str) + if (!RECORD_OPTIONS.print) return true + if (typeof encoding === 'string') { + return originals.stdout.bind(process.stderr)(str, encoding, cb) + } + + return originals.stdout.bind(process.stderr)(str, cb) +} + +const restore = (): void => { + process.stderr.write = originals.stderr + process.stdout.write = originals.stdout +} + +const reset = (): void => { + output.stderr = [] + output.stdout = [] +} + +const toString = (str: Uint8Array | string): string => + RECORD_OPTIONS.stripAnsi ? ansis.strip(str.toString()) : str.toString() + +const getStderr = (): string => output.stderr.map((b) => toString(b)).join('') +const getStdout = (): string => output.stdout.map((b) => toString(b)).join('') function traverseFilePathUntil(filename: string, predicate: (filename: string) => boolean): string { let current = filename @@ -15,27 +83,84 @@ function traverseFilePathUntil(filename: string, predicate: (filename: string) = return current } -/* eslint-disable unicorn/prefer-module */ -loadConfig.root = - process.env.OCLIF_TEST_ROOT ?? - Object.values(require.cache).find((m) => m?.children.includes(module))?.filename ?? - traverseFilePathUntil( - require.main?.path ?? module.path, - (p) => !(p.includes('node_modules') || p.includes('.pnpm') || p.includes('.yarn')), +function makeLoadOptions(loadOpts?: Interfaces.LoadOptions): Interfaces.LoadOptions { + return ( + loadOpts ?? { + root: traverseFilePathUntil( + // eslint-disable-next-line unicorn/prefer-module + require.main?.path ?? module.path, + (p) => !(p.includes('node_modules') || p.includes('.pnpm') || p.includes('.yarn')), + ), + } ) -/* eslint-enable unicorn/prefer-module */ +} -// Using a named export to import fancy causes this issue: https://github.com/oclif/test/issues/516 -export const test = fancyTest.fancy - .register('loadConfig', loadConfig) - .register('command', command) - .register('exit', exit) - .register('hook', hook) - .env({NODE_ENV: 'test'}) +export async function captureOutput( + fn: () => Promise, + opts?: CaptureOptions, +): Promise<{ + error?: Error & Partial + result?: T + stderr: string + stdout: string +}> { + RECORD_OPTIONS.print = opts?.print ?? false + RECORD_OPTIONS.stripAnsi = opts?.stripAnsi ?? true + process.stderr.write = mockedStderr + process.stdout.write = mockedStdout -export default test + try { + const result = await fn() + return { + result: result as T, + stderr: getStderr(), + stdout: getStdout(), + } + } catch (error) { + return { + ...(error instanceof Errors.CLIError && {error}), + ...(error instanceof Error && {error}), + stderr: getStderr(), + stdout: getStdout(), + } + } finally { + restore() + reset() + } +} -export {command} from './command' +export async function runCommand( + args: string[], + loadOpts?: Interfaces.LoadOptions, + captureOpts?: CaptureOptions, +): Promise<{ + error?: Error & Partial + result?: T + stderr: string + stdout: string +}> { + const loadOptions = makeLoadOptions(loadOpts) + debug('loadOpts: %O', loadOpts) + return captureOutput(async () => run(args, loadOptions), captureOpts) +} -export {Config} from '@oclif/core' -export {FancyTypes, expect} from 'fancy-test' +export async function runHook( + hook: string, + options: Record, + loadOpts?: Interfaces.LoadOptions, + recordOpts?: CaptureOptions, +): Promise<{ + error?: Error & Partial + result?: T + stderr: string + stdout: string +}> { + const loadOptions = makeLoadOptions(loadOpts) + + debug('loadOpts: %O', loadOpts) + + return captureOutput(async () => { + const config = await Config.load(loadOptions) + return config.runHook(hook, options) + }, recordOpts) +} diff --git a/src/load-config.ts b/src/load-config.ts deleted file mode 100644 index 1690bb9..0000000 --- a/src/load-config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {Config, Interfaces} from '@oclif/core' - -/** - * loads CLI plugin/multi config - * @param {loadConfig.Options} opts options - * @return {Promise} config - */ -export function loadConfig(opts: loadConfig.Options = {}): { - run(ctx: {config: Interfaces.Config}): Promise -} { - return { - async run(ctx: {config: Interfaces.Config}) { - ctx.config = await Config.load(opts.root || loadConfig.root) - return ctx.config - }, - } -} - -// eslint-disable-next-line @typescript-eslint/no-namespace -export namespace loadConfig { - export let root: string - export interface Options { - reset?: boolean - root?: string - } -} diff --git a/yarn.lock b/yarn.lock index 28a6a2f..cbd72d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -283,36 +283,25 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@oclif/core@^3.26.6": - version "3.26.6" - resolved "https://registry.yarnpkg.com/@oclif/core/-/core-3.26.6.tgz#f371868cfa0fe150a6547e6af98b359065d2f971" - integrity sha512-+FiTw1IPuJTF9tSAlTsY8bGK4sgthehjz7c2SvYdgQncTkxI2xvUch/8QpjNYGLEmUneNygvYMRBax2KJcLccA== +"@oclif/core@^4.0.0-beta.6": + version "4.0.0-beta.6" + resolved "https://registry.yarnpkg.com/@oclif/core/-/core-4.0.0-beta.6.tgz#43cc1114d502ba27b95fd52db7870d5bccf949cd" + integrity sha512-FI1eGHBJzHR1EAWJlydRUuP1Mpb2k1nI+ANRwl1KCLmdA5+zd+J0qaQpcQnItuwzcrJaMNxx92vqOTF3bp882g== dependencies: - "@types/cli-progress" "^3.11.5" ansi-escapes "^4.3.2" - ansi-styles "^4.3.0" - cardinal "^2.1.1" - chalk "^4.1.2" + ansis "^3.0.1" clean-stack "^3.0.1" - cli-progress "^3.12.0" - color "^4.2.3" + cli-spinners "^2.9.2" + cosmiconfig "^9.0.0" debug "^4.3.4" ejs "^3.1.10" get-package-type "^0.1.0" globby "^11.1.0" - hyperlinker "^1.0.0" indent-string "^4.0.0" is-wsl "^2.2.0" - js-yaml "^3.14.1" minimatch "^9.0.4" - natural-orderby "^2.0.3" - object-treeify "^1.1.33" - password-prompt "^1.1.3" - slice-ansi "^4.0.0" string-width "^4.2.3" - strip-ansi "^6.0.1" - supports-color "^8.1.1" - supports-hyperlinks "^2.2.0" + supports-color "^9.4.0" widest-line "^3.1.0" wordwrap "^1.0.0" wrap-ansi "^7.0.0" @@ -322,41 +311,6 @@ resolved "https://registry.yarnpkg.com/@oclif/prettier-config/-/prettier-config-0.2.1.tgz#1def9f38134f9bfb229257f48a35f7d0d183dc78" integrity sha512-XB8kwQj8zynXjIIWRm+6gO/r8Qft2xKtwBMSmq1JRqtA6TpwpqECqiu8LosBCyg2JBXuUy2lU23/L98KIR7FrQ== -"@sinonjs/commons@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" - integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== - dependencies: - type-detect "4.0.8" - -"@sinonjs/commons@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" - integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2", "@sinonjs/fake-timers@^10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@sinonjs/samsam@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60" - integrity sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew== - dependencies: - "@sinonjs/commons" "^2.0.0" - lodash.get "^4.4.2" - type-detect "^4.0.8" - -"@sinonjs/text-encoding@^0.7.1": - 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== - "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -377,17 +331,12 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== -"@types/chai@*": - version "4.3.9" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.9.tgz#144d762491967db8c6dea38e03d2206c2623feec" - integrity sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg== - -"@types/cli-progress@^3.11.5": - version "3.11.5" - resolved "https://registry.yarnpkg.com/@types/cli-progress/-/cli-progress-3.11.5.tgz#9518c745e78557efda057e3f96a5990c717268c3" - integrity sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g== +"@types/debug@^4.1.12": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== dependencies: - "@types/node" "*" + "@types/ms" "*" "@types/json-schema@^7.0.12": version "7.0.13" @@ -404,11 +353,6 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/lodash@*": - version "4.14.200" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.200.tgz#435b6035c7eba9cdf1e039af8212c9e9281e7149" - integrity sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q== - "@types/minimist@^1.2.0": version "1.2.4" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.4.tgz#81f886786411c45bba3f33e781ab48bd56bfca2e" @@ -419,12 +363,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.6.tgz#818551d39113081048bdddbef96701b4e8bb9d1b" integrity sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg== -"@types/node@*": - version "20.8.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.8.tgz#adee050b422061ad5255fc38ff71b2bb96ea2a0e" - integrity sha512-YRsdVxq6OaLfmR9Hy816IMp33xOBjfyOgUd77ehqg96CFywxAPbDbXvAsuN2KVg2HOT8Eh6uAfU+l4WffwPVrQ== - dependencies: - undici-types "~5.25.1" +"@types/ms@*": + version "0.7.34" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" + integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== "@types/node@^18": version "18.19.33" @@ -448,18 +390,6 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== -"@types/sinon@*": - version "10.0.20" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.20.tgz#f1585debf4c0d99f9938f4111e5479fb74865146" - integrity sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg== - dependencies: - "@types/sinonjs__fake-timers" "*" - -"@types/sinonjs__fake-timers@*": - version "8.1.4" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.4.tgz#663bb44e01f6bae4eaae3719d8b037411217c992" - integrity sha512-GDV68H0mBSN449sa5HEj51E0wfpVQb8xNSMzxf/PrypMFcLTMwJMOM/cgXiv71Mq5drkOQmUGvL1okOZcu6RrQ== - "@typescript-eslint/eslint-plugin@^6.21.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" @@ -678,7 +608,7 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.3.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -690,10 +620,10 @@ ansi-styles@^6.0.0, ansi-styles@^6.2.1: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -ansicolors@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" - integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= +ansis@^3.0.1, ansis@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.2.0.tgz#0e050c5be94784f32ffdac4b84fccba064aeae4b" + integrity sha512-Yk3BkHH9U7oPyCN3gL5Tc7CpahG/+UFv/6UG03C311Vy9lzRmA5uoxDTpU9CO3rGHL6KzJz/pdDeXZCZ5Mu/Sg== anymatch@~3.1.2: version "3.1.2" @@ -708,13 +638,6 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -798,16 +721,6 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - async@^3.2.3: version "3.2.4" resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" @@ -899,27 +812,6 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -cardinal@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" - integrity sha1-fMEFXYItISlU0HsIXeolHMe8VQU= - dependencies: - ansicolors "~0.3.2" - redeyed "~2.1.0" - -chai@^4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" - integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.3" - deep-eql "^4.1.3" - get-func-name "^2.0.2" - loupe "^2.3.6" - pathval "^1.1.1" - type-detect "^4.0.8" - chalk@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" @@ -934,7 +826,7 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -942,13 +834,6 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -check-error@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" - integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== - dependencies: - get-func-name "^2.0.2" - chokidar@3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -990,12 +875,10 @@ cli-cursor@^4.0.0: dependencies: restore-cursor "^4.0.0" -cli-progress@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" - integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A== - dependencies: - string-width "^4.2.3" +cli-spinners@^2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== cli-truncate@^4.0.0: version "4.0.0" @@ -1042,27 +925,11 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" - integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== - dependencies: - color-convert "^2.0.1" - color-string "^1.9.0" - colorette@^2.0.20: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" @@ -1140,6 +1007,16 @@ cosmiconfig@^8.3.6: parse-json "^5.2.0" path-type "^4.0.0" +cosmiconfig@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" + integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== + dependencies: + env-paths "^2.2.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -1159,7 +1036,7 @@ dargs@^7.0.0: resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== -debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4.3.4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -1191,13 +1068,6 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -deep-eql@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" - integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== - dependencies: - type-detect "^4.0.0" - deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -1231,11 +1101,6 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -diff@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" - integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -1289,6 +1154,11 @@ enhanced-resolve@^5.12.0: graceful-fs "^4.2.4" tapable "^2.2.0" +env-paths@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -1627,11 +1497,6 @@ espree@^9.6.0, espree@^9.6.1: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" -esprima@^4.0.0, esprima@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - esquery@^1.4.2, esquery@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" @@ -1691,21 +1556,6 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -fancy-test@^3.0.15: - version "3.0.15" - resolved "https://registry.yarnpkg.com/fancy-test/-/fancy-test-3.0.15.tgz#b83b44f26a558e76e1acd62a15702b863551f810" - integrity sha512-kZZFdPzVWtKxBkmLLUfxhizseGl1ZZRuCmmuCko1oiAs/X/MvE4ACm/vlzvxfu4Oeb/7MJ8hVP8rUiVm3R729Q== - dependencies: - "@types/chai" "*" - "@types/lodash" "*" - "@types/node" "*" - "@types/sinon" "*" - lodash "^4.17.13" - mock-stdin "^1.0.0" - nock "^13.5.4" - sinon "^16.1.3" - stdout-stderr "^0.1.9" - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1846,11 +1696,6 @@ get-east-asian-width@^1.0.0: resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e" integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA== -get-func-name@^2.0.0, get-func-name@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" - integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== - get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" @@ -2086,11 +1931,6 @@ husky@^9.0.3: resolved "https://registry.yarnpkg.com/husky/-/husky-9.0.11.tgz#fc91df4c756050de41b3e478b2158b87c1e79af9" integrity sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw== -hyperlinker@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" - integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ== - ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" @@ -2160,11 +2000,6 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -2363,11 +2198,6 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" @@ -2405,14 +2235,6 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js-yaml@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - jsesc@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" @@ -2443,11 +2265,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -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 sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -2460,11 +2277,6 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -just-extend@^4.0.2: - version "4.2.1" - resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" - integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== - kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -2535,11 +2347,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== - lodash.isfunction@^3.0.9: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" @@ -2585,7 +2392,7 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.21: +lodash@^4.17.15, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -2609,13 +2416,6 @@ log-update@^6.0.0: strip-ansi "^7.1.0" wrap-ansi "^9.0.0" -loupe@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.6.tgz#76e4af498103c532d1ecc9be102036a21f787b53" - integrity sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA== - dependencies: - get-func-name "^2.0.0" - lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -2768,11 +2568,6 @@ mocha@^10: yargs-parser "20.2.4" yargs-unparser "2.0.0" -mock-stdin@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mock-stdin/-/mock-stdin-1.0.0.tgz#efcfaf4b18077e14541742fd758b9cae4e5365ea" - integrity sha512-tukRdb9Beu27t6dN+XztSRHq9J0B/CoAOySGzHfn8UTfmqipA5yNT/sDUEyYdAV3Hpka6Wx6kOMxuObdOex60Q== - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -2793,31 +2588,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -natural-orderby@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz#8623bc518ba162f8ff1cdb8941d74deb0fdcc016" - integrity sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q== - -nise@^5.1.4: - version "5.1.5" - resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.5.tgz#f2aef9536280b6c18940e32ba1fbdc770b8964ee" - integrity sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw== - dependencies: - "@sinonjs/commons" "^2.0.0" - "@sinonjs/fake-timers" "^10.0.2" - "@sinonjs/text-encoding" "^0.7.1" - just-extend "^4.0.2" - path-to-regexp "^1.7.0" - -nock@^13.5.4: - version "13.5.4" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.4.tgz#8918f0addc70a63736170fef7106a9721e0dc479" - integrity sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw== - dependencies: - debug "^4.1.0" - json-stringify-safe "^5.0.1" - propagate "^2.0.0" - normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -2867,11 +2637,6 @@ object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-treeify@^1.1.33: - version "1.1.33" - resolved "https://registry.yarnpkg.com/object-treeify/-/object-treeify-1.1.33.tgz#f06fece986830a3cba78ddd32d4c11d1f76cdf40" - integrity sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A== - object.assign@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" @@ -2993,14 +2758,6 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -password-prompt@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.3.tgz#05e539f4e7ca4d6c865d479313f10eb9db63ee5f" - integrity sha512-HkrjG2aJlvF0t2BMH0e2LB/EHf3Lcq3fNMzy4GYHcQblAvOl+QQji1Lx7WRBMqpVK8p+KR7bCg7oqAMXtdgqyw== - dependencies: - ansi-escapes "^4.3.2" - cross-spawn "^7.0.3" - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -3026,23 +2783,11 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pathval@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" - integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== - picomatch@^2.0.4, picomatch@^2.2.1: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -3073,11 +2818,6 @@ prettier@^3.2.5: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== -propagate@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" - integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== - punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -3155,13 +2895,6 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -redeyed@~2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" - integrity sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs= - dependencies: - esprima "~4.0.0" - regexp-tree@^0.1.27: version "0.1.27" resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" @@ -3382,39 +3115,11 @@ signal-exit@^4.1.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -sinon@^16.1.3: - version "16.1.3" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-16.1.3.tgz#b760ddafe785356e2847502657b4a0da5501fba8" - integrity sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA== - dependencies: - "@sinonjs/commons" "^3.0.0" - "@sinonjs/fake-timers" "^10.3.0" - "@sinonjs/samsam" "^8.0.0" - diff "^5.1.0" - nise "^5.1.4" - supports-color "^7.2.0" - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - slice-ansi@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" @@ -3469,19 +3174,6 @@ split2@^4.0.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -stdout-stderr@^0.1.9: - version "0.1.13" - resolved "https://registry.yarnpkg.com/stdout-stderr/-/stdout-stderr-0.1.13.tgz#54e3450f3d4c54086a49c0c7f8786a44d1844b6f" - integrity sha512-Xnt9/HHHYfjZ7NeQLvuQDyL1LnbsbddgMFKCuaQKwGCdJm8LnstZIXop+uOY36UR1UXXoHXfMbC1KlVdVd2JLA== - dependencies: - debug "^4.1.1" - strip-ansi "^6.0.0" - string-argv@0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" @@ -3580,7 +3272,7 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@8.1.1, supports-color@^8.1.1: +supports-color@8.1.1: version "8.1.1" resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -3594,20 +3286,17 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" +supports-color@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.4.0.tgz#17bfcf686288f531db3dea3215510621ccb55954" + integrity sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw== supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" @@ -3699,11 +3388,6 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - type-fest@^0.18.0: version "0.18.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" @@ -3788,11 +3472,6 @@ 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== - undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" From 5fa48c2a03cefaa8d8f4f8abc4f8e2d113a1f69a Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Wed, 15 May 2024 16:39:59 +0000 Subject: [PATCH 03/14] chore(release): 4.0.1-dev.0 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e594b28..91578b8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/test", "description": "test helpers for oclif components", - "version": "4.0.0", + "version": "4.0.1-dev.0", "author": "Salesforce", "bugs": "https://github.com/oclif/test/issues", "dependencies": { From 5e9bbcd2feb24e931463b92901646142d9614a0f Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 15 May 2024 12:01:08 -0600 Subject: [PATCH 04/14] test: get tests passing --- .mocharc.json | 10 +- package.json | 4 +- src/index.ts | 34 ++++--- test/command.test.ts | 57 +++++------- test/exit.test.ts | 16 ++-- test/helpers/init.js | 2 - test/hook.test.ts | 13 ++- test/index.test.ts | 80 ++++++++-------- yarn.lock | 214 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 321 insertions(+), 109 deletions(-) delete mode 100644 test/helpers/init.js diff --git a/.mocharc.json b/.mocharc.json index d233271..d15df15 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,12 +1,6 @@ { - "require": [ - "test/helpers/init.js", - "ts-node/register", - "source-map-support/register" - ], - "watch-extensions": [ - "ts" - ], + "import": ["tsx"], + "watch-extensions": ["ts"], "recursive": true, "reporter": "spec", "timeout": 5000 diff --git a/package.json b/package.json index 91578b8..1b38d1b 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,11 @@ "@commitlint/config-conventional": "^18.6.3", "@oclif/core": "^4.0.0-beta.6", "@oclif/prettier-config": "^0.2.1", + "@types/chai": "^4.3.16", "@types/debug": "^4.1.12", "@types/mocha": "^10", "@types/node": "^18", + "chai": "^5.1.1", "commitlint": "^18.6.1", "eslint": "^8.57.0", "eslint-config-oclif": "^5.2.0", @@ -28,7 +30,7 @@ "mocha": "^10", "prettier": "^3.2.5", "shx": "^0.3.3", - "ts-node": "^10.9.2", + "tsx": "^4.10.2", "typescript": "^5.4.5" }, "engines": { diff --git a/src/index.ts b/src/index.ts index f4d2328..9bd63cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import ansis from 'ansis' import makeDebug from 'debug' import {dirname} from 'node:path' -const debug = makeDebug('test') +const debug = makeDebug('oclif-test') type CaptureOptions = { print?: boolean @@ -83,18 +83,23 @@ function traverseFilePathUntil(filename: string, predicate: (filename: string) = return current } -function makeLoadOptions(loadOpts?: Interfaces.LoadOptions): Interfaces.LoadOptions { +function findRoot(): string { return ( - loadOpts ?? { - root: traverseFilePathUntil( - // eslint-disable-next-line unicorn/prefer-module - require.main?.path ?? module.path, - (p) => !(p.includes('node_modules') || p.includes('.pnpm') || p.includes('.yarn')), - ), - } + process.env.OCLIF_TEST_ROOT ?? + // eslint-disable-next-line unicorn/prefer-module + Object.values(require.cache).find((m) => m?.children.includes(module))?.filename ?? + traverseFilePathUntil( + // eslint-disable-next-line unicorn/prefer-module + require.main?.path ?? module.path, + (p) => !(p.includes('node_modules') || p.includes('.pnpm') || p.includes('.yarn')), + ) ) } +function makeLoadOptions(loadOpts?: Interfaces.LoadOptions): Interfaces.LoadOptions { + return loadOpts ?? {root: findRoot()} +} + export async function captureOutput( fn: () => Promise, opts?: CaptureOptions, @@ -130,7 +135,7 @@ export async function captureOutput( } export async function runCommand( - args: string[], + args: string | string[], loadOpts?: Interfaces.LoadOptions, captureOpts?: CaptureOptions, ): Promise<{ @@ -140,8 +145,15 @@ export async function runCommand( stdout: string }> { const loadOptions = makeLoadOptions(loadOpts) + const argsArray = (Array.isArray(args) ? args : [args]).join(' ').split(' ') + + const [id, ...rest] = argsArray + const finalArgs = id === '.' ? rest : argsArray + debug('loadOpts: %O', loadOpts) - return captureOutput(async () => run(args, loadOptions), captureOpts) + debug('args: %O', finalArgs) + + return captureOutput(async () => run(finalArgs, loadOptions), captureOpts) } export async function runHook( diff --git a/test/command.test.ts b/test/command.test.ts index da8375f..9221071 100644 --- a/test/command.test.ts +++ b/test/command.test.ts @@ -1,47 +1,38 @@ +import {expect} from 'chai' import {join} from 'node:path' -import {expect, test} from '../src' +import {runCommand} from '../src' describe('command', () => { // eslint-disable-next-line unicorn/prefer-module const root = join(__dirname, 'fixtures/multi') - test - .loadConfig({root}) - .stdout() - .command(['foo:bar']) - .do((output) => { - expect(output.stdout).to.equal('hello world!\n') - const {name} = output.returned as {name: string} - expect(name).to.equal('world') - }) - .it() - test - .loadConfig({root}) - .stdout() - .command(['foo:bar', '--name=foo']) - .do((output) => expect(output.stdout).to.equal('hello foo!\n')) - .it() + it('should run a command', async () => { + const {result, stdout} = await runCommand<{name: string}>(['foo:bar'], {root}) + expect(stdout).to.equal('hello world!\n') + expect(result?.name).to.equal('world') + }) - test - .loadConfig({root}) - .stdout() - .command(['foo bar', '--name=foo']) - .do((output) => expect(output.stdout).to.equal('hello foo!\n')) - .it() + it('should run a command with a flag', async () => { + const {result, stdout} = await runCommand<{name: string}>(['foo:bar', '--name=foo'], {root}) + expect(stdout).to.equal('hello foo!\n') + expect(result?.name).to.equal('foo') + }) + + it('should run a command using spaces', async () => { + const {result, stdout} = await runCommand<{name: string}>(['foo bar', '--name=foo'], {root}) + expect(stdout).to.equal('hello foo!\n') + expect(result?.name).to.equal('foo') + }) }) describe('single command cli', () => { // eslint-disable-next-line unicorn/prefer-module const root = join(__dirname, 'fixtures/single') - test - .loadConfig({root}) - .stdout() - .command(['.']) - .do((output) => { - expect(output.stdout).to.equal('hello world!\n') - const {name} = output.returned as {name: string} - expect(name).to.equal('world') - }) - .it() + + it('should run a single command cli', async () => { + const {result, stdout} = await runCommand<{name: string}>(['.'], {root}) + expect(stdout).to.equal('hello world!\n') + expect(result?.name).to.equal('world') + }) }) diff --git a/test/exit.test.ts b/test/exit.test.ts index 1e3053e..3762d2c 100644 --- a/test/exit.test.ts +++ b/test/exit.test.ts @@ -1,16 +1,16 @@ +import {expect} from 'chai' import {join} from 'node:path' -import {expect, test} from '../src' +import {runCommand} from '../src' // eslint-disable-next-line unicorn/prefer-module const root = join(__dirname, 'fixtures/multi') describe('exit', () => { - test - .loadConfig({root}) - .stdout() - .command(['exit', '--code=101']) - .exit(101) - .do(output => expect(output.stdout).to.equal('exiting with code 101\n')) - .it() + it('should handle expected exit codes', async () => { + const {error, stdout} = await runCommand(['exit', '--code=101'], {root}) + expect(stdout).to.equal('exiting with code 101\n') + expect(error?.message).to.equal('EEXIT: 101') + expect(error?.oclif?.exit).to.equal(101) + }) }) diff --git a/test/helpers/init.js b/test/helpers/init.js deleted file mode 100644 index f8e35e6..0000000 --- a/test/helpers/init.js +++ /dev/null @@ -1,2 +0,0 @@ -const path = require('path') -process.env.TS_NODE_PROJECT = path.resolve('test/tsconfig.json') diff --git a/test/hook.test.ts b/test/hook.test.ts index 3c7b8a6..2f62ddb 100644 --- a/test/hook.test.ts +++ b/test/hook.test.ts @@ -1,15 +1,14 @@ +import {expect} from 'chai' import {join} from 'node:path' -import {expect, test} from '../src' +import {runHook} from '../src' // eslint-disable-next-line unicorn/prefer-module const root = join(__dirname, 'fixtures/multi') describe('hooks', () => { - test - .loadConfig({root}) - .stdout() - .hook('foo', {argv: ['arg']}, {root}) - .do(output => expect(output.stdout).to.equal('foo hook args: arg\n')) - .it() + it('should run a hook', async () => { + const {stdout} = await runHook('foo', {argv: ['arg']}, {root}) + expect(stdout).to.equal('foo hook args: arg\n') + }) }) diff --git a/test/index.test.ts b/test/index.test.ts index 8735838..19fbd1e 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,48 +1,50 @@ -import {expect, test} from '../src' +import {Command, Flags} from '@oclif/core' +import {expect} from 'chai' -describe('stdout', () => { - test - .stdout() - .end('logs', output => { - console.log('foo') - expect(output.stdout).to.equal('foo\n') - }) +import {captureOutput} from '../src' - test - .stdout() - .end('logs twice', output => { - console.log('foo') - expect(output.stdout).to.equal('foo\n') - console.log('bar') - expect(output.stdout).to.equal('foo\nbar\n') - }) -}) +class MyCommand extends Command { + static flags = { + channel: Flags.option({ + char: 'c', + multiple: true, + options: ['stdout', 'stderr'] as const, + required: true, + })(), + } -describe('stdout + stderr', () => { - test - .stdout() - .stderr() - .end('logs and errors', output => { - console.log('foo') - console.error('bar') - expect(output.stdout).to.equal('foo\n') - expect(output.stderr).to.equal('bar\n') - }) -}) + async run() { + const {flags} = await this.parse(MyCommand) + if (flags.channel.includes('stdout')) { + this.log('hello world!') + } -// eslint-disable-next-line unicorn/no-static-only-class -class MockOs { - static platform() { - return 'not-a-platform' + if (flags.channel.includes('stderr')) { + this.logToStderr('hello world!') + } } } -for (const os of ['darwin', 'win32', 'linux']) { - describe(os, () => { - test - .stub(MockOs, 'platform', stub => stub.returns(os)) - .end('sets os', () => { - expect(MockOs.platform()).to.equal(os) +describe('captureOutput', () => { + it('should capture stdout', async () => { + const {stdout} = await captureOutput(async () => { + await MyCommand.run(['-c=stdout']) }) + expect(stdout).to.equal('hello world!\n') }) -} + + it('should capture stderr', async () => { + const {stderr} = await captureOutput(async () => { + await MyCommand.run(['-c=stderr']) + }) + expect(stderr).to.equal('hello world!\n') + }) + + it('should capture both', async () => { + const {stderr, stdout} = await captureOutput(async () => { + await MyCommand.run(['-c=stdout', '-c=stderr']) + }) + expect(stdout).to.equal('hello world!\n') + expect(stderr).to.equal('hello world!\n') + }) +}) diff --git a/yarn.lock b/yarn.lock index cbd72d3..0e02ee8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -193,6 +193,121 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== + +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== + +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -331,6 +446,11 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== +"@types/chai@^4.3.16": + version "4.3.16" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.16.tgz#b1572967f0b8b60bf3f87fe1d854a5604ea70c82" + integrity sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ== + "@types/debug@^4.1.12": version "4.1.12" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" @@ -721,6 +841,11 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + async@^3.2.3: version "3.2.4" resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" @@ -812,6 +937,17 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== +chai@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c" + integrity sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + chalk@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" @@ -834,6 +970,11 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + chokidar@3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -1068,6 +1209,11 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== +deep-eql@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.1.tgz#21ea2c0d561a4d08cdd99c417ac584e0fb121385" + integrity sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw== + deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -1236,6 +1382,35 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild@~0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -1661,6 +1836,11 @@ fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -1696,6 +1876,11 @@ get-east-asian-width@^1.0.0: resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e" integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA== +get-func-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" @@ -1736,6 +1921,13 @@ get-tsconfig@^4.5.0: dependencies: resolve-pkg-maps "^1.0.0" +get-tsconfig@^4.7.3: + version "4.7.5" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.5.tgz#5e012498579e9a6947511ed0cd403272c7acbbaf" + integrity sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw== + dependencies: + resolve-pkg-maps "^1.0.0" + git-raw-commits@^2.0.11: version "2.0.11" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.11.tgz#bc3576638071d18655e1cc60d7f524920008d723" @@ -2416,6 +2608,13 @@ log-update@^6.0.0: strip-ansi "^7.1.0" wrap-ansi "^9.0.0" +loupe@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.1.tgz#71d038d59007d890e3247c5db97c1ec5a92edc54" + integrity sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw== + dependencies: + get-func-name "^2.0.1" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -2788,6 +2987,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + picomatch@^2.0.4, picomatch@^2.2.1: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -3381,6 +3585,16 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" +tsx@^4.10.2: + version "4.10.2" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.10.2.tgz#ed1a39f556e208e899d2d8b204aa4108fe7b4668" + integrity sha512-gOfACgv1ElsIjvt7Fp0rMJKGnMGjox0JfGOfX3kmZCV/yZumaNqtHGKBXt1KgaYS9KjDOmqGeI8gHk/W7kWVZg== + dependencies: + esbuild "~0.20.2" + get-tsconfig "^4.7.3" + optionalDependencies: + fsevents "~2.3.3" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" From f03be3f9ac252f6798a51a7beb0e4709d0f206f1 Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Wed, 15 May 2024 18:19:14 +0000 Subject: [PATCH 05/14] chore(release): 4.0.1-beta.0 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1b38d1b..9e03374 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/test", "description": "test helpers for oclif components", - "version": "4.0.1-dev.0", + "version": "4.0.1-beta.0", "author": "Salesforce", "bugs": "https://github.com/oclif/test/issues", "dependencies": { From f6eb5e30fa6f775fbd3729e61015f8dec34759d4 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 15 May 2024 13:16:26 -0600 Subject: [PATCH 06/14] fix: set node env --- src/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9bd63cf..edca52a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ const RECORD_OPTIONS: CaptureOptions = { } const originals = { + NODE_ENV: process.env.NODE_ENV, stderr: process.stderr.write, stdout: process.stdout.write, } @@ -61,6 +62,7 @@ function mockedStderr( const restore = (): void => { process.stderr.write = originals.stderr process.stdout.write = originals.stdout + process.env.NODE_ENV = originals.NODE_ENV } const reset = (): void => { @@ -113,6 +115,7 @@ export async function captureOutput( RECORD_OPTIONS.stripAnsi = opts?.stripAnsi ?? true process.stderr.write = mockedStderr process.stdout.write = mockedStdout + process.env.NODE_ENV = 'test' try { const result = await fn() @@ -150,7 +153,7 @@ export async function runCommand( const [id, ...rest] = argsArray const finalArgs = id === '.' ? rest : argsArray - debug('loadOpts: %O', loadOpts) + debug('loadOpts: %O', loadOptions) debug('args: %O', finalArgs) return captureOutput(async () => run(finalArgs, loadOptions), captureOpts) @@ -169,7 +172,7 @@ export async function runHook( }> { const loadOptions = makeLoadOptions(loadOpts) - debug('loadOpts: %O', loadOpts) + debug('loadOpts: %O', loadOptions) return captureOutput(async () => { const config = await Config.load(loadOptions) From de23862de2295ed3f2a3ea8fd1e39b260ff2abfa Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Wed, 15 May 2024 19:20:08 +0000 Subject: [PATCH 07/14] chore(release): 4.0.1-beta.1 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9e03374..41e2f26 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/test", "description": "test helpers for oclif components", - "version": "4.0.1-beta.0", + "version": "4.0.1-beta.1", "author": "Salesforce", "bugs": "https://github.com/oclif/test/issues", "dependencies": { From b3569573a27ab50880b7503fd494db756c63cbd3 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 15 May 2024 15:24:11 -0600 Subject: [PATCH 08/14] fix: localized mocking --- package.json | 2 +- src/index.ts | 117 +++++++++++++++++++-------------------------------- yarn.lock | 101 +------------------------------------------- 3 files changed, 46 insertions(+), 174 deletions(-) diff --git a/package.json b/package.json index 41e2f26..e729eba 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "debug": "^4.3.4" }, "peerDependencies": { - "@oclif/core": "^4.0.0-beta.6" + "@oclif/core": "^4.0.0-beta.7" }, "devDependencies": { "@commitlint/config-conventional": "^18.6.3", diff --git a/src/index.ts b/src/index.ts index edca52a..8061d17 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,71 +10,8 @@ type CaptureOptions = { stripAnsi?: boolean } -const RECORD_OPTIONS: CaptureOptions = { - print: false, - stripAnsi: true, -} - -const originals = { - NODE_ENV: process.env.NODE_ENV, - stderr: process.stderr.write, - stdout: process.stdout.write, -} - -const output: Record<'stderr' | 'stdout', Array> = { - stderr: [], - stdout: [], -} - -function mockedStdout(str: Uint8Array | string, cb?: (err?: Error) => void): boolean -function mockedStdout(str: Uint8Array | string, encoding?: BufferEncoding, cb?: (err?: Error) => void): boolean -function mockedStdout( - str: Uint8Array | string, - encoding?: ((err?: Error) => void) | BufferEncoding, - cb?: (err?: Error) => void, -): boolean { - output.stdout.push(str) - if (!RECORD_OPTIONS.print) return true - - if (typeof encoding === 'string') { - return originals.stdout.bind(process.stdout)(str, encoding, cb) - } - - return originals.stdout.bind(process.stdout)(str, cb) -} - -function mockedStderr(str: Uint8Array | string, cb?: (err?: Error) => void): boolean -function mockedStderr(str: Uint8Array | string, encoding?: BufferEncoding, cb?: (err?: Error) => void): boolean -function mockedStderr( - str: Uint8Array | string, - encoding?: ((err?: Error) => void) | BufferEncoding, - cb?: (err?: Error) => void, -): boolean { - output.stderr.push(str) - if (!RECORD_OPTIONS.print) return true - if (typeof encoding === 'string') { - return originals.stdout.bind(process.stderr)(str, encoding, cb) - } - - return originals.stdout.bind(process.stderr)(str, cb) -} - -const restore = (): void => { - process.stderr.write = originals.stderr - process.stdout.write = originals.stdout - process.env.NODE_ENV = originals.NODE_ENV -} - -const reset = (): void => { - output.stderr = [] - output.stdout = [] -} - -const toString = (str: Uint8Array | string): string => - RECORD_OPTIONS.stripAnsi ? ansis.strip(str.toString()) : str.toString() - -const getStderr = (): string => output.stderr.map((b) => toString(b)).join('') -const getStdout = (): string => output.stdout.map((b) => toString(b)).join('') +type MockedStdout = typeof process.stdout.write +type MockedStderr = typeof process.stderr.write function traverseFilePathUntil(filename: string, predicate: (filename: string) => boolean): string { let current = filename @@ -111,10 +48,43 @@ export async function captureOutput( stderr: string stdout: string }> { - RECORD_OPTIONS.print = opts?.print ?? false - RECORD_OPTIONS.stripAnsi = opts?.stripAnsi ?? true - process.stderr.write = mockedStderr - process.stdout.write = mockedStdout + const print = opts?.print ?? false + const stripAnsi = opts?.stripAnsi ?? true + + const originals = { + NODE_ENV: process.env.NODE_ENV, + stderr: process.stderr.write, + stdout: process.stdout.write, + } + + const output: Record<'stderr' | 'stdout', Array> = { + stderr: [], + stdout: [], + } + + const toString = (str: Uint8Array | string): string => (stripAnsi ? ansis.strip(str.toString()) : str.toString()) + const getStderr = (): string => output.stderr.map((b) => toString(b)).join('') + const getStdout = (): string => output.stdout.map((b) => toString(b)).join('') + + const mock = + (std: 'stderr' | 'stdout'): MockedStderr | MockedStdout => + (str: Uint8Array | string, encoding?: ((err?: Error) => void) | BufferEncoding, cb?: (err?: Error) => void) => { + output[std].push(str) + + if (print) { + if (encoding !== null && typeof encoding === 'function') { + cb = encoding + encoding = undefined + } + + originals[std].apply(process[std], [str, encoding, cb]) + } else if (typeof cb === 'function') cb() + + return true + } + + process.stdout.write = mock('stdout') + process.stderr.write = mock('stderr') process.env.NODE_ENV = 'test' try { @@ -126,14 +96,15 @@ export async function captureOutput( } } catch (error) { return { - ...(error instanceof Errors.CLIError && {error}), - ...(error instanceof Error && {error}), + ...(error instanceof Errors.CLIError && {error: {...error, message: toString(error.message)}}), + ...(error instanceof Error && {error: {...error, message: toString(error.message)}}), stderr: getStderr(), stdout: getStdout(), } } finally { - restore() - reset() + process.stderr.write = originals.stderr + process.stdout.write = originals.stdout + process.env.NODE_ENV = originals.NODE_ENV } } diff --git a/yarn.lock b/yarn.lock index 0e02ee8..a402f25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -186,13 +186,6 @@ dependencies: chalk "^4.1.0" -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - "@esbuild/aix-ppc64@0.20.2": version "0.20.2" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" @@ -359,24 +352,6 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -426,26 +401,6 @@ resolved "https://registry.yarnpkg.com/@oclif/prettier-config/-/prettier-config-0.2.1.tgz#1def9f38134f9bfb229257f48a35f7d0d183dc78" integrity sha512-XB8kwQj8zynXjIIWRm+6gO/r8Qft2xKtwBMSmq1JRqtA6TpwpqECqiu8LosBCyg2JBXuUy2lU23/L98KIR7FrQ== -"@tsconfig/node10@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" - integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" - integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== - "@types/chai@^4.3.16": version "4.3.16" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.16.tgz#b1572967f0b8b60bf3f87fe1d854a5604ea70c82" @@ -662,12 +617,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^8.4.1, acorn@^8.9.0: +acorn@^8.9.0: version "8.10.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== @@ -753,11 +703,6 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1158,11 +1103,6 @@ cosmiconfig@^9.0.0: js-yaml "^4.1.0" parse-json "^5.2.0" -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -1242,11 +1182,6 @@ diff@5.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -2622,11 +2557,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - map-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -3556,25 +3486,6 @@ ts-api-utils@^1.3.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== -ts-node@^10.9.2: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - tsconfig-paths@^3.15.0: version "3.15.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" @@ -3703,11 +3614,6 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -3851,11 +3757,6 @@ yargs@^17.0.0: y18n "^5.0.5" yargs-parser "^21.1.1" -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 62dbdf32b61761e3b4460b6b51798827c86b82ad Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Wed, 15 May 2024 21:25:14 +0000 Subject: [PATCH 09/14] chore(release): 4.0.1-beta.2 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e729eba..11e7903 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/test", "description": "test helpers for oclif components", - "version": "4.0.1-beta.1", + "version": "4.0.1-beta.2", "author": "Salesforce", "bugs": "https://github.com/oclif/test/issues", "dependencies": { From 0b69ca7a17ca23425bb28cc44238dba87931f4ec Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Fri, 17 May 2024 10:26:35 -0600 Subject: [PATCH 10/14] chore: update docs --- MIGRATION.md | 310 ++++++++++++++++++ README.md | 228 +------------ test/capture-output.test.ts | 69 ++++ test/exit.test.ts | 16 - test/index.test.ts | 50 --- test/{command.test.ts => run-command.test.ts} | 15 +- test/{hook.test.ts => run-hook.test.ts} | 2 +- 7 files changed, 407 insertions(+), 283 deletions(-) create mode 100644 MIGRATION.md create mode 100644 test/capture-output.test.ts delete mode 100644 test/exit.test.ts delete mode 100644 test/index.test.ts rename test/{command.test.ts => run-command.test.ts} (69%) rename test/{hook.test.ts => run-hook.test.ts} (92%) diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..708556a --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,310 @@ +# Migrating from v3 to v4 + +- [Migrating from v3 to v4](#migrating-from-v3-to-v4) + - [`.command()`](#command) + - [`.exit()`](#exit) + - [`.hook()`](#hook) + - [`.stdout() and .stderr()`](#stdout-and-stderr) + - [`.loadConfig()`](#loadconfig) + - [`.do()`](#do) + - [`.catch()`](#catch) + - [`.finally()`](#finally) + - [`.env()`](#env) + - [`.stub()`](#stub) + - [`.add()`](#add) + - [`.stdin()`](#stdin) + - [`.retries()`](#retries) + - [`.timeout()`](#timeout) + - [`.nock()`](#nock) + +## `.command()` + +`.command()` allowed you to run a command from your CLI. This can now be achieved with the `runCommand` function. + +**Before** + +```typescript +import {expect, test} from '@oclif/test' + +describe('my cli', () => { + test + .stdout() + .command(['hello:world']) + .it('runs hello world cmd', (ctx) => { + expect(ctx.stdout).to.contain('hello world!') + }) +}) +``` + +**After** + +```typescript +import {expect} from 'chai' +import {runCommand} from '@oclif/test' + +describe('my cli', () => { + it('should run the command', async () => { + const {stdout} = await runCommand<{name: string}>(['hello:world']) + expect(stdout).to.contain('hello world') + }) +}) +``` + +**Before (Single Command CLI)** + +```typescript +import {expect, test} from '@oclif/test' + +describe('my cli', () => { + test + .stdout() + .command(['.']) + .it('runs hello world cmd', (ctx) => { + expect(ctx.stdout).to.contain('hello world!') + }) +}) +``` + +**After (Single Command CLI)** + +```typescript +import {expect} from 'chai' +import {runCommand} from '@oclif/test' + +describe('my cli', () => { + it('should run the command', async () => { + const {stdout} = await runCommand<{name: string}>(['.']) + expect(stdout).to.contain('hello world') + }) +}) +``` + +## `.exit()` + +`.exit()` allowed you to test that command exited with a certain exit code. This can now be done by inspecting the `error` that's returned by `runCommand` + +**Before** + +```typescript +import {join} from 'node:path' +import {expect, test} from '@oclif/test' + +describe('exit', () => { + test + .loadConfig() + .stdout() + .command(['hello:world', '--code=101']) + .exit(101) + .do((output) => expect(output.stdout).to.equal('exiting with code 101\n')) + .it('should exit with code 101') +}) +``` + +**After** + +```typescript +import {expect} from 'chai' +import {runCommand} from '@oclif/test' + +describe('exit', () => { + it('should exit with code 101', async () => { + const {error} = await runCommand<{name: string}>(['hello:world', '--code=101']) + expect(error?.oclif?.exit).to.equal(101) + }) +}) +``` + +## `.hook()` + +`.hook()` allowed you to test a hook in your CLI. This can now be accomplished using the `runHook` function. + +**Before** + +```typescript +import {join} from 'node:path' + +import {expect, test} from '@oclif/test' + +const root = join(__dirname, 'fixtures/test-cli') + +describe('hooks', () => { + test + .loadConfig({root}) + .stdout() + .hook('foo', {argv: ['arg']}, {root}) + .do((output) => expect(output.stdout).to.equal('foo hook args: arg\n')) + .it('should run hook') +}) +``` + +**After** + +```typescript +import {join} from 'node:path' +import {expect} from 'chai' +import {runHook} from '@oclif/test' + +const root = join(__dirname, 'fixtures/test-cli') +describe('my cli', () => { + it('should run hook', async () => { + const {stdout} = await runHook('foo', {argv: ['arg']}, {root}) + expect(stdout).to.equal('foo hook args: arg\n') + }) +}) +``` + +## `.stdout() and .stderr()` + +Version 3 allowed you to access the output in stdout and stderr by attaching it to the `context`. This is now replaced by the `stdout` and `stderr` strings that are returned by `runCommand`, `runHook`, and `captureOutput` + +**Before** + +```javascript +describe('stdmock tests', () => { + fancy + .stdout() + .stderr() + .it('mocks stdout and stderr', (context) => { + console.log('foo') + console.error('bar') + expect(context.stdout).to.equal('foo\n') + expect(context.stderr).to.equal('bar\n') + }) +}) +``` + +**After** + +```typescript +import {expect} from 'chai' +import {captureOutput} from '@oclif/test' + +describe('stdmock tests', () => { + it('mocks stdout and stderr', async () => { + const {stdout, stderr} = await captureOutput(async () => { + console.log('foobar') + console.error('bar') + }) + + expect(stdout).to.equal('foo\n') + expect(stderr).to.equal('bar\n') + }) +}) +``` + +## `.loadConfig()` + +`.loadConfig()` allowed you to explicitly set the root of the CLI to be tested. This can now be achieved by passing the path into the `runCommand` function. + +**Before** + +```typescript +import {join} from 'node:path' +import {expect, test} from '@oclif/test' + +const root = join(__dirname, 'fixtures/test-cli') +describe('my cli', () => { + test + .loadConfig({root}) + .stdout() + .command(['foo:bar']) + .it('should run the command from the given directory', (ctx) => { + expect(ctx.stdout).to.equal('hello world!\n') + const {name} = ctx.returned as {name: string} + expect(name).to.equal('world') + }) +}) +``` + +**After** + +```typescript +import {join} from 'node:path' +import {expect} from 'chai' +import {runCommand} from '@oclif/test' + +const root = join(__dirname, 'fixtures/test-cli') + +describe('my cli', () => { + it('should run the command from the given directory', async () => { + const {result, stdout} = await runCommand<{name: string}>(['foo:bar'], {root}) + expect(result.name).to.equal('world') + }) +}) +``` + +## `.do()` + +`.do()` allowed you to execute some arbitrary code within the test pipeline. There's not a direct replacement in version 4, however, you are still able to execute arbitrary code within your chosen test framework. For example, mocha exposes the `beforeEach` and `before` hooks. + +## `.catch()` + +`.catch()` allowed you to catch errors in a declarative way and ensure that the error was actually thrown. We encourage you to use the utilities provided by your preferred testing framework to accomplish this. + +## `.finally()` + +`.finally()` allowed you to run a task at the end of a test, even if it failed. We encourage you to use the utilities provided by your preferred testing framework to accomplish this (for instance, mocha provided `afterEach` and `after` lifecycle hooks). + +## `.env()` + +`.env()` allowed you to set the environment variables before running the test. If you need this, you can easily implement it yourself. + +**Before** + +```javascript +describe('env tests', () => { + fancy.env({FOO: 'BAR'}).it('mocks FOO', () => { + expect(process.env.FOO).to.equal('BAR') + expect(process.env).to.not.deep.equal({FOO: 'BAR'}) + }) +}) +``` + +**After** + +```javascript +describe('env tests', () => { + let originalEnv + + beforeEach(() => { + originalEnv = {...process.env} + process.env = { + ...originalEnv + FOO: 'BAR' + } + }) + + afterEach(() => { + process.env = originalEnv + }) + + it('mocks FOO', () => { + expect(process.env.FOO).to.equal('BAR') + expect(process.env).to.not.deep.equal({FOO: 'BAR'}) + }) +}) +``` + +## `.stub()` + +`.stub()` allowed you to stub any object and ensure that the stubs were cleared before the next test. We encourage you to use a dedicated library like [sinon](https://www.npmjs.com/package/sinon) for this. + +## `.add()` + +`.add()` allowed you to extend the `context` object that was used throughout fancy-tests. There is no direct replacement for this in version 4. + +## `.stdin()` + +`.stdin()` allowed you mock stdin. There is no direct replacement for this in version 4. You can use [mock-stdin](https://www.npmjs.com/package/mock-stdin) directly if you need this functionality. + +## `.retries()` + +`.retries()` allowed you to retry the test. There is no direct replacement for this but most testing frameworks have functionality for this builtin. + +## `.timeout()` + +`.timeout` allowed to set a timeout for a test. There is no direct replacement for this but most testing frameworks have functionality for this builtin. + +## `.nock()` + +`.nock` allowed you to use [nock](https://www.npmjs.com/package/nock) to mock HTTP requests. There is no direct replacement for since you can use [nock](https://www.npmjs.com/package/nock) directly if you need to. diff --git a/README.md b/README.md index aef95e4..d5f1e25 100644 --- a/README.md +++ b/README.md @@ -6,230 +6,28 @@ test helpers for oclif CLIs [![Downloads/week](https://img.shields.io/npm/dw/@oclif/test.svg)](https://npmjs.org/package/@oclif/test) [![License](https://img.shields.io/npm/l/@oclif/test.svg)](https://github.com/oclif/test/blob/main/package.json) -## Usage - -`@oclif/test` is an extension of [fancy-test](https://github.com/oclif/fancy-test). Please see the [fancy-test documentation](https://github.com/oclif/fancy-test#fancy-test) for all the features that are available. - -The following are the features that `@oclif/test` adds to `fancy-test`. - -### `.loadConfig()` - -`.loadConfig()` creates and returns a new [`Config`](https://github.com/oclif/core/blob/main/src/config/config.ts) instance. This instance will be available on the `ctx` variable that's provided in the callback. - -```typescript -import {join} from 'node:path' -import {expect, test} from '@oclif/test' - -const root = join(__dirname, 'fixtures/test-cli') -test - .loadConfig({root}) - .stdout() - .command(['foo:bar']) - .it('should run the command from the given directory', (ctx) => { - expect(ctx.stdout).to.equal('hello world!\n') - expect(ctx.config.root).to.equal(root) - const {name} = ctx.returned as {name: string} - expect(name).to.equal('world') - }) -``` - -If you would like to run the same test without using `@oclif/test`: - -```typescript -import {Config, ux} from '@oclif/core' -import {expect} from 'chai' -import {join} from 'node:path' -import {SinonSandbox, SinonStub, createSandbox} from 'sinon' - -const root = join(__dirname, 'fixtures/test-cli') -describe('non-fancy test', () => { - let sandbox: SinonSandbox - let config: Config - let stdoutStub: SinonStub - - beforeEach(async () => { - sandbox = createSandbox() - stdoutStub = sandbox.stub(ux.write, 'stdout') - config = await Config.load({root}) - }) - - afterEach(async () => { - sandbox.restore() - }) - - it('should run command from the given directory', async () => { - const {name} = await config.runCommand<{name: string}>('foo:bar') - expect(stdoutStub.calledWith('hello world!\n')).to.be.true - expect(config.root).to.equal(root) - expect(name).to.equal('world') - }) -}) -``` - -### `.command()` - -`.command()` let's you run a command from your CLI. - -```typescript -import {expect, test} from '@oclif/test' - -describe('hello world', () => { - test - .stdout() - .command(['hello:world']) - .it('runs hello world cmd', (ctx) => { - expect(ctx.stdout).to.contain('hello world!') - }) -}) -``` - -For a [single command cli](https://oclif.io/docs/single_command_cli) you would provide `'.'` as the command. For instance: - -```typescript -import {expect, test} from '@oclif/test' +## Migration -describe('hello world', () => { - test - .stdout() - .command(['.']) - .it('runs hello world cmd', (ctx) => { - expect(ctx.stdout).to.contain('hello world!') - }) -}) -``` +See the [V4 Migration Guide](./MIGRATION.md) if you are migrating from v3 or older. -If you would like to run the same test without using `@oclif/test`: - -```typescript -import {Config, ux} from '@oclif/core' -import {expect} from 'chai' -import {SinonSandbox, SinonStub, createSandbox} from 'sinon' - -describe('non-fancy test', () => { - let sandbox: SinonSandbox - let config: Config - let stdoutStub: SinonStub - - beforeEach(async () => { - sandbox = createSandbox() - stdoutStub = sandbox.stub(ux.write, 'stdout') - config = await Config.load({root: process.cwd()}) - }) - - afterEach(async () => { - sandbox.restore() - }) - - it('should run command', async () => { - // use '.' for a single command CLI - const {name} = await config.runCommand<{name: string}>('hello:world') - expect(stdoutStub.calledWith('hello world!\n')).to.be.true - expect(name).to.equal('world') - }) -}) -``` - -### `.exit()` - -`.exit()` let's you test that a command exited with a certain exit code. - -```typescript -import {join} from 'node:path' -import {expect, test} from '@oclif/test' - -describe('exit', () => { - test - .loadConfig() - .stdout() - .command(['hello:world', '--code=101']) - .exit(101) - .do((output) => expect(output.stdout).to.equal('exiting with code 101\n')) - .it('should exit with code 101') -}) -``` - -If you would like to run the same test without using `@oclif/test`: - -```typescript -import {Config, Errors, ux} from '@oclif/core' -import {expect} from 'chai' -import {SinonSandbox, createSandbox} from 'sinon' - -describe('non-fancy test', () => { - let sandbox: SinonSandbox - let config: Config - - beforeEach(async () => { - sandbox = createSandbox() - sandbox.stub(ux.write, 'stdout') - config = await Config.load({root: process.cwd()}) - }) - - afterEach(async () => { - sandbox.restore() - }) - - it('should run command from the given directory', async () => { - try { - await config.runCommand('.') - throw new Error('Expected CLIError to be thrown') - } catch (error) { - if (error instanceof Errors.CLIError) { - expect(error.oclif.exit).to.equal(101) - } else { - throw error - } - } - }) -}) -``` - -### `.hook()` - -`.hook()` let's you test a hook in your CLI. +## Usage -```typescript -import {join} from 'node:path' +`@oclif/test` provides a handful of utilities that make it easy to test your [oclif](https://oclif.io) CLI. -import {expect, test} from '@oclif/test' +### `captureOutput` -const root = join(__dirname, 'fixtures/test-cli') +`captureOutput` allows you to get the stdout, stderr, return value, and error of the callback you provide it. This makes it possible to assert that certain strings were printed to stdout and stderr or that the callback failed with the expected error or succeeded with the expected result. -describe('hooks', () => { - test - .loadConfig({root}) - .stdout() - .hook('foo', {argv: ['arg']}, {root}) - .do((output) => expect(output.stdout).to.equal('foo hook args: arg\n')) - .it('should run hook') -}) -``` +See the [tests](./test/capture-output.test.ts) for example usage. -If you would like to run the same test without using `@oclif/test`: +### `runCommand` -```typescript -import {Config, ux} from '@oclif/core' -import {expect} from 'chai' -import {SinonSandbox, SinonStub, createSandbox} from 'sinon' +`runCommand` allows you to get the stdout, stderr, return value, and error of a command in your CLI. -describe('non-fancy test', () => { - let sandbox: SinonSandbox - let config: Config - let stdoutStub: SinonStub +See the [tests](./test/run-command.test.ts) for example usage. - beforeEach(async () => { - sandbox = createSandbox() - stdoutStub = sandbox.stub(ux.write, 'stdout') - config = await Config.load({root: process.cwd()}) - }) +### `runHook` - afterEach(async () => { - sandbox.restore() - }) +`runHook` allows you to get the stdout, stderr, return value, and error of a hook in your CLI. - it('should run hook', async () => { - const {name} = await config.runHook('foo', {argv: ['arg']}) - expect(stdoutStub.calledWith('foo hook args: arg\n')).to.be.true - }) -}) -``` +See the [tests](./test/run-hook.test.ts) for example usage. diff --git a/test/capture-output.test.ts b/test/capture-output.test.ts new file mode 100644 index 0000000..b684f9d --- /dev/null +++ b/test/capture-output.test.ts @@ -0,0 +1,69 @@ +import {Command, Errors, Flags} from '@oclif/core' +import {expect} from 'chai' + +import {captureOutput} from '../src' + +class MyCommand extends Command { + static flags = { + channel: Flags.option({ + char: 'c', + multiple: true, + options: ['stdout', 'stderr'] as const, + required: true, + })(), + throw: Flags.integer(), + } + + async run() { + const {flags} = await this.parse(MyCommand) + + if (flags.throw) throw new Errors.CLIError('error', {exit: flags.throw}) + + if (flags.channel.includes('stdout')) { + this.log('hello world!') + } + + if (flags.channel.includes('stderr')) { + this.logToStderr('hello world!') + } + + return {success: true} + } +} + +describe('captureOutput', () => { + it('should capture stdout', async () => { + const {stdout} = await captureOutput(async () => MyCommand.run(['-c=stdout'])) + expect(stdout).to.equal('hello world!\n') + }) + + it('should capture stderr', async () => { + const {stderr} = await captureOutput(async () => MyCommand.run(['-c=stderr'])) + expect(stderr).to.equal('hello world!\n') + }) + + it('should capture both', async () => { + const {stderr, stdout} = await captureOutput(async () => MyCommand.run(['-c=stdout', '-c=stderr'])) + expect(stdout).to.equal('hello world!\n') + expect(stderr).to.equal('hello world!\n') + }) + + it('should capture both from console', async () => { + const {stderr, stdout} = await captureOutput(async () => { + console.log('hello world!') + console.error('hello world!') + }) + expect(stdout).to.equal('hello world!\n') + expect(stderr).to.equal('hello world!\n') + }) + + it('should capture result', async () => { + const {result} = await captureOutput(async () => MyCommand.run(['-c=stdout'])) + expect(result).to.deep.equal({success: true}) + }) + + it('should capture error', async () => { + const {error} = await captureOutput(async () => MyCommand.run(['-c=stdout', '--throw=101'])) + expect(error?.oclif?.exit).to.equal(101) + }) +}) diff --git a/test/exit.test.ts b/test/exit.test.ts deleted file mode 100644 index 3762d2c..0000000 --- a/test/exit.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {expect} from 'chai' -import {join} from 'node:path' - -import {runCommand} from '../src' - -// eslint-disable-next-line unicorn/prefer-module -const root = join(__dirname, 'fixtures/multi') - -describe('exit', () => { - it('should handle expected exit codes', async () => { - const {error, stdout} = await runCommand(['exit', '--code=101'], {root}) - expect(stdout).to.equal('exiting with code 101\n') - expect(error?.message).to.equal('EEXIT: 101') - expect(error?.oclif?.exit).to.equal(101) - }) -}) diff --git a/test/index.test.ts b/test/index.test.ts deleted file mode 100644 index 19fbd1e..0000000 --- a/test/index.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import {Command, Flags} from '@oclif/core' -import {expect} from 'chai' - -import {captureOutput} from '../src' - -class MyCommand extends Command { - static flags = { - channel: Flags.option({ - char: 'c', - multiple: true, - options: ['stdout', 'stderr'] as const, - required: true, - })(), - } - - async run() { - const {flags} = await this.parse(MyCommand) - if (flags.channel.includes('stdout')) { - this.log('hello world!') - } - - if (flags.channel.includes('stderr')) { - this.logToStderr('hello world!') - } - } -} - -describe('captureOutput', () => { - it('should capture stdout', async () => { - const {stdout} = await captureOutput(async () => { - await MyCommand.run(['-c=stdout']) - }) - expect(stdout).to.equal('hello world!\n') - }) - - it('should capture stderr', async () => { - const {stderr} = await captureOutput(async () => { - await MyCommand.run(['-c=stderr']) - }) - expect(stderr).to.equal('hello world!\n') - }) - - it('should capture both', async () => { - const {stderr, stdout} = await captureOutput(async () => { - await MyCommand.run(['-c=stdout', '-c=stderr']) - }) - expect(stdout).to.equal('hello world!\n') - expect(stderr).to.equal('hello world!\n') - }) -}) diff --git a/test/command.test.ts b/test/run-command.test.ts similarity index 69% rename from test/command.test.ts rename to test/run-command.test.ts index 9221071..6d09fb0 100644 --- a/test/command.test.ts +++ b/test/run-command.test.ts @@ -3,7 +3,7 @@ import {join} from 'node:path' import {runCommand} from '../src' -describe('command', () => { +describe('runCommand', () => { // eslint-disable-next-line unicorn/prefer-module const root = join(__dirname, 'fixtures/multi') @@ -24,6 +24,19 @@ describe('command', () => { expect(stdout).to.equal('hello foo!\n') expect(result?.name).to.equal('foo') }) + + it('should handle single string', async () => { + const {result, stdout} = await runCommand<{name: string}>('foo:bar --name=foo', {root}) + expect(stdout).to.equal('hello foo!\n') + expect(result?.name).to.equal('foo') + }) + + it('should handle expected exit codes', async () => { + const {error, stdout} = await runCommand(['exit', '--code=101'], {root}) + expect(stdout).to.equal('exiting with code 101\n') + expect(error?.message).to.equal('EEXIT: 101') + expect(error?.oclif?.exit).to.equal(101) + }) }) describe('single command cli', () => { diff --git a/test/hook.test.ts b/test/run-hook.test.ts similarity index 92% rename from test/hook.test.ts rename to test/run-hook.test.ts index 2f62ddb..79fb8a1 100644 --- a/test/hook.test.ts +++ b/test/run-hook.test.ts @@ -6,7 +6,7 @@ import {runHook} from '../src' // eslint-disable-next-line unicorn/prefer-module const root = join(__dirname, 'fixtures/multi') -describe('hooks', () => { +describe('runHook', () => { it('should run a hook', async () => { const {stdout} = await runHook('foo', {argv: ['arg']}, {root}) expect(stdout).to.equal('foo hook args: arg\n') From ba37ede0fd48c03b7f5acf768c5ee018e339a4aa Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Fri, 17 May 2024 10:59:13 -0600 Subject: [PATCH 11/14] fix: jsdocs and types --- README.md | 5 +++ package.json | 6 +-- src/index.ts | 79 +++++++++++++++++++++++++------------ test/capture-output.test.ts | 16 +++++++- test/run-command.test.ts | 13 ++++++ yarn.lock | 2 +- 6 files changed, 89 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index d5f1e25..90cd3e2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,11 @@ See the [V4 Migration Guide](./MIGRATION.md) if you are migrating from v3 or old `captureOutput` allows you to get the stdout, stderr, return value, and error of the callback you provide it. This makes it possible to assert that certain strings were printed to stdout and stderr or that the callback failed with the expected error or succeeded with the expected result. +**Options** + +- `print` - Print everything that goes to stdout and stderr. +- `stripAnsi` - Strip ansi codes from everything that goes to stdout and stderr. Defaults to true. + See the [tests](./test/capture-output.test.ts) for example usage. ### `runCommand` diff --git a/package.json b/package.json index 11e7903..52f61a0 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ "author": "Salesforce", "bugs": "https://github.com/oclif/test/issues", "dependencies": { - "ansis": "^3.2.0", - "debug": "^4.3.4" + "debug": "^4.3.4", + "strip-ansi": "^7.1.0" }, "peerDependencies": { - "@oclif/core": "^4.0.0-beta.7" + "@oclif/core": ">= 3.0.0" }, "devDependencies": { "@commitlint/config-conventional": "^18.6.3", diff --git a/src/index.ts b/src/index.ts index 8061d17..d0ff4e4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ import {Config, Errors, Interfaces, run} from '@oclif/core' -import ansis from 'ansis' import makeDebug from 'debug' import {dirname} from 'node:path' @@ -10,6 +9,13 @@ type CaptureOptions = { stripAnsi?: boolean } +type CaptureResult = { + error?: Error & Partial + result?: T + stderr: string + stdout: string +} + type MockedStdout = typeof process.stdout.write type MockedStderr = typeof process.stderr.write @@ -39,17 +45,20 @@ function makeLoadOptions(loadOpts?: Interfaces.LoadOptions): Interfaces.LoadOpti return loadOpts ?? {root: findRoot()} } -export async function captureOutput( - fn: () => Promise, - opts?: CaptureOptions, -): Promise<{ - error?: Error & Partial - result?: T - stderr: string - stdout: string -}> { +/** + * Capture the stderr and stdout output of a function + * @param fn async function to run + * @param opts options + * - print: Whether to print the output to the console + * - stripAnsi: Whether to strip ANSI codes from the output + * @returns {Promise>} Captured output + * - error: Error object if the function throws an error + * - result: Result of the function if it returns a value and succeeds + * - stderr: Captured stderr output + * - stdout: Captured stdout output + */ +export async function captureOutput(fn: () => Promise, opts?: CaptureOptions): Promise> { const print = opts?.print ?? false - const stripAnsi = opts?.stripAnsi ?? true const originals = { NODE_ENV: process.env.NODE_ENV, @@ -62,7 +71,8 @@ export async function captureOutput( stdout: [], } - const toString = (str: Uint8Array | string): string => (stripAnsi ? ansis.strip(str.toString()) : str.toString()) + const {default: stripAnsi} = opts?.stripAnsi ?? true ? await import('strip-ansi') : {default: (str: string) => str} + const toString = (str: Uint8Array | string): string => stripAnsi(str.toString()) const getStderr = (): string => output.stderr.map((b) => toString(b)).join('') const getStdout = (): string => output.stdout.map((b) => toString(b)).join('') @@ -108,16 +118,24 @@ export async function captureOutput( } } +/** + * Capture the stderr and stdout output of a command in your CLI + * @param args Command arguments, e.g. `['my:command', '--flag']` or `'my:command --flag'` + * @param loadOpts options for loading oclif `Config` + * @param captureOpts options for capturing the output + * - print: Whether to print the output to the console + * - stripAnsi: Whether to strip ANSI codes from the output + * @returns {Promise>} Captured output + * - error: Error object if the command throws an error + * - result: Result of the command if it returns a value and succeeds + * - stderr: Captured stderr output + * - stdout: Captured stdout output + */ export async function runCommand( args: string | string[], loadOpts?: Interfaces.LoadOptions, captureOpts?: CaptureOptions, -): Promise<{ - error?: Error & Partial - result?: T - stderr: string - stdout: string -}> { +): Promise> { const loadOptions = makeLoadOptions(loadOpts) const argsArray = (Array.isArray(args) ? args : [args]).join(' ').split(' ') @@ -130,17 +148,26 @@ export async function runCommand( return captureOutput(async () => run(finalArgs, loadOptions), captureOpts) } +/** + * Capture the stderr and stdout output of a hook in your CLI + * @param hook Hook name + * @param options options to pass to the hook + * @param loadOpts options for loading oclif `Config` + * @param captureOpts options for capturing the output + * - print: Whether to print the output to the console + * - stripAnsi: Whether to strip ANSI codes from the output + * @returns {Promise>} Captured output + * - error: Error object if the hook throws an error + * - result: Result of the hook if it returns a value and succeeds + * - stderr: Captured stderr output + * - stdout: Captured stdout output + */ export async function runHook( hook: string, options: Record, loadOpts?: Interfaces.LoadOptions, - recordOpts?: CaptureOptions, -): Promise<{ - error?: Error & Partial - result?: T - stderr: string - stdout: string -}> { + captureOpts?: CaptureOptions, +): Promise> { const loadOptions = makeLoadOptions(loadOpts) debug('loadOpts: %O', loadOptions) @@ -148,5 +175,5 @@ export async function runHook( return captureOutput(async () => { const config = await Config.load(loadOptions) return config.runHook(hook, options) - }, recordOpts) + }, captureOpts) } diff --git a/test/capture-output.test.ts b/test/capture-output.test.ts index b684f9d..9e70501 100644 --- a/test/capture-output.test.ts +++ b/test/capture-output.test.ts @@ -3,6 +3,8 @@ import {expect} from 'chai' import {captureOutput} from '../src' +const bold = (s: string) => `\u001B[1m${s}\u001B[22m` + class MyCommand extends Command { static flags = { channel: Flags.option({ @@ -20,11 +22,11 @@ class MyCommand extends Command { if (flags.throw) throw new Errors.CLIError('error', {exit: flags.throw}) if (flags.channel.includes('stdout')) { - this.log('hello world!') + this.log(bold('hello world!')) } if (flags.channel.includes('stderr')) { - this.logToStderr('hello world!') + this.logToStderr(bold('hello world!')) } return {success: true} @@ -66,4 +68,14 @@ describe('captureOutput', () => { const {error} = await captureOutput(async () => MyCommand.run(['-c=stdout', '--throw=101'])) expect(error?.oclif?.exit).to.equal(101) }) + + it('should strip ansi codes by default', async () => { + const {stdout} = await captureOutput(async () => MyCommand.run(['-c=stdout'])) + expect(stdout).to.equal('hello world!\n') + }) + + it('should not strip ansi codes if stripAnsi is false', async () => { + const {stdout} = await captureOutput(async () => MyCommand.run(['-c=stdout']), {stripAnsi: false}) + expect(stdout).to.equal('\u001B[1mhello world!\u001B[22m\n') + }) }) diff --git a/test/run-command.test.ts b/test/run-command.test.ts index 6d09fb0..5040ee7 100644 --- a/test/run-command.test.ts +++ b/test/run-command.test.ts @@ -1,3 +1,4 @@ +import {Config} from '@oclif/core' import {expect} from 'chai' import {join} from 'node:path' @@ -37,6 +38,18 @@ describe('runCommand', () => { expect(error?.message).to.equal('EEXIT: 101') expect(error?.oclif?.exit).to.equal(101) }) + + it('should take existing Config instance', async () => { + const config = await Config.load(root) + const {result, stdout} = await runCommand<{name: string}>(['foo:bar'], config) + expect(stdout).to.equal('hello world!\n') + expect(result?.name).to.equal('world') + }) + + it('should find root dynamically if not provided', async () => { + const {stdout} = await runCommand(['--help']) + expect(stdout).to.include('$ @oclif/test [COMMAND]') + }) }) describe('single command cli', () => { diff --git a/yarn.lock b/yarn.lock index a402f25..1f9a3d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -690,7 +690,7 @@ ansi-styles@^6.0.0, ansi-styles@^6.2.1: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -ansis@^3.0.1, ansis@^3.2.0: +ansis@^3.0.1: version "3.2.0" resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.2.0.tgz#0e050c5be94784f32ffdac4b84fccba064aeae4b" integrity sha512-Yk3BkHH9U7oPyCN3gL5Tc7CpahG/+UFv/6UG03C311Vy9lzRmA5uoxDTpU9CO3rGHL6KzJz/pdDeXZCZ5Mu/Sg== From 7000ad63d2f4dfa52cdad16b165c785324fa2175 Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Fri, 17 May 2024 16:59:41 +0000 Subject: [PATCH 12/14] chore(release): 4.0.1-beta.3 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 52f61a0..32cc031 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/test", "description": "test helpers for oclif components", - "version": "4.0.1-beta.2", + "version": "4.0.1-beta.3", "author": "Salesforce", "bugs": "https://github.com/oclif/test/issues", "dependencies": { From e488ab777fd6f8dec2851511fce2d18556dc1116 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Fri, 17 May 2024 11:04:16 -0600 Subject: [PATCH 13/14] fix: use ansis --- package.json | 4 ++-- src/index.ts | 5 +++-- test/capture-output.test.ts | 3 +-- yarn.lock | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 32cc031..6d42e09 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "author": "Salesforce", "bugs": "https://github.com/oclif/test/issues", "dependencies": { - "debug": "^4.3.4", - "strip-ansi": "^7.1.0" + "ansis": "^3.2.0", + "debug": "^4.3.4" }, "peerDependencies": { "@oclif/core": ">= 3.0.0" diff --git a/src/index.ts b/src/index.ts index d0ff4e4..9f33fb0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import {Config, Errors, Interfaces, run} from '@oclif/core' +import ansis from 'ansis' import makeDebug from 'debug' import {dirname} from 'node:path' @@ -59,6 +60,7 @@ function makeLoadOptions(loadOpts?: Interfaces.LoadOptions): Interfaces.LoadOpti */ export async function captureOutput(fn: () => Promise, opts?: CaptureOptions): Promise> { const print = opts?.print ?? false + const stripAnsi = opts?.stripAnsi ?? true const originals = { NODE_ENV: process.env.NODE_ENV, @@ -71,8 +73,7 @@ export async function captureOutput(fn: () => Promise, opts?: Captur stdout: [], } - const {default: stripAnsi} = opts?.stripAnsi ?? true ? await import('strip-ansi') : {default: (str: string) => str} - const toString = (str: Uint8Array | string): string => stripAnsi(str.toString()) + const toString = (str: Uint8Array | string): string => (stripAnsi ? ansis.strip(str.toString()) : str.toString()) const getStderr = (): string => output.stderr.map((b) => toString(b)).join('') const getStdout = (): string => output.stdout.map((b) => toString(b)).join('') diff --git a/test/capture-output.test.ts b/test/capture-output.test.ts index 9e70501..504c8dd 100644 --- a/test/capture-output.test.ts +++ b/test/capture-output.test.ts @@ -1,10 +1,9 @@ import {Command, Errors, Flags} from '@oclif/core' +import {bold} from 'ansis' import {expect} from 'chai' import {captureOutput} from '../src' -const bold = (s: string) => `\u001B[1m${s}\u001B[22m` - class MyCommand extends Command { static flags = { channel: Flags.option({ diff --git a/yarn.lock b/yarn.lock index 1f9a3d0..a402f25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -690,7 +690,7 @@ ansi-styles@^6.0.0, ansi-styles@^6.2.1: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -ansis@^3.0.1: +ansis@^3.0.1, ansis@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.2.0.tgz#0e050c5be94784f32ffdac4b84fccba064aeae4b" integrity sha512-Yk3BkHH9U7oPyCN3gL5Tc7CpahG/+UFv/6UG03C311Vy9lzRmA5uoxDTpU9CO3rGHL6KzJz/pdDeXZCZ5Mu/Sg== From 4e4b433f1da9cdf089e3454fcd4827fa8ccf0422 Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Fri, 17 May 2024 17:04:37 +0000 Subject: [PATCH 14/14] chore(release): 4.0.1-beta.4 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d42e09..77e686d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/test", "description": "test helpers for oclif components", - "version": "4.0.1-beta.3", + "version": "4.0.1-beta.4", "author": "Salesforce", "bugs": "https://github.com/oclif/test/issues", "dependencies": {