diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index bbbcc9f44..000000000 --- a/.eslintrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": [ - "oclif", - "oclif-typescript" - ], - "rules": { - "unicorn/prefer-module": "off", - "unicorn/prefer-node-protocol": "off", - "unicorn/import-style": "off", - "unicorn/consistent-function-scoping": "off", - "unicorn/no-array-reduce": "off", - "unicorn/prefer-array-some": "off", - "node/no-missing-import": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/ban-ts-ignore": "off", - "@typescript-eslint/ban-ts-comment": "off", - "no-useless-constructor": "off" - } -} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..2887d4e87 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "extends": [ + "oclif", + "oclif-typescript" + ], + "rules": { + "sort-imports": "error", + "unicorn/prefer-module": "off", + "unicorn/import-style": "error", + "unicorn/no-array-reduce": "off", + "unicorn/prefer-array-some": "off", + "no-useless-constructor": "off" + } +} diff --git a/.github/workflows/create-github-release.yml b/.github/workflows/create-github-release.yml index afe0cc289..4dfce7f21 100644 --- a/.github/workflows/create-github-release.yml +++ b/.github/workflows/create-github-release.yml @@ -24,6 +24,7 @@ jobs: # However, if this is a manual release (workflow_dispatch), then we want to disable skip-on-empty # This helps recover from forgetting to add semantic commits ('fix:', 'feat:', etc.) skip-on-empty: ${{ github.event_name == 'push' }} + generate-readme: false # docs: # # Most repos won't use this # # Depends on the 'release' job to avoid git collisions, not for any functionality reason diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3a3d15fa..aa47b8e20 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,13 +17,11 @@ jobs: needs: linux-unit-tests strategy: matrix: - os: ["ubuntu-latest", "windows-latest"] - node_version: [lts/-1, lts/*, latest] + os: [ubuntu-latest, windows-latest] + node_version: [lts/*, latest] exclude: - os: windows-latest node_version: lts/* - - os: windows-latest - node_version: lts/-1 fail-fast: false runs-on: ${{ matrix.os }} steps: @@ -32,39 +30,77 @@ jobs: with: node-version: ${{ matrix.node_version }} cache: yarn - - run: yarn install --network-timeout 600000 + - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main - run: yarn build - - run: yarn test:e2e + - if: runner.os == 'Windows' + run: yarn mocha --forbid-only "test/**/*.e2e.ts" --exclude "test/integration/sf.e2e.ts" --parallel --timeout 1200000 + - if: runner.os == 'Linux' + run: yarn test:e2e + windows-sf-e2e: + # For whatever reason the windows-latest runner doesn't like it when you shell yarn commands in the sf repo + # which is an integral part of the setup for the tests. Instead, we replicate the setup here. + needs: linux-unit-tests + strategy: + fail-fast: false + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: latest + cache: yarn + - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main + - run: yarn build + - run: yarn link + - run: New-Item -Path D:\a -Name "e2e" -ItemType "directory" + - run: New-Item -Path D:\a\e2e -Name "sf.e2e.ts" -ItemType "directory" + - run: | + git clone https://github.com/salesforcecli/cli.git --branch mdonnalley/esm + cd cli + + $Json = Get-Content package.json | ConvertFrom-Json + $Json.dependencies | Add-Member -Force -MemberType NoteProperty -Name "@oclif/core" -Value "file:D:\a\core\core" + $Json.resolutions | Add-Member -MemberType NoteProperty -Name "@oclif/core" -Value "D:\a\core\core" + $Json | ConvertTo-Json -Depth 9 | Out-File package.json + + yarn install --network-timeout 600000 + yarn link @oclif/core + yarn build + working-directory: D:\a\e2e\sf.e2e.ts + - run: yarn mocha --forbid-only "test/integration/sf.e2e.ts" --parallel --timeout 1200000 + env: + OCLIF_CORE_E2E_SKIP_SETUP: true + OCLIF_CORE_E2E_TEST_DIR: D:\a\e2e + DEBUG: e2e:* esm-cjs-interop: needs: linux-unit-tests strategy: matrix: - os: ["ubuntu-latest", "windows-latest"] - node_version: [lts/-1, lts/*, latest] + os: [ubuntu-latest, windows-latest] + node_version: [lts/*, latest] + test: [esm, cjs, precore, coreV1, coreV2] exclude: - os: windows-latest node_version: lts/* - - os: windows-latest - node_version: lts/-1 fail-fast: false runs-on: ${{ matrix.os }} - timeout-minutes: 60 + timeout-minutes: 75 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node_version }} cache: yarn - - run: yarn install --network-timeout 600000 + - uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main - run: yarn build - - run: yarn test:esm-cjs + - run: yarn test:esm-cjs --test=${{ matrix.test }} nuts: needs: linux-unit-tests uses: salesforcecli/github-workflows/.github/workflows/externalNut.yml@main strategy: fail-fast: false matrix: - os: ["ubuntu-latest", "windows-latest"] + os: [ubuntu-latest, windows-latest] externalProjectGitUrl: - https://github.com/salesforcecli/plugin-auth - https://github.com/salesforcecli/plugin-data diff --git a/README.md b/README.md index 2a7e60cd1..1c577b447 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,11 @@ base library for oclif CLIs Migrating ===== +See the [v3 migration guide](./guides/V3_MIGRATION.md) for an overview of breaking changes that occurred between v2 and v3. + See the [v2 migration guide](./guides/V2_MIGRATION.md) for an overview of breaking changes that occurred between v1 and v2. -See the [v3 migration guide](./guides/V3_MIGRATION.md) for an overview of breaking changes that occurred between v2 and v3. +Migrating from `@oclif/config` and `@oclif/command`? See the [v1 migration guide](./guides/PRE_CORE_MIGRATION.md). CLI UX ===== diff --git a/guides/PRE_CORE_MIGRATION.md b/guides/PRE_CORE_MIGRATION.md new file mode 100644 index 000000000..16a6fdc20 --- /dev/null +++ b/guides/PRE_CORE_MIGRATION.md @@ -0,0 +1,182 @@ +Migrating to @oclif/core from deprecated libraries +============== + +Migrating to `@oclif/core` from the deprecated oclif libraries (`@oclif/config`, `@oclif/command`, `@oclif/error`, `@oclif/parser`) is relatively straight forward. + +- [Migrating to @oclif/core from deprecated libraries](#migrating-to-oclifcore-from-deprecated-libraries) + - [Update Imports](#update-imports) + - [Update your bin scripts](#update-your-bin-scripts) + - [Add `main` to your package.json](#add-main-to-your-packagejson) + - [Restore `-h`, `-v`, and `version`](#restore--h--v-and-version) + - [Configure the `topicSeparator`](#configure-the-topicseparator) + - [Update `this.parse` to `await this.parse`](#update-thisparse-to-await-thisparse) + - [Update `default` property on flag definitions](#update-default-property-on-flag-definitions) + - [Replace cli-ux library with `ux`](#replace-cli-ux-library-with-ux) + - [Single command CLIs](#single-command-clis) + +## Update Imports + +Replace imports from the old libraries with `@oclif/core`. For example, + +```typescript +import Help from '@oclif/plugin-help'; +import {Topic} from '@oclif/config'; +import {Command, Flags} from '@oclif/command' +``` + +With this import: + +```typescript +import {Command, Flags, Topic, Help} from '@oclif/core'; +``` + +## Update your bin scripts + +`@oclif/core` now supports separate bin scripts for production and development. + +You can copy these new bin scripts directly from our [example repository](https://github.com/oclif/hello-world/tree/main/bin). + +## Add `main` to your package.json + +We recommend that all oclif plugins specify the `main` field in their package.json so that we can begin working on supporting Yarn v2. + +```json +{ + "main": "lib/index.js" +} +``` + +All plugins will be required to have this field in the next major version of `@oclif/core`. + +## Restore `-h`, `-v`, and `version` + +`@oclif/config` automatically added `-h` as a short flag for `--help`, `-v` as a short flag for `--version`, and `version` as an alias for `--version`. + +`@oclif/core` removes these so you can now use those flags for whatever you want! However, we've added a way to restore that functionality if you want to keep it. + +Simply add the `additionalHelpFlags` and `additionalVersionFlags` properties to the oclif section of your package.json: + +```json +{ + "oclif": { + "additionalHelpFlags": ["-h"], + "additionalVersionFlags": ["-v"] + } +} +``` + +To get the `version` command, install `@oclif/plugin-version` into your CLI: + +```json +{ + "dependencies": { + "@oclif/plugin-version": "^3" + }, + "oclif": { + "plugins": [ + "@oclif/plugin-version" + ] + } +} +``` + +## Configure the `topicSeparator` + +By default, the `topicSeparator` is set to a colon (`:`) to maintain backwards compatibility with existing CLIs. If you prefer, you can now set it to a space. + +For colons: +```json +{ + "oclif": { + "topicSeparator": ":" + } +} +``` + +For spaces: +```json +{ + "oclif": { + "topicSeparator": " " + } +} +``` + +**NOTE: Using colons always works, even if you set the `topicSeparator` to spaces.** This means that you can enable spaces in your CLI without introducing a breaking change to your users. + +## Update `this.parse` to `await this.parse` + +The `parse` method on `Command` is now asynchronous (more [here](https://oclif.io/blog/#async-command-parsing)). So you'll now need to `await` any calls to `this.parse`: + +`const { args, flags } = this.parse(MyCommand)` => `const { args, flags } = await this.parse(MyCommand)` + +## Update `default` property on flag definitions + +The `default` property on flag definitions is now asynchronous. So you'll now need to await those. + +Example: + +```typescript +import {Command, Flags} from '@oclif/core' +import {readFile} from 'fs/promises' + +function getTeam(): Promise { + return readFile('team.txt', 'utf-8') +} + +export const team = Flags.build({ + char: 't', + description: 'team to use', + default: () => getTeam(), +}) + +export class MyCLI extends Command { + static flags = { + team: team(), + } + + async run() { + const {flags} = this.parse(MyCLI) + if (flags.team) console.log(`--team is ${flags.team}`) + } +} +``` + +## Replace cli-ux library with `ux` + +The [`cli-ux` library](https://github.com/oclif/cli-ux) has also been moved into `@oclif/core` in order to break a complex circular dependency between the two projects. + +All the exports that were available from `cli-ux` are now available under the `ux` namespace, with the exception of the `cli` export which was identical to the `ux` export. + +Old: + +```typescript +import { cli } from 'cli-ux` + +cli.log('hello world') +cli.action.start('doing things') +cli.action.stop() +``` + +New: + +```typescript +import { ux } from '@oclif/core` + +ux.log('hello world') +ux.action.start('doing things') +ux.action.stop() +``` + +## Single command CLIs + +Single command CLIs now are configured in a different way. To ensure your migrated CLI work as before, you have to add the following to your `oclif` configuration in the `package.json`: + +```json +"oclif": { + "default": ".", + "commands": "./lib" +} +``` + +Where `./lib` points to the folder in which your `tsconfig.json` is configured to output to (if you are using TypeScript), and your single command CLI entrypoint `index.(ts|js)` is located. diff --git a/guides/V3_MIGRATION.md b/guides/V3_MIGRATION.md index 82811fad4..9b83abc6f 100644 --- a/guides/V3_MIGRATION.md +++ b/guides/V3_MIGRATION.md @@ -13,6 +13,8 @@ Migrating to @oclif/core@V3 - [`noCacheDefault` flag property replaces `isWritingManifest`](#nocachedefault-flag-property-replaces-iswritingmanifest) - [Features 🎉](#features-) - [Cache Flexible taxonomy Command Permutations](#cache-flexible-taxonomy-command-permutations) + - [charAliases Flag Property](#charaliases-flag-property) + - [Flags.option](#flagsoption) ## BREAKING CHANGES ❗ @@ -114,3 +116,36 @@ export const mySensitiveFlag = Flags.string({ ### Cache Flexible taxonomy Command Permutations The command permutations for flexible taxonomy are now cached in the oclif.manifest.json allowing for quicker startup times. + +### charAliases Flag Property + +You can now define single character flag aliases using the `charAliases` property. + +### Flags.option + +There's a new flag type that infers the flag's type from the provided options. + +In v2 you would have needed to do something like this, + +```typescript +type Options = 'foo' | 'bar' +export default class MyCommand extends Command { + static flags = { + name: Flags.custom({ + options: ['foo', 'bar'], + })(), + } +} +``` + +Now in v3 you can do this, + +```typescript +export default class MyCommand extends Command { + static flags = { + name: Flags.option({ + options: ['foo', 'bar'] as const, + })(), + } +} +``` diff --git a/package.json b/package.json index 198447fc7..c3867d28d 100644 --- a/package.json +++ b/package.json @@ -45,15 +45,12 @@ "@types/chai-as-promised": "^7.1.5", "@types/clean-stack": "^2.1.1", "@types/ejs": "^3.1.2", - "@types/glob": "^8.1.0", "@types/indent-string": "^4.0.1", "@types/js-yaml": "^3.12.7", "@types/mocha": "^8.2.3", "@types/nock": "^11.1.0", - "@types/node": "^16", + "@types/node": "^18", "@types/node-notifier": "^8.0.2", - "@types/proxyquire": "^1.3.28", - "@types/shelljs": "^0.8.12", "@types/slice-ansi": "^4.0.0", "@types/strip-ansi": "^5.2.1", "@types/supports-color": "^8.1.1", @@ -64,23 +61,21 @@ "chai-as-promised": "^7.1.1", "commitlint": "^12.1.4", "cross-env": "^7.0.3", - "eslint": "^7.32.0", - "eslint-config-oclif": "^4.0.0", - "eslint-config-oclif-typescript": "^1.0.3", - "fancy-test": "^2.0.16", + "eslint": "^8.49.0", + "eslint-config-oclif": "^5.0.0", + "eslint-config-oclif-typescript": "^2.0.1", + "fancy-test": "^3.0.0-beta.2", "globby": "^11.1.0", "husky": "6", "mocha": "^10.2.0", "nock": "^13.3.0", - "proxyquire": "^2.1.3", - "shelljs": "^0.8.5", "shx": "^0.3.4", "sinon": "^11.1.2", - "tsd": "^0.25.0", - "typescript": "^4.9.5" + "tsd": "^0.29.0", + "typescript": "^5" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "files": [ "/lib", @@ -109,7 +104,7 @@ "build": "shx rm -rf lib && tsc", "commitlint": "commitlint", "compile": "tsc", - "lint": "eslint . --ext .ts --config .eslintrc", + "lint": "eslint . --ext .ts", "posttest": "yarn lint", "prepack": "yarn run build", "pretest": "yarn build --noEmit && tsc -p test --noEmit --skipLibCheck", diff --git a/src/args.ts b/src/args.ts index 9522ec751..861bf8954 100644 --- a/src/args.ts +++ b/src/args.ts @@ -1,7 +1,8 @@ -import {URL} from 'url' + import {Arg, ArgDefinition} from './interfaces/parser' -import {Command} from './command' import {dirExists, fileExists, isNotFalsy} from './util' +import {Command} from './command' +import {URL} from 'node:url' /** * Create a custom arg. @@ -22,15 +23,13 @@ import {dirExists, fileExists, isNotFalsy} from './util' */ export function custom>(defaults: Partial>): ArgDefinition export function custom>(defaults: Partial>): ArgDefinition { - return (options: any = {}) => { - return { - parse: async (i: string, _context: Command, _opts: P) => i, - ...defaults, - ...options, - input: [] as string[], - type: 'option', - } - } + return (options: any = {}) => ({ + parse: async (i: string, _context: Command, _opts: P) => i, + ...defaults, + ...options, + input: [] as string[], + type: 'option', + }) } export const boolean = custom({ @@ -38,7 +37,7 @@ export const boolean = custom({ }) export const integer = custom({ - parse: async (input, _, opts) => { + async parse(input, _, opts) { if (!/^-?\d+$/.test(input)) throw new Error(`Expected an integer but received: ${input}`) const num = Number.parseInt(input, 10) @@ -51,7 +50,7 @@ export const integer = custom({ }) export const directory = custom({ - parse: async (input, _, opts) => { + async parse(input, _, opts) { if (opts.exists) return dirExists(input) return input @@ -59,7 +58,7 @@ export const directory = custom({ }) export const file = custom({ - parse: async (input, _, opts) => { + async parse(input, _, opts) { if (opts.exists) return fileExists(input) return input @@ -71,7 +70,7 @@ export const file = custom({ * if the string is not a valid URL. */ export const url = custom({ - parse: async input => { + async parse(input) { try { return new URL(input) } catch { diff --git a/src/cli-ux/action/base.ts b/src/cli-ux/action/base.ts index 6e71dcb59..bd2a5e54e 100644 --- a/src/cli-ux/action/base.ts +++ b/src/cli-ux/action/base.ts @@ -1,6 +1,6 @@ -import {inspect} from 'util' -import {castArray} from '../../util' import {stderr, stdout} from '../stream' +import {castArray} from '../../util' +import {inspect} from 'node:util' export interface ITask { action: string; @@ -37,7 +37,7 @@ export class ActionBase { } public stop(msg = 'done'): void { - const task = this.task + const {task} = this if (!task) { return } @@ -80,7 +80,7 @@ export class ActionBase { } set status(status: string | undefined) { - const task = this.task + const {task} = this if (!task) { return } @@ -93,8 +93,8 @@ export class ActionBase { task.status = status } - public async pauseAsync(fn: () => Promise, icon?: string): Promise { - const task = this.task + public async pauseAsync(fn: () => Promise, icon?: string): Promise { + const {task} = this const active = task && task.active if (task && active) { this._pause(icon) @@ -111,7 +111,7 @@ export class ActionBase { } public pause(fn: () => any, icon?: string): Promise { - const task = this.task + const {task} = this const active = task && task.active if (task && active) { this._pause(icon) @@ -143,7 +143,9 @@ export class ActionBase { throw new Error('not implemented') } - protected _updateStatus(_: string | undefined, __?: string): void {} + protected _updateStatus(_: string | undefined, __?: string): void { + // Not implemented + } // mock out stdout/stderr so it doesn't screw up the rendering protected _stdout(toggle: boolean): void { @@ -191,7 +193,7 @@ export class ActionBase { // add newline if there isn't one already // otherwise we'll just overwrite it when we render - if (output && std && output[output.length - 1] !== '\n') { + if (output && std && output.at(-1) !== '\n') { this._write(std, '\n') } } catch (error) { @@ -202,14 +204,19 @@ export class ActionBase { // write to the real stdout/stderr protected _write(std: 'stdout' | 'stderr', s: string | string[]): void { switch (std) { - case 'stdout': + case 'stdout': { this.stdmockOrigs.stdout.apply(stdout, castArray(s) as [string]) break - case 'stderr': + } + + case 'stderr': { this.stdmockOrigs.stderr.apply(stderr, castArray(s) as [string]) break - default: + } + + default: { throw new Error(`invalid std: ${std}`) } + } } } diff --git a/src/cli-ux/action/simple.ts b/src/cli-ux/action/simple.ts index c576011df..30db51f2f 100644 --- a/src/cli-ux/action/simple.ts +++ b/src/cli-ux/action/simple.ts @@ -4,7 +4,7 @@ export default class SimpleAction extends ActionBase { public type: ActionType = 'simple' protected _start(): void { - const task = this.task + const {task} = this if (!task) return this._render(task.action, task.status) } @@ -14,27 +14,29 @@ export default class SimpleAction extends ActionBase { else this._flush() } - protected _resume(): void {} + protected _resume(): void { + // Not implemented + } protected _updateStatus(status: string, prevStatus?: string, newline = false): void { - const task = this.task + const {task, std} = this if (!task) return - if (task.active && !prevStatus) this._write(this.std, ` ${status}`) - else this._write(this.std, `${task.action}... ${status}`) + if (task.active && !prevStatus) this._write(std, ` ${status}`) + else this._write(std, `${task.action}... ${status}`) if (newline || !prevStatus) this._flush() } protected _stop(status: string): void { - const task = this.task + const {task} = this if (!task) return this._updateStatus(status, task.status, true) } private _render(action: string, status?: string) { - const task = this.task + const {task, std} = this if (!task) return if (task.active) this._flush() - this._write(this.std, status ? `${action}... ${status}` : `${action}...`) + this._write(std, status ? `${action}... ${status}` : `${action}...`) } private _flush() { diff --git a/src/cli-ux/action/spinner.ts b/src/cli-ux/action/spinner.ts index 05ca3e50d..4965c54f2 100644 --- a/src/cli-ux/action/spinner.ts +++ b/src/cli-ux/action/spinner.ts @@ -1,10 +1,10 @@ -import * as chalk from 'chalk' import * as supportsColor from 'supports-color' -const stripAnsi = require('strip-ansi') -const ansiStyles = require('ansi-styles') +import {ActionBase, ActionType} from './base' +import ansiStyles from 'ansi-styles' +import chalk from 'chalk' import {errtermwidth} from '../../screen' import spinners from './spinners' -import {ActionBase, ActionType} from './base' +import stripAnsi from 'strip-ansi' function color(s: string): string { if (!supportsColor) return s @@ -61,14 +61,14 @@ export default class SpinnerAction extends ActionBase { } private _render(icon?: string) { - const task = this.task + const {task, std, output} = this if (!task) return this._reset() this._flushStdout() const frame = icon === 'spinner' ? ` ${this._frame()}` : icon || '' const status = task.status ? ` ${task.status}` : '' this.output = `${task.action}...${frame}${status}\n` - this._write(this.std, this.output) + this._write(std, output!) } private _reset() { diff --git a/src/cli-ux/config.ts b/src/cli-ux/config.ts index 5734a5746..5718f9775 100644 --- a/src/cli-ux/config.ts +++ b/src/cli-ux/config.ts @@ -1,8 +1,8 @@ +import {ActionBase} from './action/base' import {PJSON} from '../interfaces/pjson' import {requireJson} from '../util' +import simple from './action/simple' import spinner from './action/spinner' -import simple from './action/spinner' -import {ActionBase} from './action/base' export type Levels = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' @@ -16,10 +16,10 @@ const g: any = global const globals = g.ux || (g.ux = {}) const actionType = ( - Boolean(process.stderr.isTTY) && - !process.env.CI && - !['dumb', 'emacs-color'].includes(process.env.TERM!) && - 'spinner' + Boolean(process.stderr.isTTY) + && !process.env.CI + && !['dumb', 'emacs-color'].includes(process.env.TERM!) + && 'spinner' ) || 'simple' const Action = actionType === 'spinner' ? spinner : simple diff --git a/src/cli-ux/flush.ts b/src/cli-ux/flush.ts index 148975efb..c6a0c152d 100644 --- a/src/cli-ux/flush.ts +++ b/src/cli-ux/flush.ts @@ -17,9 +17,7 @@ async function _flush() { }) const flushed = stdout.write('') - if (flushed) { - return Promise.resolve() - } + if (flushed) return return p } diff --git a/src/cli-ux/index.ts b/src/cli-ux/index.ts index a882f9258..1b7811f7a 100644 --- a/src/cli-ux/index.ts +++ b/src/cli-ux/index.ts @@ -1,16 +1,14 @@ + import * as Errors from '../errors' -import * as util from 'util' -import * as chalk from 'chalk' -import {ActionBase} from './action/base' -import {config, Config} from './config' -import {ExitError} from './exit' -import {IPromptOptions} from './prompt' import * as styled from './styled' -import {Table} from './styled' import * as uxPrompt from './prompt' -import uxWait from './wait' -import {stdout} from './stream' +import {Config, config} from './config' +import {ActionBase} from './action/base' import {flush as _flush} from './flush' +import chalk from 'chalk' +import {stdout} from './stream' +import {format as utilFormat} from 'node:util' +import uxWait from './wait' const hyperlinker = require('hyperlinker') @@ -36,7 +34,6 @@ export class ux { return config.action } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types public static styledObject(obj: any, keys?: string[]): void { this.info(styled.styledObject(obj, keys)) } @@ -71,18 +68,18 @@ export class ux { public static trace(format: string, ...args: string[]): void { if (this.config.outputLevel === 'trace') { - stdout.write(util.format(format, ...args) + '\n') + stdout.write(utilFormat(format, ...args) + '\n') } } public static debug(format: string, ...args: string[]): void { if (['trace', 'debug'].includes(this.config.outputLevel)) { - stdout.write(util.format(format, ...args) + '\n') + stdout.write(utilFormat(format, ...args) + '\n') } } public static info(format: string, ...args: string[]): void { - stdout.write(util.format(format, ...args) + '\n') + stdout.write(utilFormat(format, ...args) + '\n') } public static log(format?: string, ...args: string[]): void { @@ -113,45 +110,40 @@ export class ux { } } -const action = ux.action -const annotation = ux.annotation -const anykey = ux.anykey -const confirm = ux.confirm -const debug = ux.debug -const done = ux.done -const error = Errors.error -const exit = Errors.exit -const flush = ux.flush -const info = ux.info -const log = ux.log -const progress = ux.progress -const prompt = ux.prompt -const styledHeader = ux.styledHeader -const styledJSON = ux.styledJSON -const styledObject = ux.styledObject -const table = ux.table -const trace = ux.trace -const tree = ux.tree -const url = ux.url -const wait = ux.wait -const warn = Errors.warn +const { + action, + annotation, + anykey, + confirm, + debug, + done, + flush, + info, + log, + progress, + prompt, + styledHeader, + styledJSON, + styledObject, + table, + trace, + tree, + url, + wait, +} = ux +const {error, exit, warn} = Errors export { action, - ActionBase, annotation, anykey, - config, - Config, confirm, debug, done, error, exit, - ExitError, flush, info, - IPromptOptions, log, progress, prompt, @@ -159,7 +151,6 @@ export { styledJSON, styledObject, table, - Table, trace, tree, url, @@ -182,3 +173,9 @@ const uxListener = process.listeners('exit').find(fn => fn.name === uxProcessExi if (!uxListener) { process.once('exit', uxProcessExitHandler) } + +export {ExitError} from './exit' +export {IPromptOptions} from './prompt' +export {Table} from './styled' +export {ActionBase} from './action/base' +export {config, Config} from './config' diff --git a/src/cli-ux/list.ts b/src/cli-ux/list.ts index 4a0306b36..46773cb7d 100644 --- a/src/cli-ux/list.ts +++ b/src/cli-ux/list.ts @@ -1,5 +1,5 @@ -import {stdtermwidth} from '../screen' import {maxBy} from '../util' +import {stdtermwidth} from '../screen' const wordwrap = require('wordwrap') function linewrap(length: number, s: string): string { diff --git a/src/cli-ux/prompt.ts b/src/cli-ux/prompt.ts index a3a8c0d11..9af137f84 100644 --- a/src/cli-ux/prompt.ts +++ b/src/cli-ux/prompt.ts @@ -1,7 +1,6 @@ import * as Errors from '../errors' -import config from './config' - -import * as chalk from 'chalk' +import chalk from 'chalk' +import {config} from './config' import {stderr} from './stream' export interface IPromptOptions { @@ -77,8 +76,8 @@ async function single(options: IPromptConfig): Promise { function replacePrompt(prompt: string) { const ansiEscapes = require('ansi-escapes') - stderr.write(ansiEscapes.cursorHide + ansiEscapes.cursorUp(1) + ansiEscapes.cursorLeft + prompt + - ansiEscapes.cursorDown(1) + ansiEscapes.cursorLeft + ansiEscapes.cursorShow) + stderr.write(ansiEscapes.cursorHide + ansiEscapes.cursorUp(1) + ansiEscapes.cursorLeft + prompt + + ansiEscapes.cursorDown(1) + ansiEscapes.cursorLeft + ansiEscapes.cursorShow) } async function _prompt(name: string, inputOptions: Partial = {}): Promise { @@ -95,11 +94,15 @@ async function _prompt(name: string, inputOptions: Partial = {}) const passwordPrompt = require('password-prompt') switch (options.type) { - case 'normal': + case 'normal': { return normal(options) - case 'single': + } + + case 'single': { return single(options) - case 'mask': + } + + case 'mask': { return passwordPrompt(options.prompt, { method: options.type, required: options.required, @@ -108,15 +111,20 @@ async function _prompt(name: string, inputOptions: Partial = {}) replacePrompt(getPrompt(name, 'hide', inputOptions.default)) return value }) - case 'hide': + } + + case 'hide': { return passwordPrompt(options.prompt, { method: options.type, required: options.required, default: options.default, }) - default: + } + + default: { throw new Error(`unexpected type ${options.type}`) } + } } /** @@ -126,9 +134,7 @@ async function _prompt(name: string, inputOptions: Partial = {}) * @returns Promise */ export async function prompt(name: string, options: IPromptOptions = {}): Promise { - return config.action.pauseAsync(() => { - return _prompt(name, options) - }, chalk.cyan('?')) + return config.action.pauseAsync(() => _prompt(name, options), chalk.cyan('?')) } /** @@ -139,7 +145,8 @@ export async function prompt(name: string, options: IPromptOptions = {}): Promis export function confirm(message: string): Promise { return config.action.pauseAsync(async () => { const confirm = async (): Promise => { - const response = (await _prompt(message)).toLowerCase() + const raw = await _prompt(message) + const response = raw.toLowerCase() if (['n', 'no'].includes(response)) return false if (['y', 'yes'].includes(response)) return true return confirm() @@ -157,9 +164,9 @@ export function confirm(message: string): Promise { export async function anykey(message?: string): Promise { const tty = Boolean(process.stdin.setRawMode) if (!message) { - message = tty ? - `Press any key to continue or ${chalk.yellow('q')} to exit` : - `Press enter to continue or ${chalk.yellow('q')} to exit` + message = tty + ? `Press any key to continue or ${chalk.yellow('q')} to exit` + : `Press enter to continue or ${chalk.yellow('q')} to exit` } const char = await prompt(message, {type: 'single', required: false}) diff --git a/src/cli-ux/styled/index.ts b/src/cli-ux/styled/index.ts index 291c51575..93ce7faa2 100644 --- a/src/cli-ux/styled/index.ts +++ b/src/cli-ux/styled/index.ts @@ -1,13 +1,6 @@ -import styledJSON from './json' -import styledObject from './object' -import * as Table from './table' -import tree from './tree' -import progress from './progress' -export { - styledJSON, - styledObject, - Table, - tree, - progress, -} +export * as Table from './table' +export {default as progress} from './progress' +export {default as styledJSON} from './json' +export {default as styledObject} from './object' +export {default as tree} from './tree' diff --git a/src/cli-ux/styled/json.ts b/src/cli-ux/styled/json.ts index 6da73b23c..c7db532d0 100644 --- a/src/cli-ux/styled/json.ts +++ b/src/cli-ux/styled/json.ts @@ -1,4 +1,4 @@ -import * as chalk from 'chalk' +import chalk from 'chalk' import {ux} from '../../index' diff --git a/src/cli-ux/styled/object.ts b/src/cli-ux/styled/object.ts index fd872ef79..96345c016 100644 --- a/src/cli-ux/styled/object.ts +++ b/src/cli-ux/styled/object.ts @@ -1,7 +1,6 @@ -import * as chalk from 'chalk' -import * as util from 'util' +import chalk from 'chalk' +import {inspect} from 'node:util' -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export default function styledObject(obj: any, keys?: string[]): string { const output: string[] = [] const keyLengths = Object.keys(obj).map(key => key.toString().length) @@ -10,16 +9,14 @@ export default function styledObject(obj: any, keys?: string[]): string { if (typeof obj === 'string' || typeof obj === 'number') return obj if (typeof obj === 'object') { return Object.keys(obj) - .map(k => k + ': ' + util.inspect(obj[k])) + .map(k => k + ': ' + inspect(obj[k])) .join(', ') } - return util.inspect(obj) + return inspect(obj) } - const logKeyValue = (key: string, value: any): string => { - return `${chalk.blue(key)}:` + ' '.repeat(maxKeyLength - key.length - 1) + pp(value) - } + const logKeyValue = (key: string, value: any): string => `${chalk.blue(key)}:` + ' '.repeat(maxKeyLength - key.length - 1) + pp(value) for (const key of keys || Object.keys(obj).sort()) { const value = obj[key] diff --git a/src/cli-ux/styled/table.ts b/src/cli-ux/styled/table.ts index 04f8b0cf2..5d173d268 100644 --- a/src/cli-ux/styled/table.ts +++ b/src/cli-ux/styled/table.ts @@ -1,15 +1,14 @@ -import * as Interfaces from '../../interfaces' import * as F from '../../flags' -import {stdtermwidth} from '../../screen' -import * as chalk from 'chalk' +import * as Interfaces from '../../interfaces' import {capitalize, sumBy} from '../../util' +import chalk from 'chalk' +import {inspect} from 'node:util' +import {orderBy} from 'natural-orderby' import {safeDump} from 'js-yaml' -import {inspect} from 'util' +import sliceAnsi from 'slice-ansi' import {stdout} from '../stream' - -const sw = require('string-width') -const {orderBy} = require('natural-orderby') -const sliceAnsi = require('slice-ansi') +import {stdtermwidth} from '../../screen' +import sw from 'string-width' class Table> { options: table.Options & { printLine(s: any): any } @@ -23,7 +22,7 @@ class Table> { const extended = col.extended ?? false // turn null and undefined into empty strings by default const get = col.get ?? ((row: any) => row[key] ?? '') - const header = typeof col.header === 'string' ? col.header : capitalize(key.replace(/_/g, ' ')) + const header = typeof col.header === 'string' ? col.header : capitalize(key.replaceAll('_', ' ')) const minWidth = Math.max(col.minWidth ?? 0, sw(header) + 1) return { @@ -66,7 +65,6 @@ class Table> { // filter rows if (this.options.filter) { - /* eslint-disable-next-line prefer-const */ let [header, regex] = this.options.filter!.split('=') const isNot = header[0] === '-' if (isNot) header = header.slice(1) @@ -84,9 +82,7 @@ class Table> { if (this.options.sort) { const sorters = this.options.sort!.split(',') const sortHeaders = sorters.map(k => k[0] === '-' ? k.slice(1) : k) - const sortKeys = this.filterColumnsFromHeaders(sortHeaders).map(c => { - return ((v: any) => v[c.key]) - }) + const sortKeys = this.filterColumnsFromHeaders(sortHeaders).map(c => ((v: any) => v[c.key])) const sortKeysOrder = sorters.map(k => k[0] === '-' ? 'desc' : 'asc') rows = orderBy(rows, sortKeys, sortKeysOrder) } @@ -103,18 +99,25 @@ class Table> { this.data = rows switch (this.options.output) { - case 'csv': + case 'csv': { this.outputCSV() break - case 'json': + } + + case 'json': { this.outputJSON() break - case 'yaml': + } + + case 'yaml': { this.outputYAML() break - default: + } + + default: { this.outputTable() } + } } private findColumnFromHeader(header: string): (table.Column & { key: string; width?: number; maxWidth?: number }) | undefined { @@ -141,15 +144,10 @@ class Table> { private resolveColumnsToObjectArray() { const {data, columns} = this - return data.map((d: any) => { - // eslint-disable-next-line unicorn/prefer-object-from-entries - return columns.reduce((obj, col) => { - return { - ...obj, - [col.key]: d[col.key] ?? '', - } - }, {}) - }) + return data.map((d: any) => + + Object.fromEntries(columns.map(col => [col.key, d[col.key] ?? ''])), + ) } private outputJSON() { @@ -338,7 +336,7 @@ export namespace table { export function flags(): IFlags export function flags(opts: { except: Z | Z[] }): ExcludeFlags export function flags(opts: { only: K | K[] }): IncludeFlags - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + export function flags(opts?: any): any { if (opts) { const f = {} @@ -380,12 +378,10 @@ export namespace table { } } -const getWidestColumnWith = (data: any[], columnKey: string): number => { - return data.reduce((previous, current) => { - const d = current[columnKey] - // convert multi-line cell to single longest line - // for width calculations - const manyLines = (d as string).split('\n') - return Math.max(previous, manyLines.length > 1 ? Math.max(...manyLines.map((r: string) => sw(r))) : sw(d)) - }, 0) -} +const getWidestColumnWith = (data: any[], columnKey: string): number => data.reduce((previous, current) => { + const d = current[columnKey] + // convert multi-line cell to single longest line + // for width calculations + const manyLines = (d as string).split('\n') + return Math.max(previous, manyLines.length > 1 ? Math.max(...manyLines.map((r: string) => sw(r))) : sw(d)) +}, 0) diff --git a/src/cli-ux/wait.ts b/src/cli-ux/wait.ts index 3d5560c4b..7514385ff 100644 --- a/src/cli-ux/wait.ts +++ b/src/cli-ux/wait.ts @@ -1,5 +1,3 @@ -export default (ms = 1000): Promise => { - return new Promise(resolve => { - setTimeout(resolve, ms) - }) -} +export default (ms = 1000): Promise => new Promise(resolve => { + setTimeout(resolve, ms) +}) diff --git a/src/command.ts b/src/command.ts index 1bccd6ef6..7a54e2136 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1,33 +1,34 @@ -import {fileURLToPath} from 'url' -import * as chalk from 'chalk' -import {format, inspect} from 'util' -import {ux} from './cli-ux' -import {Config} from './config' + import * as Errors from './errors' -import {PrettyPrintableError} from './errors' import * as Parser from './parser' import { + ArgInput, + ArgOutput, + ArgProps, BooleanFlagProps, - CompletableFlag, Deprecation, - Arg as IArg, - ArgInput, FlagInput, FlagOutput, + Arg as IArg, + Flag as IFlag, Input, - ArgProps, OptionFlagProps, ParserOutput, - ArgOutput, } from './interfaces/parser' -import {formatCommandDeprecationWarning, formatFlagDeprecationWarning, toConfiguredId, normalizeArgv} from './help/util' -import {Plugin} from './interfaces/plugin' -import {LoadOptions} from './interfaces/config' +import {format, inspect} from 'node:util' +import {formatCommandDeprecationWarning, formatFlagDeprecationWarning, normalizeArgv, toConfiguredId} from './help/util' +import {requireJson, uniq} from './util' +import {stderr, stdout} from './cli-ux/stream' import {CommandError} from './interfaces/errors' -import {boolean} from './flags' -import {requireJson} from './util' +import {Config} from './config' +import {LoadOptions} from './interfaces/config' import {PJSON} from './interfaces' -import {stdout, stderr} from './cli-ux/stream' +import {Plugin} from './interfaces/plugin' +import {PrettyPrintableError} from './errors' +import {boolean} from './flags' +import chalk from 'chalk' +import {fileURLToPath} from 'node:url' +import {ux} from './cli-ux' const pjson = requireJson(__dirname, '..', 'package.json') @@ -63,7 +64,7 @@ export abstract class Command { * The tweet-sized description for your class, used in a parent-commands * sub-command listing and as the header for the command help. */ - public static summary?: string; + public static summary?: string /** * A full description of how to use the command. @@ -76,9 +77,9 @@ export abstract class Command { public static hidden: boolean /** Mark the command as a given state (e.g. beta or deprecated) in help */ - public static state?: 'beta' | 'deprecated' | string; + public static state?: 'beta' | 'deprecated' | string - public static deprecationOptions?: Deprecation; + public static deprecationOptions?: Deprecation /** * Emit deprecation warning when a command alias is used @@ -103,9 +104,9 @@ export abstract class Command { public static plugin: Plugin | undefined - public static readonly pluginName?: string; - public static readonly pluginType?: string; - public static readonly pluginAlias?: string; + public static readonly pluginName?: string + public static readonly pluginType?: string + public static readonly pluginAlias?: string /** * An array of examples to show at the end of the command's help. @@ -190,6 +191,7 @@ export abstract class Command { } static set baseFlags(flags: FlagInput) { + // eslint-disable-next-line prefer-object-spread this._baseFlags = Object.assign({}, this.baseFlags, flags) this.flags = {} // force the flags setter to run } @@ -202,6 +204,7 @@ export abstract class Command { } public static set flags(flags: FlagInput) { + // eslint-disable-next-line prefer-object-spread this._flags = Object.assign({}, this._flags ?? {}, this.baseFlags, flags) } @@ -214,7 +217,9 @@ export abstract class Command { try { this.debug = require('debug')(this.id ? `${this.config.bin}:${this.id}` : this.config.bin) } catch { - this.debug = () => {} + this.debug = () => { + // noop + } } } @@ -315,8 +320,10 @@ export abstract class Command { } const deprecateAliases = flagDef?.deprecateAliases - const aliases = (flagDef?.aliases ?? []).map(a => a.length === 1 ? `-${a}` : `--${a}`) - if (deprecateAliases && aliases.length > 0) { + if (deprecateAliases) { + const aliases = uniq([...flagDef?.aliases ?? [], ...flagDef?.charAliases ?? []]).map(a => a.length === 1 ? `-${a}` : `--${a}`) + if (aliases.length === 0) return + const foundAliases = aliases.filter(alias => this.argv.some(a => a.startsWith(alias))) for (const alias of foundAliases) { let preferredUsage = `--${flagDef?.name}` @@ -373,7 +380,7 @@ export abstract class Command { protected async finally(_: Error | undefined): Promise { try { - const config = Errors.config + const {config} = Errors if (config.errorLogger) await config.errorLogger.flush() } catch (error: any) { console.error(error) @@ -435,9 +442,11 @@ export namespace Command { hasDynamicHelp?: boolean; permutations?: string[] aliasPermutations?: string[]; + isESM?: boolean; + relativePath?: string[]; } - export type Flag = CompletableFlag + export type Flag = IFlag export namespace Flag { export type Cached = Omit & (BooleanFlagProps | OptionFlagProps) diff --git a/src/config/config.ts b/src/config/config.ts index 6ed2583a7..83428679a 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,23 +1,23 @@ -import {fileURLToPath, URL} from 'node:url' -import {format} from 'node:util' -import {userInfo as osUserInfo, arch, platform, homedir, tmpdir, type, release} from 'node:os' -import {sep, join} from 'node:path' - import * as ejs from 'ejs' +import {ArchTypes, Config as IConfig, LoadOptions, PlatformTypes, VersionDetails} from '../interfaces/config' +import {Arg, OptionFlag} from '../interfaces/parser' import {CLIError, error, exit, warn} from '../errors' -import {Options, Plugin as IPlugin} from '../interfaces/plugin' -import {Config as IConfig, ArchTypes, PlatformTypes, LoadOptions, VersionDetails} from '../interfaces/config' +import {Debug, collectUsableIds, getCommandIdPermutations} from './util' import {Hook, Hooks, PJSON, Topic} from '../interfaces' -import {Debug, compact, collectUsableIds, getCommandIdPermutations} from './util' -import {ensureArgObject, isProd, requireJson} from '../util' -import ModuleLoader from '../module-loader' -import {getHelpFlagAdditions} from '../help' +import {Plugin as IPlugin, Options} from '../interfaces/plugin' +import {URL, fileURLToPath} from 'node:url' +import {arch, userInfo as osUserInfo, release, tmpdir, type} from 'node:os' +import {compact, ensureArgObject, getHomeDir, getPlatform, isProd, requireJson} from '../util' +import {join, sep} from 'node:path' + import {Command} from '../command' -import {CompletableOptionFlag, Arg} from '../interfaces/parser' -import {stdout} from '../cli-ux/stream' -import Performance from '../performance' -import {settings} from '../settings' +import {Performance} from '../performance' import PluginLoader from './plugin-loader' +import {format} from 'node:util' +import {getHelpFlagAdditions} from '../help' +import {loadWithData} from '../module-loader' +import {settings} from '../settings' +import {stdout} from '../cli-ux/stream' // eslint-disable-next-line new-cap const debug = Debug() @@ -95,8 +95,8 @@ export class Config implements IConfig { public valid!: boolean public version!: string public windows!: boolean - public binAliases?: string[]; - public nsisCustomization?:string; + public binAliases?: string[] + public nsisCustomization?:string protected warned = false @@ -155,6 +155,7 @@ export class Config implements IConfig { // eslint-disable-next-line complexity public async load(): Promise { settings.performanceEnabled = (settings.performanceEnabled === undefined ? this.options.enablePerf : settings.performanceEnabled) ?? false + const marker = Performance.mark('config.load') this.pluginLoader = new PluginLoader({root: this.options.root, plugins: this.options.plugins}) Config._rootPlugin = await this.pluginLoader.loadRoot() @@ -170,7 +171,7 @@ export class Config implements IConfig { this.valid = Config._rootPlugin.valid this.arch = (arch() === 'ia32' ? 'x86' : arch() as any) - this.platform = WSL ? 'wsl' : platform() as any + this.platform = WSL ? 'wsl' : getPlatform() this.windows = this.platform === 'win32' this.bin = this.pjson.oclif.bin || this.name this.binAliases = this.pjson.oclif.binAliases @@ -184,7 +185,7 @@ export class Config implements IConfig { this.shell = this._shell() this.debug = this._debug() - this.home = process.env.HOME || (this.windows && this.windowsHome()) || homedir() || tmpdir() + this.home = process.env.HOME || (this.windows && this.windowsHome()) || getHomeDir() || tmpdir() this.cacheDir = this.scopedEnvVar('CACHE_DIR') || this.macosCacheDir() || this.dir('cache') this.configDir = this.scopedEnvVar('CONFIG_DIR') || this.dir('config') this.dataDir = this.scopedEnvVar('DATA_DIR') || this.dir('data') @@ -217,8 +218,6 @@ export class Config implements IConfig { }, } - const marker = Performance.mark('config.load') - await this.loadPluginsAndCommands() debug('config done') @@ -232,7 +231,7 @@ export class Config implements IConfig { } async loadPluginsAndCommands(opts?: {force: boolean}): Promise { - const marker = Performance.mark('config.loadPluginsAndCommands') + const pluginsMarker = Performance.mark('config.loadAllPlugins') const {plugins, errors} = await this.pluginLoader.loadChildren({ devPlugins: this.options.devPlugins, userPlugins: this.options.userPlugins, @@ -242,16 +241,19 @@ export class Config implements IConfig { }) this.plugins = plugins + pluginsMarker?.stop() + + const commandsMarker = Performance.mark('config.loadAllCommands') for (const plugin of this.plugins.values()) { this.loadCommands(plugin) this.loadTopics(plugin) } + commandsMarker?.stop() + for (const error of errors) { this.warn(error) } - - marker?.stop() } public async runHook( @@ -311,13 +313,12 @@ export class Config implements IConfig { const marker = Performance.mark(`config.runHook#${p.name}(${hook})`) try { /* eslint-disable no-await-in-loop */ - const {isESM, module, filePath} = await ModuleLoader.loadWithData(p, hook) - + const {isESM, module, filePath} = await loadWithData(p, join(p.root, hook)) debug('start', isESM ? '(import)' : '(require)', filePath) - const result = timeout ? - await withTimeout(timeout, search(module).call(context, {...opts as any, config: this})) : - await search(module).call(context, {...opts as any, config: this}) + const result = timeout + ? await withTimeout(timeout, search(module).call(context, {...opts as any, config: this})) + : await search(module).call(context, {...opts as any, config: this}) final.successes.push({plugin: p, result}) if (p.name === '@oclif/plugin-legacy' && event === 'init') { @@ -354,9 +355,9 @@ export class Config implements IConfig { let c = cachedCommand ?? this.findCommand(id) if (!c) { const matches = this.flexibleTaxonomy ? this.findMatches(id, argv) : [] - const hookResult = this.flexibleTaxonomy && matches.length > 0 ? - await this.runHook('command_incomplete', {id, argv, matches}) : - await this.runHook('command_not_found', {id, argv}) + const hookResult = this.flexibleTaxonomy && matches.length > 0 + ? await this.runHook('command_incomplete', {id, argv, matches}) + : await this.runHook('command_not_found', {id, argv}) if (hookResult.successes[0]) return hookResult.successes[0].result as T if (hookResult.failures[0]) throw hookResult.failures[0].error @@ -413,7 +414,7 @@ export class Config implements IConfig { */ public scopedEnvVarKey(k: string): string { return [this.bin, k] - .map(p => p.replace(/@/g, '').replace(/[/-]/g, '_')) + .map(p => p.replaceAll('@', '').replaceAll(/[/-]/g, '_')) .join('_') .toUpperCase() } @@ -424,8 +425,8 @@ export class Config implements IConfig { * @returns {string[]} e.g. ['SF_DEBUG', 'SFDX_DEBUG'] */ public scopedEnvVarKeys(k: string): string[] { - return [this.bin, ...this.binAliases ?? []].filter(alias => Boolean(alias)).map(alias => - [alias.replace(/@/g, '').replace(/[/-]/g, '_'), k].join('_').toUpperCase()) + return [this.bin, ...this.binAliases ?? []].filter(Boolean).map(alias => + [alias.replaceAll('@', '').replaceAll(/[/-]/g, '_'), k].join('_').toUpperCase()) } public findCommand(id: string, opts: { must: true }): Command.Loadable @@ -464,13 +465,11 @@ export class Config implements IConfig { * @returns string[] */ public findMatches(partialCmdId: string, argv: string[]): Command.Loadable[] { - const flags = argv.filter(arg => !getHelpFlagAdditions(this).includes(arg) && arg.startsWith('-')).map(a => a.replace(/-/g, '')) + const flags = argv.filter(arg => !getHelpFlagAdditions(this).includes(arg) && arg.startsWith('-')).map(a => a.replaceAll('-', '')) const possibleMatches = [...this.commandPermutations.get(partialCmdId)].map(k => this._commands.get(k)!) const matches = possibleMatches.filter(command => { - const cmdFlags = Object.entries(command.flags).flatMap(([flag, def]) => { - return def.char ? [def.char, flag] : [flag] - }) as string[] + const cmdFlags = Object.entries(command.flags).flatMap(([flag, def]) => def.char ? [def.char, flag] : [flag]) as string[] // A command is a match if the provided flags belong to the full command return flags.every(f => cmdFlags.includes(f)) @@ -539,7 +538,7 @@ export class Config implements IConfig { } public s3Url(key: string): string { - const host = this.pjson.oclif.update.s3.host + const {host} = this.pjson.oclif.update.s3 if (!host) throw new Error('no s3 host is set') const url = new URL(host) url.pathname = join(url.pathname, key) @@ -551,9 +550,9 @@ export class Config implements IConfig { } protected dir(category: 'cache' | 'data' | 'config'): string { - const base = process.env[`XDG_${category.toUpperCase()}_HOME`] || - (this.windows && process.env.LOCALAPPDATA) || - join(this.home, category === 'data' ? '.local/share' : '.' + category) + const base = process.env[`XDG_${category.toUpperCase()}_HOME`] + || (this.windows && process.env.LOCALAPPDATA) + || join(this.home, category === 'data' ? '.local/share' : '.' + category) return join(base, this.dirname) } @@ -575,7 +574,7 @@ export class Config implements IConfig { protected _shell(): string { let shellPath - const COMSPEC = process.env.COMSPEC + const {COMSPEC} = process.env const SHELL = process.env.SHELL ?? osUserInfo().shell?.split(sep)?.pop() if (SHELL) { shellPath = SHELL.split('/') @@ -585,7 +584,7 @@ export class Config implements IConfig { shellPath = ['unknown'] } - return shellPath[shellPath.length - 1] + return shellPath.at(-1) ?? 'unknown' } protected _debug(): number { @@ -668,8 +667,13 @@ export class Config implements IConfig { this._commands.set(command.id, command) } + // v3 moved command id permutations to the manifest, but some plugins may not have + // the new manifest yet. For those, we need to calculate the permutations here. + const permutations = this.flexibleTaxonomy && command.permutations === undefined + ? getCommandIdPermutations(command.id) + : command.permutations ?? [command.id] // set every permutation - for (const permutation of command.permutations ?? [command.id]) { + for (const permutation of permutations) { this.commandPermutations.add(permutation, command.id) } @@ -683,7 +687,14 @@ export class Config implements IConfig { } // set every permutation of the aliases - for (const permutation of command.aliasPermutations ?? [alias]) { + + // v3 moved command alias permutations to the manifest, but some plugins may not have + // the new manifest yet. For those, we need to calculate the permutations here. + const aliasPermutations = this.flexibleTaxonomy && command.aliasPermutations === undefined + ? getCommandIdPermutations(alias) + : command.permutations ?? [alias] + // set every permutation + for (const permutation of aliasPermutations) { this.commandPermutations.add(permutation, command.id) } } @@ -802,7 +813,7 @@ export class Config implements IConfig { } // when no manifest exists, the default is calculated. This may throw, so we need to catch it -const defaultFlagToCached = async (flag: CompletableOptionFlag, respectNoCacheDefault: boolean) => { +const defaultFlagToCached = async (flag: OptionFlag, respectNoCacheDefault: boolean) => { if (respectNoCacheDefault && flag.noCacheDefault) return // Prefer the defaultHelp function (returns a friendly string for complex types) if (typeof flag.defaultHelp === 'function') { @@ -866,6 +877,7 @@ export async function toCached(c: Command.Class, plugin?: IPlugin, respectNoCach deprecated: flag.deprecated, deprecateAliases: c.deprecateAliases, aliases: flag.aliases, + charAliases: flag.charAliases, delimiter: flag.delimiter, noCacheDefault: flag.noCacheDefault, } @@ -890,6 +902,7 @@ export async function toCached(c: Command.Class, plugin?: IPlugin, respectNoCach deprecated: flag.deprecated, deprecateAliases: c.deprecateAliases, aliases: flag.aliases, + charAliases: flag.charAliases, delimiter: flag.delimiter, noCacheDefault: flag.noCacheDefault, } diff --git a/src/config/plugin-loader.ts b/src/config/plugin-loader.ts index 4da7520c2..7965a08bb 100644 --- a/src/config/plugin-loader.ts +++ b/src/config/plugin-loader.ts @@ -1,10 +1,10 @@ -import * as path from 'path' - -import {Options, Plugin as IPlugin} from '../interfaces/plugin' import * as Plugin from './plugin' -import {loadJSON, Debug} from './util' -import {isProd} from '../util' -import Performance from '../performance' +import {Plugin as IPlugin, Options} from '../interfaces/plugin' +import {isProd, readJson} from '../util' +import {Debug} from './util' +import {PJSON} from '../interfaces' +import {Performance} from '../performance' +import {join} from 'node:path' // eslint-disable-next-line new-cap const debug = Debug() @@ -42,8 +42,18 @@ export default class PluginLoader { const plugins = [...this.plugins.values()] rootPlugin = plugins.find(p => p.root === this.options.root) ?? plugins[0] } else { + const marker = Performance.mark('plugin.load#root') rootPlugin = new Plugin.Plugin({root: this.options.root}) await rootPlugin.load() + marker?.addDetails({ + hasManifest: rootPlugin.hasManifest ?? false, + commandCount: rootPlugin.commands.length, + topicCount: rootPlugin.topics.length, + type: rootPlugin.type, + usesMain: Boolean(rootPlugin.pjson.main), + name: rootPlugin.name, + }) + marker?.stop() } this.plugins.set(rootPlugin.name, rootPlugin) @@ -71,7 +81,7 @@ export default class PluginLoader { // do not load oclif.devPlugins in production if (isProd()) return try { - const devPlugins = opts.rootPlugin.pjson.oclif.devPlugins + const {devPlugins} = opts.rootPlugin.pjson.oclif if (devPlugins) await this.loadPlugins(opts.rootPlugin.root, 'dev', devPlugins) } catch (error: any) { process.emitWarning(error) @@ -82,9 +92,9 @@ export default class PluginLoader { private async loadUserPlugins(opts: LoadOpts): Promise { if (opts.userPlugins !== false) { try { - const userPJSONPath = path.join(opts.dataDir, 'package.json') + const userPJSONPath = join(opts.dataDir, 'package.json') debug('reading user plugins pjson %s', userPJSONPath) - const pjson = await loadJSON(userPJSONPath) + const pjson = await readJson(userPJSONPath) if (!pjson.oclif) pjson.oclif = {schema: 1} if (!pjson.oclif.plugins) pjson.oclif.plugins = [] await this.loadPlugins(userPJSONPath, 'user', pjson.oclif.plugins.filter((p: any) => p.type === 'user')) @@ -112,6 +122,10 @@ export default class PluginLoader { opts.root = plugin.root || opts.root } + if (parent) { + opts.parent = parent + } + if (this.plugins.has(name)) return const pluginMarker = Performance.mark(`plugin.load#${name}`) const instance = new Plugin.Plugin(opts) diff --git a/src/config/plugin.ts b/src/config/plugin.ts index 0d3f988b2..8f623db75 100644 --- a/src/config/plugin.ts +++ b/src/config/plugin.ts @@ -1,20 +1,24 @@ import {CLIError, error} from '../errors' -import * as globby from 'globby' -import * as path from 'path' -import {inspect} from 'util' - +import { + Debug, + flatMap, + getCommandIdPermutations, + mapValues, + resolvePackage, +} from './util' import {Plugin as IPlugin, PluginOptions} from '../interfaces/plugin' -import {toCached} from './config' -import {Debug, getCommandIdPermutations} from './util' +import {compact, exists, isProd, readJson, requireJson} from '../util' +import {dirname, join, parse, relative, sep} from 'node:path' +import {loadWithData, loadWithDataFromManifest} from '../module-loader' +import {Command} from '../command' import {Manifest} from '../interfaces/manifest' import {PJSON} from '../interfaces/pjson' +import {Performance} from '../performance' import {Topic} from '../interfaces/topic' +import {inspect} from 'node:util' +import {sync} from 'globby' +import {toCached} from './config' import {tsPath} from './ts-node' -import {compact, exists, resolvePackage, flatMap, loadJSON, mapValues} from './util' -import {isProd, requireJson} from '../util' -import ModuleLoader from '../module-loader' -import {Command} from '../command' -import Performance from '../performance' const _pjson = requireJson(__dirname, '..', '..', 'package.json') @@ -33,9 +37,9 @@ function topicsToArray(input: any, base?: string): Topic[] { // essentially just "cd .." function * up(from: string) { - while (path.dirname(from) !== from) { + while (dirname(from) !== from) { yield from - from = path.dirname(from) + from = dirname(from) } yield from @@ -43,9 +47,9 @@ function * up(from: string) { async function findSourcesRoot(root: string) { for (const next of up(root)) { - const cur = path.join(next, 'package.json') + const cur = join(next, 'package.json') // eslint-disable-next-line no-await-in-loop - if (await exists(cur)) return path.dirname(cur) + if (await exists(cur)) return dirname(cur) } } @@ -64,18 +68,18 @@ async function findRootLegacy(name: string | undefined, root: string): Promise(join(next, 'package.json')) if (pkg.name === name) return next } catch {} } else { - cur = path.join(next, 'package.json') + cur = join(next, 'package.json') // eslint-disable-next-line no-await-in-loop - if (await exists(cur)) return path.dirname(cur) + if (await exists(cur)) return dirname(cur) } } } @@ -87,12 +91,21 @@ async function findRoot(name: string | undefined, root: string) { pkgPath = resolvePackage(name, {paths: [root]}) } catch {} - return pkgPath ? findSourcesRoot(path.dirname(pkgPath)) : findRootLegacy(name, root) + return pkgPath ? findSourcesRoot(dirname(pkgPath)) : findRootLegacy(name, root) } return findSourcesRoot(root) } +const cachedCommandCanBeUsed = (manifest: Manifest | undefined, id: string): boolean => + Boolean(manifest?.commands[id] && ('isESM' in manifest.commands[id] && 'relativePath' in manifest.commands[id])) + +const search = (cmd: any) => { + if (typeof cmd.run === 'function') return cmd + if (cmd.default && cmd.default.run) return cmd.default + return Object.values(cmd).find((cmd: any) => typeof cmd.run === 'function') +} + export class Plugin implements IPlugin { _base = `${_pjson.name}@${_pjson.version}` @@ -142,16 +155,20 @@ export class Plugin implements IPlugin { public async load(): Promise { this.type = this.options.type || 'core' this.tag = this.options.tag - const root = await findRoot(this.options.name, this.options.root) + if (this.options.parent) this.parent = this.options.parent as Plugin + // Linked plugins already have a root so there's no need to search for it. + // However there could be child plugins nested inside the linked plugin, in which + // case we still need to search for the child plugin's root. + const root = this.type === 'link' && !this.parent ? this.options.root : await findRoot(this.options.name, this.options.root) if (!root) throw new CLIError(`could not find package.json with ${inspect(this.options)}`) this.root = root this._debug('reading %s plugin %s', this.type, root) - this.pjson = await loadJSON(path.join(root, 'package.json')) + this.pjson = await readJson(join(root, 'package.json')) this.flexibleTaxonomy = this.options?.flexibleTaxonomy || this.pjson.oclif?.flexibleTaxonomy || false this.moduleType = this.pjson.type === 'module' ? 'module' : 'commonjs' this.name = this.pjson.name this.alias = this.options.name ?? this.pjson.name - const pjsonPath = path.join(root, 'package.json') + const pjsonPath = join(root, 'package.json') if (!this.name) throw new CLIError(`no name in ${pjsonPath}`) if (!isProd() && !this.pjson.files) this.warn(`files attribute must be specified in ${pjsonPath}`) // eslint-disable-next-line new-cap @@ -197,12 +214,12 @@ export class Plugin implements IPlugin { '**/*.+(js|cjs|mjs|ts|tsx)', '!**/*.+(d.ts|test.ts|test.js|spec.ts|spec.js)?(x)', ] - const ids = globby.sync(patterns, {cwd: this.commandsDir}) + const ids = sync(patterns, {cwd: this.commandsDir}) .map(file => { - const p = path.parse(file) + const p = parse(file) const topics = p.dir.split('/') const command = p.name !== 'index' && p.name - const id = [...topics, command].filter(f => f).join(':') + const id = [...topics, command].filter(Boolean).join(':') return id === '' ? '.' : id }) this._debug('found commands', ids) @@ -217,29 +234,28 @@ export class Plugin implements IPlugin { public async findCommand(id: string, opts: {must?: boolean} = {}): Promise { const marker = Performance.mark(`plugin.findCommand#${this.name}.${id}`, {id, plugin: this.name}) + const fetch = async () => { if (!this.commandsDir) return - const search = (cmd: any) => { - if (typeof cmd.run === 'function') return cmd - if (cmd.default && cmd.default.run) return cmd.default - return Object.values(cmd).find((cmd: any) => typeof cmd.run === 'function') - } - - let m + let module + let isESM: boolean | undefined + let filePath: string | undefined try { - const p = path.join(this.commandsDir ?? this.pjson.oclif.commands, ...id.split(':')) - const {isESM, module, filePath} = await ModuleLoader.loadWithData(this, p) + ({isESM, module, filePath} = cachedCommandCanBeUsed(this.manifest, id) + ? await loadWithDataFromManifest(this.manifest.commands[id], this.root) + : await loadWithData(this, join(this.commandsDir ?? this.pjson.oclif.commands, ...id.split(':')))) this._debug(isESM ? '(import)' : '(require)', filePath) - m = module } catch (error: any) { if (!opts.must && error.code === 'MODULE_NOT_FOUND') return throw error } - const cmd = search(m) + const cmd = search(module) if (!cmd) return cmd.id = id cmd.plugin = this + cmd.isESM = isESM + cmd.relativePath = relative(this.root, filePath || '').split(sep) return cmd } @@ -256,8 +272,8 @@ export class Plugin implements IPlugin { const readManifest = async (dotfile = false): Promise => { try { - const p = path.join(this.root, `${dotfile ? '.' : ''}oclif.manifest.json`) - const manifest: Manifest = await loadJSON(p) + const p = join(this.root, `${dotfile ? '.' : ''}oclif.manifest.json`) + const manifest = await readJson(p) if (!process.env.OCLIF_NEXT_VERSION && manifest.version.split('-')[0] !== this.version.split('-')[0]) { process.emitWarning(`Mismatched version in ${this.name} plugin manifest. Expected: ${this.version} Received: ${manifest.version}\nThis usually means you have an oclif.manifest.json file that should be deleted in development. This file should be automatically generated when publishing.`) } else { @@ -302,6 +318,7 @@ export class Plugin implements IPlugin { else throw this.addErrorScope(error, scope) } }))) + // eslint-disable-next-line unicorn/no-await-expression-member, unicorn/prefer-native-coercion-functions .filter((f): f is [string, Command.Cached] => Boolean(f)) .reduce((commands, [id, c]) => { commands[id] = c diff --git a/src/config/ts-node.ts b/src/config/ts-node.ts index 416cbf708..67093f80e 100644 --- a/src/config/ts-node.ts +++ b/src/config/ts-node.ts @@ -1,39 +1,40 @@ -import * as fs from 'fs' -import * as path from 'path' import * as TSNode from 'ts-node' - -import {TSConfig, Plugin} from '../interfaces' -import {settings} from '../settings' -import {isProd} from '../util' +import {Plugin, TSConfig} from '../interfaces' +import {isProd, readJsonSync} from '../util' +import {join, relative as pathRelative} from 'node:path' +import {Config} from './config' import {Debug} from './util' +import {existsSync} from 'node:fs' import {memoizedWarn} from '../errors' +import {settings} from '../settings' + // eslint-disable-next-line new-cap const debug = Debug('ts-node') -const TS_CONFIGS: Record = {} +export const TS_CONFIGS: Record = {} const REGISTERED = new Set() function loadTSConfig(root: string): TSConfig | undefined { if (TS_CONFIGS[root]) return TS_CONFIGS[root] - const tsconfigPath = path.join(root, 'tsconfig.json') + const tsconfigPath = join(root, 'tsconfig.json') let typescript: typeof import('typescript') | undefined try { typescript = require('typescript') } catch { try { - typescript = require(path.join(root, 'node_modules', 'typescript')) + typescript = require(join(root, 'node_modules', 'typescript')) } catch {} } - if (fs.existsSync(tsconfigPath) && typescript) { + if (existsSync(tsconfigPath) && typescript) { const tsconfig = typescript.parseConfigFileTextToJson( tsconfigPath, - fs.readFileSync(tsconfigPath, 'utf8'), + readJsonSync(tsconfigPath, false), ).config if (!tsconfig || !tsconfig.compilerOptions) { throw new Error( - `Could not read and parse tsconfig.json at ${tsconfigPath}, or it ` + - 'did not contain a "compilerOptions" section.') + `Could not read and parse tsconfig.json at ${tsconfigPath}, or it ` + + 'did not contain a "compilerOptions" section.') } TS_CONFIGS[root] = tsconfig @@ -51,19 +52,19 @@ function registerTSNode(root: string): TSConfig | undefined { const tsNode: typeof TSNode = require(tsNodePath) const typeRoots = [ - path.join(root, 'node_modules', '@types'), + join(root, 'node_modules', '@types'), ] const rootDirs: string[] = [] if (tsconfig.compilerOptions.rootDirs) { for (const r of tsconfig.compilerOptions.rootDirs) { - rootDirs.push(path.join(root, r)) + rootDirs.push(join(root, r)) } } else if (tsconfig.compilerOptions.rootDir) { - rootDirs.push(path.join(root, tsconfig.compilerOptions.rootDir)) + rootDirs.push(join(root, tsconfig.compilerOptions.rootDir)) } else { - rootDirs.push(path.join(root, 'src')) + rootDirs.push(join(root, 'src')) } const conf: TSNode.RegisterOptions = { @@ -111,7 +112,7 @@ export function tsPath(root: string, orig: string, plugin: Plugin): string export function tsPath(root: string, orig: string | undefined, plugin?: Plugin): string | undefined export function tsPath(root: string, orig: string | undefined, plugin?: Plugin): string | undefined { if (!orig) return orig - orig = orig.startsWith(root) ? orig : path.join(root, orig) + orig = orig.startsWith(root) ? orig : join(root, orig) // NOTE: The order of these checks matter! @@ -120,22 +121,28 @@ export function tsPath(root: string, orig: string | undefined, plugin?: Plugin): return orig } - // Skip ts-node registration for ESM plugins. - // The node ecosystem is not mature enough to support auto-transpiling ESM modules at this time. - // See the following: - // - https://github.com/TypeStrong/ts-node/issues/1791#issuecomment-1149754228 - // - https://github.com/nodejs/node/issues/49432 - // - https://github.com/nodejs/node/pull/49407 - // - https://github.com/nodejs/node/issues/34049 - if (plugin?.moduleType === 'module') { - debug(`Skipping ts-node registration for ${root} because it's an ESM module`) + const isProduction = isProd() + /** + * Skip ts-node registration for ESM plugins. + * The node ecosystem is not mature enough to support auto-transpiling ESM modules at this time. + * See the following: + * - https://github.com/TypeStrong/ts-node/issues/1791#issuecomment-1149754228 + * - https://github.com/nodejs/node/issues/49432 + * - https://github.com/nodejs/node/pull/49407 + * - https://github.com/nodejs/node/issues/34049 + * + * We still register ts-node for ESM plugins when NODE_ENV is "test" or "development" and root plugin is also ESM. + * In other words, this allows plugins to be auto-transpiled when developing locally using `bin/dev.js`. + */ + if ((isProduction || Config.rootPlugin?.moduleType === 'commonjs') && plugin?.moduleType === 'module') { + debug(`Skipping ts-node registration for ${root} because it's an ESM module (NODE_ENV: ${process.env.NODE_ENV}, root plugin module type: ${Config.rootPlugin?.moduleType})))`) if (plugin.type === 'link') memoizedWarn(`${plugin.name} is a linked ESM module and cannot be auto-transpiled. Existing compiled source will be used instead.`) return orig } - if (settings.tsnodeEnabled === undefined && isProd() && plugin?.type !== 'link') { + if (settings.tsnodeEnabled === undefined && isProduction && plugin?.type !== 'link') { debug(`Skipping ts-node registration for ${root} because NODE_ENV is NOT "test" or "development"`) return orig } @@ -147,17 +154,17 @@ export function tsPath(root: string, orig: string | undefined, plugin?: Plugin): const rootDirPath = rootDir || (rootDirs || [])[0] if (!rootDirPath || !outDir) return orig // rewrite path from ./lib/foo to ./src/foo - const lib = path.join(root, outDir) // ./lib - const src = path.join(root, rootDirPath) // ./src - const relative = path.relative(lib, orig) // ./commands + const lib = join(root, outDir) // ./lib + const src = join(root, rootDirPath) // ./src + const relative = pathRelative(lib, orig) // ./commands // For hooks, it might point to a js file, not a module. Something like "./hooks/myhook.js" which doesn't need the js. - const out = path.join(src, relative).replace(/\.js$/, '') // ./src/commands + const out = join(src, relative).replace(/\.js$/, '') // ./src/commands // this can be a directory of commands or point to a hook file // if it's a directory, we check if the path exists. If so, return the path to the directory. // For hooks, it might point to a module, not a file. Something like "./hooks/myhook" // That file doesn't exist, and the real file is "./hooks/myhook.ts" // In that case we attempt to resolve to the filename. If it fails it will revert back to the lib path - if (fs.existsSync(out) || fs.existsSync(out + '.ts')) return out + if (existsSync(out) || existsSync(out + '.ts')) return out return orig } catch (error: any) { debug(error) diff --git a/src/config/util.ts b/src/config/util.ts index b0b2e8152..182b336a5 100644 --- a/src/config/util.ts +++ b/src/config/util.ts @@ -1,5 +1,3 @@ -import * as fs from 'fs' - const debug = require('debug') export function flatMap(arr: T[], fn: (i: T) => U[]): U[] { @@ -14,37 +12,10 @@ export function mapValues, TResult>(obj: {[P in ke }, {} as any) } -export function exists(path: string): Promise { - // eslint-disable-next-line no-promise-executor-return - return new Promise(resolve => resolve(fs.existsSync(path))) -} - export function resolvePackage(id: string, paths: { paths: string[] }): string { return require.resolve(id, paths) } -export function loadJSON(path: string): Promise { - debug('config')('loadJSON %s', path) - return new Promise((resolve, reject) => { - fs.readFile(path, 'utf8', (err: any, d: any) => { - try { - if (err) reject(err) - else resolve(JSON.parse(d)) - } catch (error: any) { - reject(error) - } - }) - }) -} - -export function compact(a: (T | undefined)[]): T[] { - return a.filter((a): a is T => Boolean(a)) -} - -export function uniq(arr: T[]): T[] { - return [...new Set(arr)].sort() -} - function displayWarnings() { if (process.listenerCount('warning') > 1) return process.on('warning', (warning: any) => { @@ -54,7 +25,10 @@ function displayWarnings() { } export function Debug(...scope: string[]): (..._: any) => void { - if (!debug) return (..._: any[]) => {} + if (!debug) return (..._: any[]) => { + // noop + } + const d = debug(['config', ...scope].join(':')) if (d.enabled) displayWarnings() return (...args: any[]) => d(...args) @@ -107,16 +81,5 @@ export function getCommandIdPermutations(commandId: string): string[] { * @param commandIds string[] * @returns string[] */ -export function collectUsableIds(commandIds: string[]): Set { - const usuableIds: string[] = [] - for (const id of commandIds) { - const parts = id.split(':') - while (parts.length > 0) { - const name = parts.join(':') - if (name) usuableIds.push(name) - parts.pop() - } - } - - return new Set(usuableIds) -} +export const collectUsableIds = (commandIds: string[]): Set => + new Set(commandIds.flatMap(id => id.split(':').map((_, i, a) => a.slice(0, i + 1).join(':')))) diff --git a/src/errors/config.ts b/src/errors/config.ts index 7a757f969..9efff616e 100644 --- a/src/errors/config.ts +++ b/src/errors/config.ts @@ -1,5 +1,5 @@ -import {settings} from '../settings' import {Logger} from './logger' +import {settings} from '../settings' function displayWarnings() { if (process.listenerCount('warning') > 1) return diff --git a/src/errors/errors/cli.ts b/src/errors/errors/cli.ts index 3fb241c7f..b4aa7e869 100644 --- a/src/errors/errors/cli.ts +++ b/src/errors/errors/cli.ts @@ -1,11 +1,10 @@ -import * as chalk from 'chalk' -import * as indent from 'indent-string' -import * as cs from 'clean-stack' -import * as wrap from 'wrap-ansi' - -import * as screen from '../../screen' +import {OclifError, PrettyPrintableError} from '../../interfaces/errors' +import chalk from 'chalk' import {config} from '../config' -import {PrettyPrintableError, OclifError} from '../../interfaces/errors' +import cs from 'clean-stack' +import {errtermwidth} from '../../screen' +import indent from 'indent-string' +import wrap from 'wrap-ansi' /** * properties specific to internal oclif error handling @@ -47,7 +46,7 @@ export class CLIError extends Error implements OclifError { } let output = `${this.name}: ${this.message}` - output = wrap(output, screen.errtermwidth - 6, {trim: false, hard: true} as any) + output = wrap(output, errtermwidth - 6, {trim: false, hard: true} as any) output = indent(output, 3) output = indent(output, 1, {indent: this.bang, includeEmptyLines: true} as any) output = indent(output, 1) diff --git a/src/errors/errors/exit.ts b/src/errors/errors/exit.ts index 11dd684d9..7796c0a12 100644 --- a/src/errors/errors/exit.ts +++ b/src/errors/errors/exit.ts @@ -2,8 +2,6 @@ import {CLIError} from './cli' import {OclifError} from '../../interfaces' export class ExitError extends CLIError implements OclifError { - oclif!: { exit: number } - code = 'EEXIT' constructor(exitCode = 1) { diff --git a/src/errors/errors/module-load.ts b/src/errors/errors/module-load.ts index 07afa7d79..211c1d9e5 100644 --- a/src/errors/errors/module-load.ts +++ b/src/errors/errors/module-load.ts @@ -2,8 +2,6 @@ import {CLIError} from './cli' import {OclifError} from '../../interfaces' export class ModuleLoadError extends CLIError implements OclifError { - oclif!: { exit: number } - code = 'MODULE_NOT_FOUND' constructor(message: string) { diff --git a/src/errors/errors/pretty-print.ts b/src/errors/errors/pretty-print.ts index 1f29a1275..3067501db 100644 --- a/src/errors/errors/pretty-print.ts +++ b/src/errors/errors/pretty-print.ts @@ -1,9 +1,8 @@ -import * as wrap from 'wrap-ansi' -import indent = require('indent-string') - -import * as screen from '../../screen' -import {config} from '../config' import {PrettyPrintableError} from '../../interfaces/errors' +import {config} from '../config' +import {errtermwidth} from '../../screen' +import indent from 'indent-string' +import wrap from 'wrap-ansi' // These exist for backwards compatibility with CLIError type CLIErrorDisplayOptions = { name?: string; bang?: string } @@ -48,7 +47,7 @@ export default function prettyPrint(error: Error & PrettyPrintableError & CLIErr .filter(Boolean) .join('\n') - let output = wrap(formatted, screen.errtermwidth - 6, {trim: false, hard: true} as any) + let output = wrap(formatted, errtermwidth - 6, {trim: false, hard: true} as any) output = indent(output, 3) output = indent(output, 1, {indent: bang || '', includeEmptyLines: true} as any) output = indent(output, 1) diff --git a/src/errors/handle.ts b/src/errors/handle.ts index 0c0af3d3d..138e73d8e 100644 --- a/src/errors/handle.ts +++ b/src/errors/handle.ts @@ -1,26 +1,39 @@ /* eslint-disable no-process-exit */ /* eslint-disable unicorn/no-process-exit */ +import {OclifError, PrettyPrintableError} from '../interfaces' +import {CLIError} from './errors/cli' +import {ExitError} from '.' +import clean from 'clean-stack' import {config} from './config' import prettyPrint from './errors/pretty-print' -import {ExitError} from '.' -import clean = require('clean-stack') -import {CLIError} from './errors/cli' -import {OclifError, PrettyPrintableError} from '../interfaces' -export async function handle(err: Error & Partial & Partial & {skipOclifErrorHandling?: boolean}): Promise { +/** + * This is an odd abstraction for process.exit, but it allows us to stub it in tests. + * + * https://github.com/sinonjs/sinon/issues/562 + */ +export const Exit = { + exit(code = 0) { + process.exit(code) + }, +} + +type ErrorToHandle = Error & Partial & Partial & {skipOclifErrorHandling?: boolean} + +export async function handle(err: ErrorToHandle): Promise { try { if (!err) err = new CLIError('no error?') - if (err.message === 'SIGINT') process.exit(1) + if (err.message === 'SIGINT') Exit.exit(1) const shouldPrint = !(err instanceof ExitError) && !err.skipOclifErrorHandling const pretty = prettyPrint(err) const stack = clean(err.stack || '', {pretty: true}) if (shouldPrint) { - console.error(pretty ? pretty : stack) + console.error(pretty ?? stack) } - const exitCode = err.oclif?.exit !== undefined && err.oclif?.exit !== false ? err.oclif?.exit : 1 + const exitCode = err.oclif?.exit ?? 1 if (config.errorLogger && err.code !== 'EEXIT') { if (stack) { @@ -28,12 +41,12 @@ export async function handle(err: Error & Partial & Partia } await config.errorLogger.flush() - .then(() => process.exit(exitCode)) + .then(() => Exit.exit(exitCode)) .catch(console.error) - } else process.exit(exitCode) + } else Exit.exit(exitCode) } catch (error: any) { console.error(err.stack) console.error(error.stack) - process.exit(1) + Exit.exit(1) } } diff --git a/src/errors/index.ts b/src/errors/index.ts index 4fbbbfe0e..17cc8217f 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -1,3 +1,9 @@ +import {CLIError, addOclifExitCode} from './errors/cli' +import {OclifError, PrettyPrintableError} from '../interfaces' +import prettyPrint, {applyPrettyPrintOptions} from './errors/pretty-print' +import {ExitError} from './errors/exit' +import {config} from './config' + export {handle} from './handle' export {ExitError} from './errors/exit' export {ModuleLoadError} from './errors/module-load' @@ -5,14 +11,6 @@ export {CLIError} from './errors/cli' export {Logger} from './logger' export {config} from './config' -import {config} from './config' -import {CLIError, addOclifExitCode} from './errors/cli' -import {ExitError} from './errors/exit' -import prettyPrint, {applyPrettyPrintOptions} from './errors/pretty-print' -import {OclifError, PrettyPrintableError} from '../interfaces' - -export {PrettyPrintableError} - export function exit(code = 0): never { throw new ExitError(code) } @@ -61,3 +59,5 @@ export function memoizedWarn(input: string | Error): void { WARNINGS.add(input) } + +export {PrettyPrintableError} from '../interfaces' diff --git a/src/errors/logger.ts b/src/errors/logger.ts index ece0311b2..f2a79c329 100644 --- a/src/errors/logger.ts +++ b/src/errors/logger.ts @@ -1,5 +1,5 @@ -import * as fs from 'fs/promises' -import {dirname} from 'path' +import {appendFile, mkdir} from 'node:fs/promises' +import {dirname} from 'node:path' import stripAnsi = require('strip-ansi') const timestamp = () => new Date().toISOString() @@ -34,8 +34,8 @@ export class Logger { if (this.buffer.length === 0) return const mylines = this.buffer this.buffer = [] - await fs.mkdir(dirname(this.file), {recursive: true}) - await fs.appendFile(this.file, mylines.join('\n') + '\n') + await mkdir(dirname(this.file), {recursive: true}) + await appendFile(this.file, mylines.join('\n') + '\n') }) await this.flushing } diff --git a/src/execute.ts b/src/execute.ts index f83b01475..d5e95aa25 100644 --- a/src/execute.ts +++ b/src/execute.ts @@ -1,8 +1,8 @@ -import {settings} from './settings' +import {LoadOptions} from './interfaces' import {flush} from './cli-ux/flush' import {handle} from './errors/handle' -import run from './main' -import * as Interfaces from './interfaces' +import {run} from './main' +import {settings} from './settings' /** * Load and run oclif CLI @@ -12,7 +12,7 @@ import * as Interfaces from './interfaces' * * @example For ESM dev.js * ``` - * #!/usr/bin/env node + * #!/usr/bin/env ts-node * void (async () => { * const oclif = await import('@oclif/core') * await oclif.execute({development: true, dir: import.meta.url}) @@ -30,7 +30,7 @@ import * as Interfaces from './interfaces' * * @example For CJS dev.js * ``` - * #!/usr/bin/env node + * #!/usr/bin/env ts-node * void (async () => { * const oclif = await import('@oclif/core') * await oclif.execute({development: true, dir: __dirname}) @@ -46,11 +46,11 @@ import * as Interfaces from './interfaces' * })() * ``` */ -export default async function execute( +export async function execute( options: { dir: string; args?: string[]; - loadOptions?: Interfaces.LoadOptions; + loadOptions?: LoadOptions; development?: boolean; }, ): Promise { diff --git a/src/flags.ts b/src/flags.ts index 786d3db26..61df7db68 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -1,10 +1,45 @@ -import {URL} from 'url' -import {loadHelpClass} from './help' -import {BooleanFlag} from './interfaces' -import {FlagDefinition, OptionFlagDefaults, FlagParser} from './interfaces/parser' +/* eslint-disable valid-jsdoc */ +import {BooleanFlag, CustomOptions, FlagDefinition, OptionFlag} from './interfaces' import {dirExists, fileExists} from './util' import {CLIError} from './errors' +import {URL} from 'node:url' +import {loadHelpClass} from './help' + +type NotArray = T extends Array ? never: T; + +export function custom( + defaults: Partial> & { + multiple: true + } & ( + {required: true} | {default: OptionFlag['default']} + ), +): FlagDefinition + +export function custom( + defaults: Partial> & { + multiple?: false | undefined; + } & ( + {required: true} | {default: OptionFlag, P>['default']} + ), +): FlagDefinition + +export function custom( + defaults: Partial> & { + default?: OptionFlag, P>['default'] | undefined; + multiple?: false | undefined; + required?: false | undefined; + }, +): FlagDefinition + +export function custom( + defaults: Partial> & { + multiple: true; + default?: OptionFlag['default'] | undefined; + required?: false | undefined; + }, +): FlagDefinition +export function custom(): FlagDefinition /** * Create a custom flag. * @@ -22,24 +57,17 @@ import {CLIError} from './errors' * }, * }) */ -export function custom>( - defaults: {parse: FlagParser, multiple: true} & Partial>, -): FlagDefinition -export function custom>( - defaults: {parse: FlagParser} & Partial>, -): FlagDefinition -export function custom>(defaults: Partial>): FlagDefinition -export function custom>(defaults: Partial>): FlagDefinition { - return (options: any = {}) => { - return { - parse: async (input, _ctx, _opts) => input, - ...defaults, - ...options, - input: [] as string[], - multiple: Boolean(options.multiple === undefined ? defaults.multiple : options.multiple), - type: 'option', - } - } +export function custom( + defaults?: Partial>, +): FlagDefinition { + return (options: any = {}) => ({ + parse: async (input, _ctx, _opts) => input, + ...defaults, + ...options, + input: [] as string[], + multiple: Boolean(options.multiple === undefined ? defaults?.multiple ?? false : options.multiple), + type: 'option', + }) } export function boolean( @@ -54,7 +82,7 @@ export function boolean( } export const integer = custom({ - parse: async (input, _, opts) => { + async parse(input, _, opts) { if (!/^-?\d+$/.test(input)) throw new CLIError(`Expected an integer but received: ${input}`) const num = Number.parseInt(input, 10) @@ -67,7 +95,7 @@ export const integer = custom({ }) export const directory = custom({ - parse: async (input, _, opts) => { + async parse(input, _, opts) { if (opts.exists) return dirExists(input) return input @@ -75,7 +103,7 @@ export const directory = custom({ }) export const file = custom({ - parse: async (input, _, opts) => { + async parse(input, _, opts) { if (opts.exists) return fileExists(input) return input @@ -87,37 +115,97 @@ export const file = custom({ * if the string is not a valid URL. */ export const url = custom({ - parse: async input => { + async parse(input) { try { return new URL(input) } catch { - throw new Error(`Expected a valid url but received: ${input}`) + throw new CLIError(`Expected a valid url but received: ${input}`) } }, }) -const stringFlag = custom({}) -export {stringFlag as string} - -export const version = (opts: Partial> = {}): BooleanFlag => { - return boolean({ - description: 'Show CLI version.', - ...opts, - parse: async (_: any, ctx) => { - ctx.log(ctx.config.userAgent) - ctx.exit(0) - }, - }) -} +export const string = custom() + +export const version = (opts: Partial> = {}): BooleanFlag => boolean({ + description: 'Show CLI version.', + ...opts, + async parse(_, ctx) { + ctx.log(ctx.config.userAgent) + ctx.exit(0) + }, +}) -export const help = (opts: Partial> = {}): BooleanFlag => { - return boolean({ - description: 'Show CLI help.', - ...opts, - parse: async (_, cmd) => { - const Help = await loadHelpClass(cmd.config) - await new Help(cmd.config, cmd.config.pjson.helpOptions).showHelp(cmd.id ? [cmd.id, ...cmd.argv] : cmd.argv) - cmd.exit(0) - }, +export const help = (opts: Partial> = {}): BooleanFlag => boolean({ + description: 'Show CLI help.', + ...opts, + async parse(_, cmd) { + const Help = await loadHelpClass(cmd.config) + await new Help(cmd.config, cmd.config.pjson.helpOptions).showHelp(cmd.id ? [cmd.id, ...cmd.argv] : cmd.argv) + cmd.exit(0) + }, +}) + +type ElementType> = T[number]; + +export function option( + defaults: Partial[], P>> & { + options: T; + multiple: true + } & ( + {required: true} | { + default: OptionFlag[], P>['default'] | undefined; + } + ), +): FlagDefinition + +export function option( + defaults: Partial, P>> & { + options: T; + multiple?: false | undefined; + } & ( + {required: true} | {default: OptionFlag, P>['default']} + ), +): FlagDefinition + +export function option( + defaults: Partial, P>> & { + options: T; + default?: OptionFlag, P>['default'] | undefined; + multiple?: false | undefined; + required?: false | undefined; + }, +): FlagDefinition + +export function option( + defaults: Partial[], P>> & { + options: T; + multiple: true; + default?: OptionFlag[], P>['default'] | undefined; + required?: false | undefined; + }, +): FlagDefinition + +/** + * Create a custom flag that infers the flag type from the provided options. + * + * @example + * export default class MyCommand extends Command { + * static flags = { + * name: Flags.option({ + * options: ['foo', 'bar'] as const, + * })(), + * } + * } + */ +export function option( + defaults: Partial, P>> & {options: T}, +): FlagDefinition { + return (options: any = {}) => ({ + parse: async (input, _ctx, _opts) => input, + ...defaults, + ...options, + input: [] as string[], + multiple: Boolean(options.multiple === undefined ? defaults.multiple : options.multiple), + type: 'option', }) } diff --git a/src/help/command.ts b/src/help/command.ts index 1d64b6b6b..7faae8825 100644 --- a/src/help/command.ts +++ b/src/help/command.ts @@ -1,20 +1,16 @@ -import * as chalk from 'chalk' -import stripAnsi = require('strip-ansi') - -import {castArray, compact, ensureArgObject, sortBy} from '../util' import * as Interfaces from '../interfaces' import {HelpFormatter, HelpSection, HelpSectionRenderer} from './formatter' -import {DocOpts} from './docopts' +import {castArray, compact, ensureArgObject, sortBy} from '../util' import {Command} from '../command' +import {DocOpts} from './docopts' +import chalk from 'chalk' +import stripAnsi from 'strip-ansi' // Don't use os.EOL because we need to ensure that a string // written on any platform, that may use \r\n or \n, will be // split on any platform, not just the os specific EOL at runtime. const POSSIBLE_LINE_FEED = /\r\n|\n/ -const { - underline, -} = chalk let { dim, } = chalk @@ -123,15 +119,15 @@ export class CommandHelp extends HelpFormatter { } protected usage(): string { - const usage = this.command.usage + const {usage} = this.command const body = (usage ? castArray(usage) : [this.defaultUsage()]) .map(u => { const allowedSpacing = this.opts.maxWidth - this.indentSpacing const line = `$ ${this.config.bin} ${u}`.trim() if (line.length > allowedSpacing) { const splitIndex = line.slice(0, Math.max(0, allowedSpacing)).lastIndexOf(' ') - return line.slice(0, Math.max(0, splitIndex)) + '\n' + - this.indent(this.wrap(line.slice(Math.max(0, splitIndex)), this.indentSpacing * 2)) + return line.slice(0, Math.max(0, splitIndex)) + '\n' + + this.indent(this.wrap(line.slice(Math.max(0, splitIndex)), this.indentSpacing * 2)) } return this.wrap(line) @@ -180,48 +176,38 @@ export class CommandHelp extends HelpFormatter { protected examples(examples: Command.Example[] | undefined | string): string | undefined { if (!examples || examples.length === 0) return - const formatIfCommand = (example: string): string => { - example = this.render(example) - if (example.startsWith(this.config.bin)) return dim(`$ ${example}`) - if (example.startsWith(`$ ${this.config.bin}`)) return dim(example) - return example - } - - const isCommand = (example: string) => stripAnsi(formatIfCommand(example)).startsWith(`$ ${this.config.bin}`) - const body = castArray(examples).map(a => { let description let commands if (typeof a === 'string') { const lines = a .split(POSSIBLE_LINE_FEED) - .filter(line => Boolean(line)) + .filter(Boolean) // If the example is \n then format correctly - // eslint-disable-next-line unicorn/no-array-callback-reference - if (lines.length >= 2 && !isCommand(lines[0]) && lines.slice(1).every(isCommand)) { + if (lines.length >= 2 && !this.isCommand(lines[0]) && lines.slice(1).every(i => this.isCommand(i))) { description = lines[0] commands = lines.slice(1) } else { - return lines.map(line => formatIfCommand(line)).join('\n') + return lines.map(line => this.formatIfCommand(line)).join('\n') } } else { description = a.description commands = [a.command] } - const multilineSeparator = - this.config.platform === 'win32' ? - (this.config.shell.includes('powershell') ? '`' : '^') : - '\\' + const multilineSeparator + = this.config.platform === 'win32' + ? (this.config.shell.includes('powershell') ? '`' : '^') + : '\\' // The command will be indented in the section, which is also indented const finalIndentedSpacing = this.indentSpacing * 2 - const multilineCommands = commands.map(c => { + const multilineCommands = commands.map(c => // First indent keeping room for escaped newlines - return this.indent(this.wrap(formatIfCommand(c), finalIndentedSpacing + 4)) + this.indent(this.wrap(this.formatIfCommand(c), finalIndentedSpacing + 4)) // Then add the escaped newline - .split(POSSIBLE_LINE_FEED).join(` ${multilineSeparator}\n `) - }).join('\n') + .split(POSSIBLE_LINE_FEED).join(` ${multilineSeparator}\n `), + ).join('\n') return `${this.wrap(description, finalIndentedSpacing)}\n\n${multilineCommands}` }).join('\n\n') @@ -270,7 +256,7 @@ export class CommandHelp extends HelpFormatter { } if (flag.multiple) value += '...' - if (!value.includes('|')) value = underline(value) + if (!value.includes('|')) value = chalk.underline(value) label += `=${value}` } @@ -312,5 +298,16 @@ export class CommandHelp extends HelpFormatter { return body } + + private formatIfCommand(example: string): string { + example = this.render(example) + if (example.startsWith(this.config.bin)) return dim(`$ ${example}`) + if (example.startsWith(`$ ${this.config.bin}`)) return dim(example) + return example + } + + private isCommand(example: string): boolean { + return stripAnsi(this.formatIfCommand(example)).startsWith(`$ ${this.config.bin}`) + } } export default CommandHelp diff --git a/src/help/docopts.ts b/src/help/docopts.ts index 37ef17e52..3ee8f8299 100644 --- a/src/help/docopts.ts +++ b/src/help/docopts.ts @@ -79,9 +79,7 @@ export class DocOpts { public toString(): string { const opts = this.cmd.id === '.' || this.cmd.id === '' ? [] : ['<%= command.id %>'] if (this.cmd.args) { - const a = Object.values(ensureArgObject(this.cmd.args)).map(arg => { - return arg.required ? arg.name.toUpperCase() : `[${arg.name.toUpperCase()}]` - }) || [] + const a = Object.values(ensureArgObject(this.cmd.args)).map(arg => arg.required ? arg.name.toUpperCase() : `[${arg.name.toUpperCase()}]`) || [] opts.push(...a) } @@ -156,11 +154,7 @@ export class DocOpts { delete this.flagMap[toCombine] } - if (isRequired) { - elementMap[flagName] = `(${elementMap[flagName] || ''})` - } else { - elementMap[flagName] = `[${elementMap[flagName] || ''}]` - } + elementMap[flagName] = isRequired ? `(${elementMap[flagName] || ''})` : `[${elementMap[flagName] || ''}]` // We handled this flag, don't handle it again delete this.flagMap[flagName] diff --git a/src/help/formatter.ts b/src/help/formatter.ts index 4b61a569f..ad039f851 100644 --- a/src/help/formatter.ts +++ b/src/help/formatter.ts @@ -1,19 +1,13 @@ -import * as Chalk from 'chalk' -import indent = require('indent-string') -import stripAnsi = require('strip-ansi') -import {Command} from '../command' - import * as Interfaces from '../interfaces' +import {Command} from '../command' +import chalk from 'chalk' +import indent from 'indent-string' import {stdtermwidth} from '../screen' +import stripAnsi from 'strip-ansi' import {template} from './util' - -const width = require('string-width') -const widestLine = require('widest-line') - -const wrap = require('wrap-ansi') -const { - bold, -} = Chalk +import widestLine from 'widest-line' +import width from 'string-width' +import wrap from 'wrap-ansi' export type HelpSectionKeyValueTable = {name: string; description: string}[] export type HelpSection = {header: string; body: string | HelpSectionKeyValueTable | [string, string | undefined][] | undefined} | undefined; @@ -202,7 +196,7 @@ export class HelpFormatter { } const output = [ - bold(header), + chalk.bold(header), this.indent(Array.isArray(newBody) ? this.renderList(newBody, {stripAnsi: this.opts.stripAnsi, indentation: 2}) : newBody), ].join('\n') return this.opts.stripAnsi ? stripAnsi(output) : output diff --git a/src/help/index.ts b/src/help/index.ts index 20cc06234..871d6c2f5 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -1,15 +1,17 @@ -import stripAnsi = require('strip-ansi') -import * as util from 'util' import * as Interfaces from '../interfaces' -import {error} from '../errors' -import CommandHelp from './command' -import RootHelp from './root' import {compact, sortBy, uniqBy} from '../util' import {formatCommandDeprecationWarning, getHelpFlagAdditions, standardizeIDFromArgv, toConfiguredId} from './util' -import {HelpFormatter} from './formatter' -import {toCached} from '../config/config' import {Command} from '../command' +import {CommandHelp} from './command' +import {HelpFormatter} from './formatter' +import RootHelp from './root' +import {error} from '../errors' +import {format} from 'node:util' import {stdout} from '../cli-ux/stream' +import {toCached} from '../config/config' + +import stripAnsi = require('strip-ansi') + export {CommandHelp} from './command' export {standardizeIDFromArgv, loadHelpClass, getHelpFlagAdditions, normalizeArgv} from './util' @@ -62,7 +64,7 @@ export class Help extends HelpBase { } protected get sortedCommands(): Command.Loadable[] { - let commands = this.config.commands + let {commands} = this.config commands = commands.filter(c => this.opts.all || !c.hidden) commands = sortBy(commands, c => c.id) @@ -148,9 +150,9 @@ export class Help extends HelpBase { if (state) { this.log( - state === 'deprecated' ? - `${formatCommandDeprecationWarning(toConfiguredId(name, this.config), command.deprecationOptions)}` : - `This command is in ${state}.\n`, + state === 'deprecated' + ? `${formatCommandDeprecationWarning(toConfiguredId(name, this.config), command.deprecationOptions)}` + : `This command is in ${state}.\n`, ) } @@ -185,9 +187,9 @@ export class Help extends HelpBase { const state = this.config.pjson?.oclif?.state if (state) { this.log( - state === 'deprecated' ? - `${this.config.bin} is deprecated` : - `${this.config.bin} is in ${state}.\n`, + state === 'deprecated' + ? `${this.config.bin} is deprecated` + : `${this.config.bin} is in ${state}.\n`, ) } @@ -212,7 +214,7 @@ export class Help extends HelpBase { } protected async showTopicHelp(topic: Interfaces.Topic): Promise { - const name = topic.name + const {name} = topic const depth = name.split(':').length const subTopics = this.sortedTopics.filter(t => t.name.startsWith(name + ':') && t.name.split(':').length === depth + 1) @@ -241,8 +243,8 @@ export class Help extends HelpBase { protected formatCommand(command: Command.Class | Command.Loadable | Command.Cached): string { if (this.config.topicSeparator !== ':') { - command.id = command.id.replace(/:/g, this.config.topicSeparator) - command.aliases = command.aliases && command.aliases.map(a => a.replace(/:/g, this.config.topicSeparator)) + command.id = command.id.replaceAll(':', this.config.topicSeparator) + command.aliases = command.aliases && command.aliases.map(a => a.replaceAll(':', this.config.topicSeparator)) } const help = this.getCommandHelpClass(command) @@ -257,7 +259,7 @@ export class Help extends HelpBase { if (commands.length === 0) return '' const body = this.renderList(commands.map(c => { - if (this.config.topicSeparator !== ':') c.id = c.id.replace(/:/g, this.config.topicSeparator) + if (this.config.topicSeparator !== ':') c.id = c.id.replaceAll(':', this.config.topicSeparator) return [ c.id, this.summary(c), @@ -291,7 +293,7 @@ export class Help extends HelpBase { const summary = description.split('\n')[0] description = description.split('\n').slice(1).join('\n') let topicID = `${topic.name}:COMMAND` - if (this.config.topicSeparator !== ':') topicID = topicID.replace(/:/g, this.config.topicSeparator) + if (this.config.topicSeparator !== ':') topicID = topicID.replaceAll(':', this.config.topicSeparator) let output = compact([ summary, this.section(this.opts.usageHeader || 'USAGE', `$ ${this.config.bin} ${topicID}`), @@ -304,7 +306,7 @@ export class Help extends HelpBase { protected formatTopics(topics: Interfaces.Topic[]): string { if (topics.length === 0) return '' const body = this.renderList(topics.map(c => { - if (this.config.topicSeparator !== ':') c.name = c.name.replace(/:/g, this.config.topicSeparator) + if (this.config.topicSeparator !== ':') c.name = c.name.replaceAll(':', this.config.topicSeparator) return [ c.name, c.description && this.render(c.description.split('\n')[0]), @@ -322,6 +324,6 @@ export class Help extends HelpBase { } protected log(...args: string[]): void { - stdout.write(util.format.apply(this, args) + '\n') + stdout.write(format.apply(this, args) + '\n') } } diff --git a/src/help/root.ts b/src/help/root.ts index 795a0cfb1..6aaa587f2 100644 --- a/src/help/root.ts +++ b/src/help/root.ts @@ -1,8 +1,7 @@ -import stripAnsi = require('strip-ansi') - -import {compact} from '../util' import * as Interfaces from '../interfaces' import {HelpFormatter} from './formatter' +import {compact} from '../util' +import stripAnsi = require('strip-ansi') export default class RootHelp extends HelpFormatter { constructor(public config: Interfaces.Config, public opts: Interfaces.HelpOptions) { diff --git a/src/help/util.ts b/src/help/util.ts index d6e0c460d..278c26050 100644 --- a/src/help/util.ts +++ b/src/help/util.ts @@ -1,8 +1,8 @@ import * as ejs from 'ejs' -import {Config as IConfig, HelpOptions, Deprecation} from '../interfaces' +import {Deprecation, HelpOptions, Config as IConfig} from '../interfaces' import {Help, HelpBase} from '.' -import ModuleLoader from '../module-loader' import {collectUsableIds} from '../config/util' +import {load} from '../module-loader' interface HelpBaseDerived { new(config: IConfig, opts?: Partial): HelpBase; @@ -13,12 +13,12 @@ function extractClass(exported: any): HelpBaseDerived { } export async function loadHelpClass(config: IConfig): Promise { - const pjson = config.pjson + const {pjson} = config const configuredClass = pjson && pjson.oclif && pjson.oclif.helpClass if (configuredClass) { try { - const exported = await ModuleLoader.load(config, configuredClass) as HelpBaseDerived + const exported = await load(config, configuredClass) as HelpBaseDerived return extractClass(exported) as HelpBaseDerived } catch (error: any) { throw new Error(`Unable to load configured help class "${configuredClass}", failed with message:\n${error.message}`) @@ -28,7 +28,6 @@ export async function loadHelpClass(config: IConfig): Promise { return Help } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function template(context: any): (t: string) => string { function render(t: string): string { return ejs.render(t, context) @@ -37,6 +36,9 @@ export function template(context: any): (t: string) => string { return render } +const isFlag = (s: string) => s.startsWith('-') +const isArgWithValue = (s: string) => s.includes('=') + function collateSpacedCmdIDFromArgs(argv: string[], config: IConfig): string[] { if (argv.length === 1) return argv @@ -45,8 +47,6 @@ function collateSpacedCmdIDFromArgs(argv: string[], config: IConfig): string[] { const final: string[] = [] const idPresent = (id: string) => ids.has(id) - const isFlag = (s: string) => s.startsWith('-') - const isArgWithValue = (s: string) => s.includes('=') const finalizeId = (s?: string) => s ? [...final, s].join(':') : final.join(':') const hasArgs = () => { @@ -78,12 +78,12 @@ function collateSpacedCmdIDFromArgs(argv: string[], config: IConfig): string[] { } export function toStandardizedId(commandID: string, config: IConfig): string { - return commandID.replace(new RegExp(config.topicSeparator, 'g'), ':') + return commandID.replaceAll(new RegExp(config.topicSeparator, 'g'), ':') } export function toConfiguredId(commandID: string, config: IConfig): string { const defaultTopicSeparator = ':' - return commandID.replace(new RegExp(defaultTopicSeparator, 'g'), config.topicSeparator || defaultTopicSeparator) + return commandID.replaceAll(new RegExp(defaultTopicSeparator, 'g'), config.topicSeparator || defaultTopicSeparator) } export function standardizeIDFromArgv(argv: string[], config: IConfig): string[] { diff --git a/src/index.ts b/src/index.ts index 97621e091..4eea78bfa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,55 +1,4 @@ -import {Command} from './command' -import run from './main' -import execute from './execute' -import {handle} from './errors/handle' -import {Config, Plugin, tsPath, toCached} from './config' -import * as Interfaces from './interfaces' -import * as Errors from './errors' -import * as Flags from './flags' -import * as Args from './args' -import {CommandHelp, HelpBase, Help, loadHelpClass} from './help' -import {toStandardizedId, toConfiguredId} from './help/util' -import * as Parser from './parser' -import {Hook} from './interfaces/hooks' -import {settings, Settings} from './settings' -import {HelpSection, HelpSectionRenderer, HelpSectionKeyValueTable} from './help/formatter' -import * as ux from './cli-ux' -import {stderr, stdout} from './cli-ux/stream' -import Performance from './performance' -import {flush} from './cli-ux/flush' - -export { - Args, - Command, - CommandHelp, - Config, - Errors, - execute, - Flags, - flush, - handle, - Help, - HelpBase, - HelpSection, - HelpSectionKeyValueTable, - HelpSectionRenderer, - Hook, - Interfaces, - loadHelpClass, - Parser, - Performance, - Plugin, - run, - settings, - Settings, - stderr, - stdout, - toCached, - toConfiguredId, - toStandardizedId, - tsPath, - ux, -} +import {stderr} from './cli-ux/stream' function checkCWD() { try { @@ -62,3 +11,23 @@ function checkCWD() { } checkCWD() + +export * as Args from './args' +export * as Errors from './errors' +export * as Flags from './flags' +export * as Interfaces from './interfaces' +export * as Parser from './parser' +export * as ux from './cli-ux' +export {CommandHelp, HelpBase, Help, loadHelpClass} from './help' +export {Config, toCached, Plugin, tsPath} from './config' +export {HelpSection, HelpSectionRenderer, HelpSectionKeyValueTable} from './help/formatter' +export {Settings, settings} from './settings' +export {stdout, stderr} from './cli-ux/stream' +export {toConfiguredId, toStandardizedId} from './help/util' +export {Command} from './command' +export {Hook} from './interfaces/hooks' +export {Performance} from './performance' +export {execute} from './execute' +export {flush} from './cli-ux/flush' +export {handle} from './errors/handle' +export {run} from './main' diff --git a/src/interfaces/config.ts b/src/interfaces/config.ts index c694e35ac..df7eebb5e 100644 --- a/src/interfaces/config.ts +++ b/src/interfaces/config.ts @@ -1,11 +1,11 @@ +import {Hook, Hooks} from './hooks' +import {Options, Plugin} from './plugin' +import {Command} from '../command' import {PJSON} from './pjson' -import {Hooks, Hook} from './hooks' -import {Plugin, Options} from './plugin' import {Topic} from './topic' -import {Command} from '../command' export type LoadOptions = Options | string | Config | undefined -export type PlatformTypes = 'darwin' | 'linux' | 'win32' | 'aix' | 'freebsd' | 'openbsd' | 'sunos' | 'wsl' +export type PlatformTypes = NodeJS.Platform | 'wsl' export type ArchTypes = 'arm' | 'arm64' | 'mips' | 'mipsel' | 'ppc' | 'ppc64' | 's390' | 's390x' | 'x32' | 'x64' | 'x86' export type PluginVersionDetail = { diff --git a/src/interfaces/errors.ts b/src/interfaces/errors.ts index f31c0d0a0..e0c083e3a 100644 --- a/src/interfaces/errors.ts +++ b/src/interfaces/errors.ts @@ -2,7 +2,7 @@ export type CommandError = Error & {exitCode?: number}; export interface OclifError { oclif: { - exit?: number | false; + exit?: number; }; } diff --git a/src/interfaces/parser.ts b/src/interfaces/parser.ts index 462a58113..7fc4ded0f 100644 --- a/src/interfaces/parser.ts +++ b/src/interfaces/parser.ts @@ -1,6 +1,5 @@ -import {Command} from '../command' import {AlphabetLowercase, AlphabetUppercase} from './alphabet' -import {Config} from './config' +import {Command} from '../command' export type FlagOutput = { [name: string]: any } export type ArgOutput = { [name: string]: any } @@ -154,6 +153,10 @@ export type FlagProps = { * Alternate names that can be used for this flag. */ aliases?: string[]; + /** + * Alternate short chars that can be used for this flag. + */ + charAliases?: (AlphabetLowercase | AlphabetUppercase)[]; /** * Emit deprecation warning when a flag alias is provided */ @@ -205,13 +208,14 @@ export type BooleanFlagProps = FlagProps & { export type OptionFlagProps = FlagProps & { type: 'option'; helpValue?: string; - options?: string[]; + options?: readonly string[]; multiple?: boolean; } export type FlagParserContext = Command & {token: FlagToken} -export type FlagParser = (input: I, context: FlagParserContext, opts: P & OptionFlag) => Promise +export type FlagParser = (input: I, context: FlagParserContext, opts: P & OptionFlag) => + T extends Array ? Promise : Promise export type ArgParserContext = Command & {token: ArgToken} @@ -238,32 +242,118 @@ export type BooleanFlag = FlagProps & BooleanFlagProps & { parse: (input: boolean, context: FlagParserContext, opts: FlagProps & BooleanFlagProps) => Promise } -export type OptionFlagDefaults = FlagProps & OptionFlagProps & { - parse: FlagParser - defaultHelp?: FlagDefaultHelp; - input: string[]; - default?: M extends true ? FlagDefault : FlagDefault; -} - export type OptionFlag = FlagProps & OptionFlagProps & { parse: FlagParser defaultHelp?: FlagDefaultHelp; input: string[]; -} & ({ default?: FlagDefault; - multiple: false; -} | { - default?: FlagDefault; - multiple: true; -}) +} -export type FlagDefinition = { +type ReturnTypeSwitches = {multiple: boolean; requiredOrDefaulted: boolean} + +/** + * The logic here is as follows: + * - If requiredOrDefaulted is true && multiple is true, then the return type is T[] + * - It's possible that T extends an Array, if so we want to return T so that the return isn't T[][] + * - If requiredOrDefaulted is true && multiple is false, then the return type is T + * - If requiredOrDefaulted is false && multiple is true, then the return type is T[] | undefined + * - It's possible that T extends an Array, if so we want to return T so that the return isn't T[][] + * - If requiredOrDefaulted is false && multiple is false, then the return type is T | undefined + */ +type FlagReturnType = + R['requiredOrDefaulted'] extends true ? + R['multiple'] extends true ? + [T] extends [Array] ? T : + T[] : + T : + R['multiple'] extends true ? + [T] extends [Array] ? T | undefined : + T[] | undefined : + T | undefined + +/** + * FlagDefinition types a function that takes `options` and returns an OptionFlag. + * + * This is returned by `Flags.custom()` and `Flags.option()`, which each take a `defaults` object + * that mirrors the OptionFlag interface. + * + * The `T` in the `OptionFlag` return type is determined by a combination of the provided defaults for + * `multiple`, `required`, and `default` and the provided options for those same properties. If these properties + * are provided in the options, they override the defaults. + * + * no options or defaults -> T | undefined + * `required` -> T + * `default` -> T + * `multiple` -> T[] | undefined + * `required` + `multiple` -> T[] + * `default` + `multiple` -> T[] + */ +export type FlagDefinition< + T, + P = CustomOptions, + R extends ReturnTypeSwitches = {multiple: false, requiredOrDefaulted: false} +> = { + ( + // `multiple` is set to false and `required` is set to true in options, potentially overriding the default + options: P & { multiple: false; required: true } & Partial, P>> + ): OptionFlag>; + ( + // `multiple` is set to true and `required` is set to false in options, potentially overriding the default + options: P & { multiple: true; required: false } & Partial, P>> + ): OptionFlag>; + ( + // `multiple` is set to true and `required` is set to false in options, potentially overriding the default + options: P & { multiple: false; required: false } & Partial, P>> + ): OptionFlag>; + ( + options: R['multiple'] extends true ? + // `multiple` is defaulted to true and either `required=true` or `default` are provided in options + P & ( + { required: true } | + { default: OptionFlag, P>['default'] } + ) & Partial, P>> : + // `multiple` is NOT defaulted to true and either `required=true` or `default` are provided in options + P & { multiple?: false | undefined } & ( + { required: true } | + { default: OptionFlag, P>['default'] } + ) & Partial, P>> + ): OptionFlag>; + ( + options: R['multiple'] extends true ? + // `multiple` is defaulted to true and either `required=true` or `default` are provided in options + P & ( + { required: true } | + { default: OptionFlag, P>['default'] } + ) & Partial, P>> : + // `multiple` is NOT defaulted to true but `multiple=true` and either `required=true` or `default` are provided in options + P & { multiple: true } & ( + { required: true } | + { default: OptionFlag, P>['default'] } + ) & Partial, P>> + ): OptionFlag>; + ( + // `multiple` is not provided in options but either `required=true` or `default` are provided + options: P & { multiple?: false | undefined; } & ( + { required: true } | + { default: OptionFlag, P>['default'] } + ) & Partial, P>> + ): OptionFlag>; + ( + // `required` is set to false in options, potentially overriding the default + options: P & { required: false } & Partial, P>> + ): OptionFlag>; + ( + // `multiple` is set to false in options, potentially overriding the default + options: P & { multiple: false } & Partial, P>> + ): OptionFlag>; ( - options: P & { multiple: true } & ({ required: true } | { default: FlagDefault }) & Partial> - ): OptionFlag; - (options: P & { multiple: true } & Partial>): OptionFlag; - (options: P & ({ required: true } | { default: FlagDefault }) & Partial>): OptionFlag; - (options?: P & Partial>): OptionFlag; + // Catch all for when `multiple` is not set in the options + options?: P & { multiple?: false | undefined } & Partial, P>> + ): OptionFlag>; + ( + // `multiple` is set to true in options, potentially overriding the default + options: P & { multiple: true } & Partial, P>> + ): OptionFlag>; } export type Flag = BooleanFlag | OptionFlag @@ -290,26 +380,6 @@ export type ParserContext = Command & { token?: FlagToken | ArgToken; } -export type CompletionContext = { - args?: { [name: string]: string }; - flags?: { [name: string]: string }; - argv?: string[]; - config: Config; -} - -export type Completion = { - skipCache?: boolean; - cacheDuration?: number; - cacheKey?(ctx: CompletionContext): Promise; - options(ctx: CompletionContext): Promise; -} - -export type CompletableOptionFlag = OptionFlag & { - completion?: Completion; -} - -export type CompletableFlag = BooleanFlag | CompletableOptionFlag - -export type FlagInput = { [P in keyof T]: CompletableFlag } +export type FlagInput = { [P in keyof T]: Flag } export type ArgInput = { [P in keyof T]: Arg } diff --git a/src/interfaces/plugin.ts b/src/interfaces/plugin.ts index f4467e90b..50207bcba 100644 --- a/src/interfaces/plugin.ts +++ b/src/interfaces/plugin.ts @@ -77,6 +77,7 @@ export interface Plugin { hooks: { [k: string]: string[] }; readonly commandIDs: string[]; readonly topics: Topic[]; + readonly hasManifest: boolean; findCommand(id: string, opts: { must: true }): Promise; findCommand(id: string, opts?: { must: boolean }): Promise | undefined; diff --git a/src/main.ts b/src/main.ts index 488679ab4..435346c45 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,11 @@ -import {fileURLToPath} from 'url' - -import {format, inspect} from 'util' - import * as Interfaces from './interfaces' -import {URL} from 'url' -import {Config} from './config' +import {URL, fileURLToPath} from 'node:url' +import {format, inspect} from 'node:util' import {getHelpFlagAdditions, loadHelpClass, normalizeArgv} from './help' + +import {Config} from './config' +import {Performance} from './performance' import {stdout} from './cli-ux/stream' -import Performance from './performance' const debug = require('debug')('oclif:main') @@ -34,14 +32,14 @@ export const versionAddition = (argv: string[], config?: Interfaces.Config): boo return false } -export default async function run(argv?: string[], options?: Interfaces.LoadOptions): Promise { +export async function run(argv?: string[], options?: Interfaces.LoadOptions): Promise { const marker = Performance.mark('main.run') const initMarker = Performance.mark('main.run#init') const collectPerf = async () => { marker?.stop() - initMarker?.stop() + if (!initMarker?.stopped) initMarker?.stop() await Performance.collect() Performance.debug() } diff --git a/src/module-loader.ts b/src/module-loader.ts index 715a2e7b2..c3105d027 100644 --- a/src/module-loader.ts +++ b/src/module-loader.ts @@ -1,8 +1,9 @@ -import * as path from 'path' -import * as url from 'url' -import {existsSync, lstatSync} from 'fs' -import {ModuleLoadError} from './errors' import {Config as IConfig, Plugin as IPlugin} from './interfaces' +import {existsSync, lstatSync} from 'node:fs' +import {extname, join, sep} from 'node:path' +import {Command} from './command' +import {ModuleLoadError} from './errors' +import {pathToFileURL} from 'node:url' import {tsPath} from './config' const getPackageType = require('get-package-type') @@ -13,86 +14,115 @@ const getPackageType = require('get-package-type') // eslint-disable-next-line camelcase const s_EXTENSIONS: string[] = ['.ts', '.js', '.mjs', '.cjs'] -const isPlugin = (config: IConfig|IPlugin): config is IPlugin => { - return (config).type !== undefined -} +const isPlugin = (config: IConfig|IPlugin): config is IPlugin => (config).type !== undefined /** - * Provides a static class with several utility methods to work with Oclif config / plugin to load ESM or CJS Node - * modules and source files. + * Loads and returns a module. + * + * Uses `getPackageType` to determine if `type` is set to 'module. If so loads '.js' files as ESM otherwise uses + * a bare require to load as CJS. Also loads '.mjs' files as ESM. + * + * Uses dynamic import to load ESM source or require for CommonJS. * - * @author Michael Leahy (https://github.com/typhonrt) + * A unique error, ModuleLoadError, combines both CJS and ESM loader module not found errors into a single error that + * provides a consistent stack trace and info. + * + * @param {IConfig|IPlugin} config - Oclif config or plugin config. + * @param {string} modulePath - NPM module name or file path to load. + * + * @returns {Promise<*>} The entire ESM module from dynamic import or CJS module by require. */ -// eslint-disable-next-line unicorn/no-static-only-class -export default class ModuleLoader { - /** - * Loads and returns a module. - * - * Uses `getPackageType` to determine if `type` is set to 'module. If so loads '.js' files as ESM otherwise uses - * a bare require to load as CJS. Also loads '.mjs' files as ESM. - * - * Uses dynamic import to load ESM source or require for CommonJS. - * - * A unique error, ModuleLoadError, combines both CJS and ESM loader module not found errors into a single error that - * provides a consistent stack trace and info. - * - * @param {IConfig|IPlugin} config - Oclif config or plugin config. - * @param {string} modulePath - NPM module name or file path to load. - * - * @returns {Promise<*>} The entire ESM module from dynamic import or CJS module by require. - */ - static async load(config: IConfig|IPlugin, modulePath: string): Promise { - let filePath: string | undefined - let isESM: boolean | undefined - try { - ({isESM, filePath} = ModuleLoader.resolvePath(config, modulePath)) - // It is important to await on import to catch the error code. - return isESM ? await import(url.pathToFileURL(filePath).href) : require(filePath) - } catch (error: any) { - if (error.code === 'MODULE_NOT_FOUND' || error.code === 'ERR_MODULE_NOT_FOUND') { - throw new ModuleLoadError(`${isESM ? 'import()' : 'require'} failed to load ${filePath || modulePath}`) - } +export async function load(config: IConfig|IPlugin, modulePath: string): Promise { + let filePath: string | undefined + let isESM: boolean | undefined + try { + ({isESM, filePath} = resolvePath(config, modulePath)) + return isESM ? await import(pathToFileURL(filePath).href) : require(filePath) + } catch (error: any) { + if (error.code === 'MODULE_NOT_FOUND' || error.code === 'ERR_MODULE_NOT_FOUND') { + throw new ModuleLoadError(`${isESM ? 'import()' : 'require'} failed to load ${filePath || modulePath}`) + } - throw error + throw error + } +} + +/** + * Loads a module and returns an object with the module and data about the module. + * + * Uses `getPackageType` to determine if `type` is set to `module`. If so loads '.js' files as ESM otherwise uses + * a bare require to load as CJS. Also loads '.mjs' files as ESM. + * + * Uses dynamic import to load ESM source or require for CommonJS. + * + * A unique error, ModuleLoadError, combines both CJS and ESM loader module not found errors into a single error that + * provides a consistent stack trace and info. + * + * @param {IConfig|IPlugin} config - Oclif config or plugin config. + * @param {string} modulePath - NPM module name or file path to load. + * + * @returns {Promise<{isESM: boolean, module: *, filePath: string}>} An object with the loaded module & data including + * file path and whether the module is ESM. + */ +export async function loadWithData(config: IConfig|IPlugin, modulePath: string): Promise<{isESM: boolean; module: any; filePath: string}> { + let filePath: string | undefined + let isESM: boolean | undefined + try { + ({isESM, filePath} = resolvePath(config, modulePath)) + const module = isESM ? await import(pathToFileURL(filePath).href) : require(filePath) + return {isESM, module, filePath} + } catch (error: any) { + if (error.code === 'MODULE_NOT_FOUND' || error.code === 'ERR_MODULE_NOT_FOUND') { + throw new ModuleLoadError( + `${isESM ? 'import()' : 'require'} failed to load ${filePath || modulePath}: ${error.message}`, + ) } + + throw error } +} - /** - * Loads a module and returns an object with the module and data about the module. - * - * Uses `getPackageType` to determine if `type` is set to `module`. If so loads '.js' files as ESM otherwise uses - * a bare require to load as CJS. Also loads '.mjs' files as ESM. - * - * Uses dynamic import to load ESM source or require for CommonJS. - * - * A unique error, ModuleLoadError, combines both CJS and ESM loader module not found errors into a single error that - * provides a consistent stack trace and info. - * - * @param {IConfig|IPlugin} config - Oclif config or plugin config. - * @param {string} modulePath - NPM module name or file path to load. - * - * @returns {Promise<{isESM: boolean, module: *, filePath: string}>} An object with the loaded module & data including - * file path and whether the module is ESM. - */ - static async loadWithData(config: IConfig|IPlugin, modulePath: string): Promise<{isESM: boolean; module: any; filePath: string}> { - let filePath: string | undefined - let isESM: boolean | undefined - try { - ({isESM, filePath} = ModuleLoader.resolvePath(config, modulePath)) - const module = isESM ? await import(url.pathToFileURL(filePath).href) : require(filePath) - return {isESM, module, filePath} - } catch (error: any) { - if (error.code === 'MODULE_NOT_FOUND' || error.code === 'ERR_MODULE_NOT_FOUND') { - throw new ModuleLoadError( - `${isESM ? 'import()' : 'require'} failed to load ${filePath || modulePath}: ${error.message}`, - ) - } +/** + * Loads a module and returns an object with the module and data about the module. + * + * Uses cached `isESM` and `relativePath` in plugin manifest to determine if dynamic import (isESM = true) + * or require (isESM = false | undefined) should be used. + * + * A unique error, ModuleLoadError, combines both CJS and ESM loader module not found errors into a single error that + * provides a consistent stack trace and info. + * + * @param {Command.Cached} cached - Cached command data from plugin manifest. + * @param {string} modulePath - NPM module name or file path to load. + * + * @returns {Promise<{isESM: boolean, module: *, filePath: string}>} An object with the loaded module & data including + * file path and whether the module is ESM. + */ +export async function loadWithDataFromManifest(cached: Command.Cached, modulePath: string): Promise<{isESM: boolean; module: any; filePath: string}> { + const {isESM, relativePath, id} = cached + if (!relativePath) { + throw new ModuleLoadError(`Cached command ${id} does not have a relative path`) + } + + if (isESM === undefined) { + throw new ModuleLoadError(`Cached command ${id} does not have the isESM property set`) + } - throw error + const filePath = join(modulePath, relativePath.join(sep)) + try { + const module = isESM ? await import(pathToFileURL(filePath).href) : require(filePath) + return {isESM, module, filePath} + } catch (error: any) { + if (error.code === 'MODULE_NOT_FOUND' || error.code === 'ERR_MODULE_NOT_FOUND') { + throw new ModuleLoadError( + `${isESM ? 'import()' : 'require'} failed to load ${filePath || modulePath}: ${error.message}`, + ) } + + throw error } +} - /** +/** * For `.js` files uses `getPackageType` to determine if `type` is set to `module` in associated `package.json`. If * the `modulePath` provided ends in `.mjs` it is assumed to be ESM. * @@ -101,94 +131,96 @@ export default class ModuleLoader { * @returns {boolean} The modulePath is an ES Module. * @see https://www.npmjs.com/package/get-package-type */ - static isPathModule(filePath: string): boolean { - const extension = path.extname(filePath).toLowerCase() - - switch (extension) { - case '.js': - case '.jsx': - case '.ts': - case '.tsx': - return getPackageType.sync(filePath) === 'module' - - case '.mjs': - case '.mts': - return true - - default: - return false - } +export function isPathModule(filePath: string): boolean { + const extension = extname(filePath).toLowerCase() + + switch (extension) { + case '.js': + case '.jsx': + case '.ts': + case '.tsx': { + return getPackageType.sync(filePath) === 'module' } - /** - * Resolves a modulePath first by `require.resolve` to allow Node to resolve an actual module. If this fails then - * the `modulePath` is resolved from the root of the provided config. `Config.tsPath` is used for initial resolution. - * If this file path does not exist then several extensions are tried from `s_EXTENSIONS` in order: '.js', '.mjs', - * '.cjs'. After a file path has been selected `isPathModule` is used to determine if the file is an ES Module. - * - * @param {IConfig|IPlugin} config - Oclif config or plugin config. - * @param {string} modulePath - File path to load. - * - * @returns {{isESM: boolean, filePath: string}} An object including file path and whether the module is ESM. - */ - static resolvePath(config: IConfig|IPlugin, modulePath: string): {isESM: boolean; filePath: string} { - let isESM: boolean - let filePath: string | undefined - - try { - filePath = require.resolve(modulePath) - isESM = ModuleLoader.isPathModule(filePath) - } catch { - filePath = (isPlugin(config) ? tsPath(config.root, modulePath, config) : tsPath(config.root, modulePath)) ?? modulePath - - let fileExists = false - let isDirectory = false - if (existsSync(filePath)) { - fileExists = true - try { - if (lstatSync(filePath)?.isDirectory?.()) { - fileExists = false - isDirectory = true - } - } catch {} - } + case '.mjs': + case '.mts': { + return true + } - if (!fileExists) { - // Try all supported extensions. - let foundPath = ModuleLoader.findFile(filePath) - if (!foundPath && isDirectory) { - // Since filePath is a directory, try looking for index file. - foundPath = ModuleLoader.findFile(path.join(filePath, 'index')) - } + default: { + return false + } + } +} - if (foundPath) { - filePath = foundPath +/** + * Resolves a modulePath first by `require.resolve` to allow Node to resolve an actual module. If this fails then + * the `modulePath` is resolved from the root of the provided config. `Config.tsPath` is used for initial resolution. + * If this file path does not exist then several extensions are tried from `s_EXTENSIONS` in order: '.js', '.mjs', + * '.cjs'. After a file path has been selected `isPathModule` is used to determine if the file is an ES Module. + * + * @param {IConfig|IPlugin} config - Oclif config or plugin config. + * @param {string} modulePath - File path to load. + * + * @returns {{isESM: boolean, filePath: string}} An object including file path and whether the module is ESM. + */ +function resolvePath(config: IConfig|IPlugin, modulePath: string): {isESM: boolean; filePath: string} { + let isESM: boolean + let filePath: string | undefined + + try { + filePath = require.resolve(modulePath) + isESM = isPathModule(filePath) + } catch { + filePath = (isPlugin(config) ? tsPath(config.root, modulePath, config) : tsPath(config.root, modulePath)) ?? modulePath + + let fileExists = false + let isDirectory = false + if (existsSync(filePath)) { + fileExists = true + try { + if (lstatSync(filePath)?.isDirectory?.()) { + fileExists = false + isDirectory = true } + } catch {} + } + + if (!fileExists) { + // Try all supported extensions. + let foundPath = findFile(filePath) + if (!foundPath && isDirectory) { + // Since filePath is a directory, try looking for index file. + foundPath = findFile(join(filePath, 'index')) } - isESM = ModuleLoader.isPathModule(filePath) + if (foundPath) { + filePath = foundPath + } } - return {isESM, filePath} + isESM = isPathModule(filePath) } - /** - * Try adding the different extensions from `s_EXTENSIONS` to find the file. - * - * @param {string} filePath - File path to load. - * - * @returns {string | null} Modified file path including extension or null if file is not found. - */ - static findFile(filePath: string) : string | null { - // eslint-disable-next-line camelcase - for (const extension of s_EXTENSIONS) { - const testPath = `${filePath}${extension}` + return {isESM, filePath} +} - if (existsSync(testPath)) { - return testPath - } - } +/** + * Try adding the different extensions from `s_EXTENSIONS` to find the file. + * + * @param {string} filePath - File path to load. + * + * @returns {string | null} Modified file path including extension or null if file is not found. + */ +function findFile(filePath: string) : string | null { + // eslint-disable-next-line camelcase + for (const extension of s_EXTENSIONS) { + const testPath = `${filePath}${extension}` - return null + if (existsSync(testPath)) { + return testPath + } } + + return null } diff --git a/src/parser/errors.ts b/src/parser/errors.ts index d6a832159..499120dbf 100644 --- a/src/parser/errors.ts +++ b/src/parser/errors.ts @@ -1,11 +1,10 @@ +import {Arg, ArgInput, CLIParseErrorOptions} from '../interfaces/parser' +import {Flag, OptionFlag} from '../interfaces' import {CLIError} from '../errors' - +import chalk from 'chalk' import {flagUsages} from './help' import {renderList} from '../cli-ux/list' -import * as chalk from 'chalk' -import {OptionFlag, Flag} from '../interfaces' -import {uniq} from '../config/util' -import {Arg, ArgInput, CLIParseErrorOptions} from '../interfaces/parser' +import {uniq} from '../util' export {CLIError} from '../errors' diff --git a/src/parser/help.ts b/src/parser/help.ts index 9480dc475..7b6bfd396 100644 --- a/src/parser/help.ts +++ b/src/parser/help.ts @@ -1,5 +1,5 @@ -import * as chalk from 'chalk' import {Flag, FlagUsageOptions} from '../interfaces/parser' +import chalk from 'chalk' import {sortBy} from '../util' export function flagUsage(flag: Flag, options: FlagUsageOptions = {}): [string, string | undefined] { diff --git a/src/parser/index.ts b/src/parser/index.ts index 422af2846..95a62791e 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -1,6 +1,7 @@ +import {ArgInput, FlagInput, Input, OutputArgs, OutputFlags, ParserOutput} from '../interfaces/parser' import {Parser} from './parse' import {validate} from './validate' -import {ArgInput, FlagInput, Input, OutputArgs, OutputFlags, ParserOutput} from '../interfaces/parser' + export {flagUsages} from './help' export async function parse< diff --git a/src/parser/parse.ts b/src/parser/parse.ts index cc8d4a772..6376ec80c 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -4,7 +4,7 @@ import { ArgParserContext, ArgToken, BooleanFlag, - CompletableFlag, + Flag, FlagParserContext, FlagToken, Metadata, @@ -17,15 +17,18 @@ import { ParserOutput, ParsingToken, } from '../interfaces/parser' -import * as readline from 'readline' import {isTruthy, last, pickBy} from '../util' +import {createInterface} from 'node:readline' let debug: any try { - // eslint-disable-next-line no-negated-condition - debug = process.env.CLI_FLAGS_DEBUG !== '1' ? () => {} : require('debug')('../parser') + debug = process.env.CLI_FLAGS_DEBUG === '1' ? require('debug')('../parser') : () => { + // noop + } } catch { - debug = () => {} + debug = () => { + // noop + } } const readStdin = async (): Promise => { @@ -42,10 +45,10 @@ const readStdin = async (): Promise => { return new Promise(resolve => { let result = '' const ac = new AbortController() - const signal = ac.signal + const {signal} = ac const timeout = setTimeout(() => ac.abort(), 100) - const rl = readline.createInterface({ + const rl = createInterface({ input: stdin, output: stdout, terminal: false, @@ -74,6 +77,12 @@ function isNegativeNumber(input: string): boolean { return /^-\d/g.test(input) } +const validateOptions = (flag: OptionFlag, input: string): string => { + if (flag.options && !flag.options.includes(input)) + throw new FlagInvalidOptionError(flag, input) + return input +} + export class Parser, BFlags extends OutputFlags, TArgs extends OutputArgs> { private readonly argv: string[] @@ -91,47 +100,14 @@ export class Parser f.type === 'boolean') as any - this.flagAliases = Object.fromEntries(Object.values(input.flags).flatMap(flag => { - return (flag.aliases ?? []).map(a => [a, flag]) - })) + this.flagAliases = Object.fromEntries(Object.values(input.flags).flatMap(flag => ([...flag.aliases ?? [], ...flag.charAliases ?? []]).map(a => [a, flag]))) } public async parse(): Promise> { this._debugInput() - const findLongFlag = (arg: string):string | undefined => { - const name = arg.slice(2) - if (this.input.flags[name]) { - return name - } - - if (this.flagAliases[name]) { - return this.flagAliases[name].name - } - - if (arg.startsWith('--no-')) { - const flag = this.booleanFlags[arg.slice(5)] - if (flag && flag.allowNo) return flag.name - } - } - - const findShortFlag = ([_, char]: string):string | undefined => { - if (this.flagAliases[char]) { - return this.flagAliases[char].name - } - - return Object.keys(this.input.flags).find(k => (this.input.flags[k].char === char && char !== undefined && this.input.flags[k].char !== undefined)) - } - - const findFlag = (arg: string): { name?: string, isLong: boolean } => { - const isLong = arg.startsWith('--') - const short = isLong ? false : arg.startsWith('-') - const name = isLong ? findLongFlag(arg) : (short ? findShortFlag(arg) : undefined) - return {name, isLong} - } - const parseFlag = (arg: string): boolean => { - const {name, isLong} = findFlag(arg) + const {name, isLong} = this.findFlag(arg) if (!name) { const i = arg.indexOf('=') if (i !== -1) { @@ -150,15 +126,20 @@ export class Parser o.type === 'flag' && o.flag === name)) { + throw new CLIError(`Flag --${name} can only be specified once`) + } + this.currentFlag = flag const input = isLong || arg.length < 3 ? this.argv.shift() : arg.slice(arg[2] === '=' ? 3 : 2) // if the value ends up being one of the command's flags, the user didn't provide an input - if ((typeof input !== 'string') || findFlag(input).name) { + if ((typeof input !== 'string') || this.findFlag(input).name) { throw new CLIError(`Flag --${name} expects a value`) } - this.raw.push({type: 'flag', flag: flag.name, input: input}) + this.raw.push({type: 'flag', flag: flag.name, input}) } else { this.raw.push({type: 'flag', flag: flag.name, input: arg}) // push the rest of the short characters back on the stack @@ -233,12 +214,6 @@ export class Parser { type ValueFunction = (fws: FlagWithStrategy, flags?: Record) => Promise - const validateOptions = (flag: OptionFlag, input: string): string => { - if (flag.options && !flag.options.includes(input)) - throw new FlagInvalidOptionError(flag, input) - return input - } - const parseFlagOrThrowError = async (input: any, flag: BooleanFlag | OptionFlag, context: ParserContext | undefined, token?: FlagToken) => { if (!flag.parse) return input @@ -292,6 +267,7 @@ export class Parser v.trim().replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1')) .map(async v => parseFlagOrThrowError(v, i.inputFlag.flag, this.context, {...last(i.tokens) as FlagToken, input: v})), + // eslint-disable-next-line unicorn/no-await-expression-member )).map(v => validateOptions(i.inputFlag.flag as OptionFlag, v)), } } @@ -343,18 +319,19 @@ export class Parser Promise.resolve(isTruthy(process.env[i.inputFlag.flag.env as string] ?? 'false')), + valueFunction: async (i: FlagWithStrategy) => isTruthy(process.env[i.inputFlag.flag.env as string] ?? 'false'), } } } // no input, but flag has default value + // eslint-disable-next-line no-constant-binary-expression, valid-typeof if (typeof fws.inputFlag.flag.default !== undefined) { return { ...fws, metadata: {setFromDefault: true}, - valueFunction: typeof fws.inputFlag.flag.default === 'function' ? - (i: FlagWithStrategy, allFlags = {}) => fws.inputFlag.flag.default({options: i.inputFlag.flag, flags: allFlags}) : - async () => fws.inputFlag.flag.default, + valueFunction: typeof fws.inputFlag.flag.default === 'function' + ? (i: FlagWithStrategy, allFlags = {}) => fws.inputFlag.flag.default({options: i.inputFlag.flag, flags: allFlags}) + : async () => fws.inputFlag.flag.default, } } @@ -365,11 +342,11 @@ export class Parser { if (fws.inputFlag.flag.type === 'option' && fws.inputFlag.flag.defaultHelp) { return { - ...fws, helpFunction: typeof fws.inputFlag.flag.defaultHelp === 'function' ? + ...fws, helpFunction: typeof fws.inputFlag.flag.defaultHelp === 'function' // @ts-expect-error flag type isn't specific enough to know defaultHelp will definitely be there - (i: FlagWithStrategy, flags: Record, ...context) => i.inputFlag.flag.defaultHelp({options: i.inputFlag, flags}, ...context) : + ? (i: FlagWithStrategy, flags: Record, ...context) => i.inputFlag.flag.defaultHelp({options: i.inputFlag, flags}, ...context) // @ts-expect-error flag type isn't specific enough to know defaultHelp will definitely be there - (i: FlagWithStrategy) => i.inputFlag.flag.defaultHelp, + : (i: FlagWithStrategy) => i.inputFlag.flag.defaultHelp, } } @@ -400,12 +377,12 @@ export class Parser Object.fromEntries( fwsArray.filter(fws => fws.value !== undefined) .map(fws => [fws.inputFlag.name, fws.value]), - ) + ) as TFlags & BFlags & { json: boolean | undefined } type FlagWithStrategy = { inputFlag: { name: string, - flag: CompletableFlag + flag: Flag } tokens?: FlagToken[], valueFunction?: ValueFunction; @@ -434,7 +411,6 @@ export class Parser typeof fws.helpFunction === 'function')) ? await addDefaultHelp(flagsWithAllValues) : flagsWithAllValues return { - // @ts-ignore original version returned an any. Not sure how to get to the return type for `flags` prop flags: fwsArrayToObject(finalFlags), metadata: {flags: Object.fromEntries(finalFlags.filter(fws => fws.metadata).map(fws => [fws.inputFlag.name, fws.metadata as MetadataFlag]))}, } @@ -488,7 +464,7 @@ export class Parser (this.input.flags[k].char === char && char !== undefined && this.input.flags[k].char !== undefined)) + } + + private findFlag(arg: string): { name?: string, isLong: boolean } { + const isLong = arg.startsWith('--') + const short = isLong ? false : arg.startsWith('-') + const name = isLong ? this.findLongFlag(arg) : (short ? this.findShortFlag(arg) : undefined) + return {name, isLong} + } } diff --git a/src/parser/validate.ts b/src/parser/validate.ts index fe5c342f4..0f7d0ea02 100644 --- a/src/parser/validate.ts +++ b/src/parser/validate.ts @@ -1,13 +1,13 @@ +import {Arg, Flag, FlagRelationship, ParserInput, ParserOutput} from '../interfaces/parser' import { + FailedFlagValidationError, InvalidArgsSpecError, + NonExistentFlagsError, RequiredArgsError, - Validation, UnexpectedArgsError, - FailedFlagValidationError, - NonExistentFlagsError, + Validation, } from './errors' -import {Arg, CompletableFlag, Flag, FlagRelationship, ParserInput, ParserOutput} from '../interfaces/parser' -import {uniq} from '../config/util' +import {uniq} from '../util' export async function validate(parse: { input: ParserInput; @@ -172,18 +172,25 @@ export async function validate(parse: { return {...base, status: 'success'} } - function validateRelationships(name: string, flag: CompletableFlag): Promise[] { + function validateRelationships(name: string, flag: Flag): Promise[] { return ((flag.relationships ?? []).map(relationship => { switch (relationship.type) { - case 'all': + case 'all': { return validateDependsOn(name, relationship.flags) - case 'some': + } + + case 'some': { return validateSome(name, relationship.flags) - case 'none': + } + + case 'none': { return validateExclusive(name, relationship.flags) - default: + } + + default: { throw new Error(`Unknown relationship type: ${relationship.type}`) } + } })) } diff --git a/src/performance.ts b/src/performance.ts index 9705f88e0..284eb2af8 100644 --- a/src/performance.ts +++ b/src/performance.ts @@ -1,4 +1,4 @@ -import {PerformanceObserver, performance} from 'perf_hooks' +import {PerformanceObserver, performance} from 'node:perf_hooks' import {settings} from './settings' type Details = Record @@ -16,7 +16,8 @@ type PerfHighlights = { runTime: number; initTime: number; commandLoadTime: number; - pluginLoadTimes: Record; + commandRunTime: number; + pluginLoadTimes: Record; corePluginsLoadTime: number; userPluginsLoadTime: number; linkedPluginsLoadTime: number; @@ -24,10 +25,10 @@ type PerfHighlights = { } class Marker { - public module: string; - public method: string; - public scope: string; - public stopped = false; + public module: string + public method: string + public scope: string + public stopped = false private startMarker: string private stopMarker: string @@ -58,7 +59,7 @@ class Marker { } } -export default class Performance { +export class Performance { private static markers: Record = {} private static _results: PerfResult[] = [] private static _highlights: PerfHighlights @@ -140,7 +141,7 @@ export default class Performance { const pluginLoadTimes = Object.fromEntries(Performance.results .filter(({name}) => name.startsWith('plugin.load#')) .sort((a, b) => b.duration - a.duration) - .map(({scope, duration}) => [scope, duration])) + .map(({scope, duration, details}) => [scope, {duration, details}])) const hookRunTimes = Performance.results .filter(({name}) => name.startsWith('config.runHook#')) @@ -163,10 +164,13 @@ export default class Performance { .sort((a, b) => b.duration - a.duration) .map(({scope, duration}) => [scope, duration])) + const commandRunTime = Performance.results.find(({name}) => name.startsWith('config.runCommand#'))?.duration ?? 0 + Performance._highlights = { configLoadTime: Performance.getResult('config.load')?.duration ?? 0, runTime: Performance.getResult('main.run')?.duration ?? 0, initTime: Performance.getResult('main.run#init')?.duration ?? 0, + commandRunTime, commandLoadTime, pluginLoadTimes, hookRunTimes, @@ -199,17 +203,21 @@ export default class Performance { if (!Performance.enabled) return const debug = require('debug')('perf') - + debug('Total Time: %sms', Performance.highlights.runTime.toFixed(4)) debug('Init Time: %sms', Performance.highlights.initTime.toFixed(4)) debug('Config Load Time: %sms', Performance.highlights.configLoadTime.toFixed(4)) - debug('Command Load Time: %sms', Performance.highlights.commandLoadTime.toFixed(4)) - debug('Execution Time: %sms', Performance.highlights.runTime.toFixed(4)) + debug(' • Plugins Load Time: %sms', Performance.getResult('config.loadAllPlugins')?.duration.toFixed(4) ?? 0) + debug(' • Commands Load Time: %sms', Performance.getResult('config.loadAllCommands')?.duration.toFixed(4) ?? 0) debug('Core Plugin Load Time: %sms', Performance.highlights.corePluginsLoadTime.toFixed(4)) debug('User Plugin Load Time: %sms', Performance.highlights.userPluginsLoadTime.toFixed(4)) debug('Linked Plugin Load Time: %sms', Performance.highlights.linkedPluginsLoadTime.toFixed(4)) debug('Plugin Load Times:') - for (const [plugin, duration] of Object.entries(Performance.highlights.pluginLoadTimes)) { - debug(` ${plugin}: ${duration.toFixed(4)}ms`) + for (const [plugin, result] of Object.entries(Performance.highlights.pluginLoadTimes)) { + if (result.details.hasManifest) { + debug(` ${plugin}: ${result.duration.toFixed(4)}ms`) + } else { + debug(` ${plugin}: ${result.duration.toFixed(4)}ms (no manifest!)`) + } } debug('Hook Run Times:') @@ -219,5 +227,8 @@ export default class Performance { debug(` ${plugin}: ${duration.toFixed(4)}ms`) } } + + debug('Command Load Time: %sms', Performance.highlights.commandLoadTime.toFixed(4)) + debug('Command Run Time: %sms', Performance.highlights.commandRunTime.toFixed(4)) } } diff --git a/src/screen.ts b/src/screen.ts index 68b1e6fbb..529513d9c 100644 --- a/src/screen.ts +++ b/src/screen.ts @@ -1,4 +1,4 @@ -import {stdout, stderr} from './cli-ux/stream' +import {stderr, stdout} from './cli-ux/stream' import {settings} from './settings' function termwidth(stream: any): number { diff --git a/src/util.ts b/src/util.ts index 224089099..a5c0eab5b 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,7 +1,11 @@ -import * as fs from 'fs' -import {join} from 'path' -import {Command} from './command' +import {access, stat} from 'node:fs/promises' +import {homedir, platform} from 'node:os' +import {readFile, readFileSync} from 'node:fs' import {ArgInput} from './interfaces/parser' +import {Command} from './command' +import {join} from 'node:path' + +const debug = require('debug') export function pickBy>(obj: T, fn: (i: T[keyof T]) => boolean): Partial { return Object.entries(obj) @@ -12,6 +16,7 @@ export function pickBy(a: (T | undefined)[]): T[] { + // eslint-disable-next-line unicorn/prefer-native-coercion-functions return a.filter((a): a is T => Boolean(a)) } @@ -24,28 +29,28 @@ export function uniqBy(arr: T[], fn: (cur: T) => any): T[] { export function last(arr?: T[]): T | undefined { if (!arr) return - return arr.slice(-1)[0] + return arr.at(-1) } type SortTypes = string | number | undefined | boolean -export function sortBy(arr: T[], fn: (i: T) => SortTypes | SortTypes[]): T[] { - function compare(a: SortTypes | SortTypes[], b: SortTypes | SortTypes[]): number { - a = a === undefined ? 0 : a - b = b === undefined ? 0 : b - - if (Array.isArray(a) && Array.isArray(b)) { - if (a.length === 0 && b.length === 0) return 0 - const diff = compare(a[0], b[0]) - if (diff !== 0) return diff - return compare(a.slice(1), b.slice(1)) - } - - if (a < b) return -1 - if (a > b) return 1 - return 0 +function compare(a: SortTypes | SortTypes[], b: SortTypes | SortTypes[]): number { + a = a === undefined ? 0 : a + b = b === undefined ? 0 : b + + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length === 0 && b.length === 0) return 0 + const diff = compare(a[0], b[0]) + if (diff !== 0) return diff + return compare(a.slice(1), b.slice(1)) } + if (a < b) return -1 + if (a > b) return 1 + return 0 +} + +export function sortBy(arr: T[], fn: (i: T) => SortTypes | SortTypes[]): T[] { return arr.sort((a, b) => compare(fn(a), fn(b))) } @@ -78,12 +83,22 @@ export function capitalize(s: string): string { return s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : '' } +export async function exists(path: string): Promise { + try { + await access(path) + return true + } catch { + return false + } +} + export const dirExists = async (input: string): Promise => { - if (!fs.existsSync(input)) { + if (!await exists(input)) { throw new Error(`No directory found at ${input}`) } - if (!(await fs.promises.stat(input)).isDirectory()) { + const fileStat = await stat(input) + if (!fileStat.isDirectory()) { throw new Error(`${input} exists but is not a directory`) } @@ -91,11 +106,12 @@ export const dirExists = async (input: string): Promise => { } export const fileExists = async (input: string): Promise => { - if (!fs.existsSync(input)) { + if (!await exists(input)) { throw new Error(`No file found at ${input}`) } - if (!(await fs.promises.stat(input)).isFile()) { + const fileStat = await stat(input) + if (!fileStat.isFile()) { throw new Error(`${input} exists but is not a file`) } @@ -111,7 +127,7 @@ export function isNotFalsy(input: string): boolean { } export function requireJson(...pathParts: string[]): T { - return JSON.parse(fs.readFileSync(join(...pathParts), 'utf8')) + return JSON.parse(readFileSync(join(...pathParts), 'utf8')) } /** @@ -122,7 +138,54 @@ export function requireJson(...pathParts: string[]): T { * @returns ArgInput */ export function ensureArgObject(args?: any[] | ArgInput | { [name: string]: Command.Arg.Cached}): ArgInput { - return (Array.isArray(args) ? (args ?? []).reduce((x, y) => { - return {...x, [y.name]: y} - }, {} as ArgInput) : args ?? {}) as ArgInput + return (Array.isArray(args) ? (args ?? []).reduce((x, y) => ({...x, [y.name]: y}), {} as ArgInput) : args ?? {}) as ArgInput +} + +export function uniq(arr: T[]): T[] { + return [...new Set(arr)].sort() +} + +/** + * Call os.homedir() and return the result + * + * Wrapping this allows us to stub these in tests since os.homedir() is + * non-configurable and non-writable. + * + * @returns The user's home directory + */ +export function getHomeDir(): string { + return homedir() +} + +/** + * Call os.platform() and return the result + * + * Wrapping this allows us to stub these in tests since os.platform() is + * non-configurable and non-writable. + * + * @returns The process' platform + */ +export function getPlatform(): NodeJS.Platform { + return platform() +} + +export function readJson(path: string): Promise { + debug('config')('readJson %s', path) + return new Promise((resolve, reject) => { + readFile(path, 'utf8', (err: any, d: any) => { + try { + if (err) reject(err) + else resolve(JSON.parse(d) as T) + } catch (error: any) { + reject(error) + } + }) + }) +} + +export function readJsonSync(path: string, parse: false): string +export function readJsonSync(path: string, parse?: true): T +export function readJsonSync(path: string, parse = true): T | string { + const contents = readFileSync(path, 'utf8') + return parse ? JSON.parse(contents) as T : contents } diff --git a/test/.eslintrc.json b/test/.eslintrc.json new file mode 100644 index 000000000..43284ce5e --- /dev/null +++ b/test/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": "../.eslintrc.json", + "rules": { + "unicorn/consistent-function-scoping": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/ban-ts-ignore": "off", + "@typescript-eslint/ban-ts-comment": "off", + "sort-imports": "off" + } +} diff --git a/test/cli-ux/export.test.ts b/test/cli-ux/export.test.ts index 4a50da411..4a62835b7 100644 --- a/test/cli-ux/export.test.ts +++ b/test/cli-ux/export.test.ts @@ -1,4 +1,4 @@ -import {ux, Table, IPromptOptions, Config, ActionBase, ExitError} from '../../src/cli-ux' +import {ActionBase, Config, ExitError, IPromptOptions, Table, ux} from '../../src/cli-ux' import {expect} from 'chai' type MyColumns = Record diff --git a/test/cli-ux/fancy.ts b/test/cli-ux/fancy.ts index 74d8c174f..f6709c0d3 100644 --- a/test/cli-ux/fancy.ts +++ b/test/cli-ux/fancy.ts @@ -1,14 +1,9 @@ -import {expect, fancy as base, FancyTypes} from 'fancy-test' +import {fancy as base} from 'fancy-test' import {rm} from 'node:fs/promises' import {join} from 'node:path' import {ux} from '../../src/cli-ux' -export { - expect, - FancyTypes, -} - let count = 0 export const fancy = base @@ -19,6 +14,9 @@ export const fancy = base const chalk = require('chalk') chalk.level = 0 }) +// eslint-disable-next-line unicorn/prefer-top-level-await .finally(async () => { await ux.done() }) + +export {FancyTypes, expect} from 'fancy-test' diff --git a/test/cli-ux/prompt.test.ts b/test/cli-ux/prompt.test.ts index fd8c6815c..59ae25ce6 100644 --- a/test/cli-ux/prompt.test.ts +++ b/test/cli-ux/prompt.test.ts @@ -1,6 +1,6 @@ import * as chai from 'chai' -const expect = chai.expect +const {expect} = chai import {ux} from '../../src/cli-ux' diff --git a/test/cli-ux/styled/header.test.ts b/test/cli-ux/styled/header.test.ts index 65fb7146b..92047983f 100644 --- a/test/cli-ux/styled/header.test.ts +++ b/test/cli-ux/styled/header.test.ts @@ -1,7 +1,7 @@ import {expect} from 'chai' import {stdout} from '../../../src' -import {stub, SinonStub} from 'sinon' +import {SinonStub, stub} from 'sinon' import {ux} from '../../../src/cli-ux' describe('styled/header', () => { diff --git a/test/command/command.test.ts b/test/command/command.test.ts index 4b2cae5d3..ddbad21bb 100644 --- a/test/command/command.test.ts +++ b/test/command/command.test.ts @@ -31,11 +31,11 @@ describe('command', () => { fancy .do(async () => { class Command extends Base { - static description = 'test command' + static description = 'test command' - async run() { - return 101 - } + async run() { + return 101 + } } expect(await Command.run([])).to.equal(101) @@ -73,45 +73,45 @@ describe('command', () => { fancy .do(async () => { class C extends Command { - static id = 'foo:bar' - static title = 'cmd title' - static type = 'mytype' - static usage = ['$ usage'] - static description = 'test command' - static aliases = ['alias1', 'alias2'] - static hidden = true - // @ts-ignore - static flags = { - flaga: Flags.boolean(), - flagb: Flags.string({ - char: 'b', - hidden: true, - required: false, - description: 'flagb desc', - options: ['a', 'b'], - default: async () => 'a', - }), - flagc: Flags.integer({ - char: 'c', - min: 1, - max: 10, - required: false, - description: 'flagc desc', - options: ['a', 'b'], - // @ts-expect-error: context is any - default: async context => context.options.min + 1, - }), - } - - static args = { - arg1: Args.string({ - description: 'arg1 desc', - required: true, - hidden: false, - options: ['af', 'b'], - default: async () => 'a', - }), - } + static id = 'foo:bar' + static title = 'cmd title' + static type = 'mytype' + static usage = ['$ usage'] + static description = 'test command' + static aliases = ['alias1', 'alias2'] + static hidden = true + // @ts-ignore + static flags = { + flaga: Flags.boolean(), + flagb: Flags.string({ + char: 'b', + hidden: true, + required: false, + description: 'flagb desc', + options: ['a', 'b'], + default: async () => 'a', + }), + flagc: Flags.integer({ + char: 'c', + min: 1, + max: 10, + required: false, + description: 'flagc desc', + options: ['a', 'b'], + // @ts-expect-error: context is any + default: async context => context.options.min + 1, + }), + } + + static args = { + arg1: Args.string({ + description: 'arg1 desc', + required: true, + hidden: false, + options: ['af', 'b'], + default: async () => 'a', + }), + } } const c = await toCached(C, undefined, false) @@ -137,6 +137,7 @@ describe('command', () => { flaga: { aliases: undefined, char: undefined, + charAliases: undefined, description: undefined, dependsOn: undefined, deprecateAliases: undefined, @@ -157,6 +158,7 @@ describe('command', () => { flagb: { aliases: undefined, char: 'b', + charAliases: undefined, description: 'flagb desc', dependsOn: undefined, deprecateAliases: undefined, @@ -180,6 +182,7 @@ describe('command', () => { flagc: { aliases: undefined, char: 'c', + charAliases: undefined, default: 2, delimiter: undefined, dependsOn: undefined, @@ -273,14 +276,14 @@ describe('command', () => { .stdout() .it('has a flag', async ctx => { class CMD extends Base { - static flags = { - foo: Flags.string(), - } - - async run() { - const {flags} = await this.parse(CMD) - this.log(flags.foo) - } + static flags = { + foo: Flags.string(), + } + + async run() { + const {flags} = await this.parse(CMD) + this.log(flags.foo) + } } await CMD.run(['--foo=bar']) @@ -370,20 +373,20 @@ describe('command', () => { .stderr() .do(async () => { class CMD extends Command { - static flags = { - name: Flags.string({ - deprecated: { - to: '--full-name', - version: '2.0.0', - }, - }), - force: Flags.boolean(), - } - - async run() { - await this.parse(CMD) - this.log('running command') - } + static flags = { + name: Flags.string({ + deprecated: { + to: '--full-name', + version: '2.0.0', + }, + }), + force: Flags.boolean(), + } + + async run() { + await this.parse(CMD) + this.log('running command') + } } await CMD.run(['--name', 'astro']) @@ -398,20 +401,20 @@ describe('command', () => { .stderr() .do(async () => { class CMD extends Command { - static flags = { - name: Flags.string({ - deprecated: { - to: '--full-name', - version: '2.0.0', - }, - }), - force: Flags.boolean(), - } - - async run() { - await this.parse(CMD) - this.log('running command') - } + static flags = { + name: Flags.string({ + deprecated: { + to: '--full-name', + version: '2.0.0', + }, + }), + force: Flags.boolean(), + } + + async run() { + await this.parse(CMD) + this.log('running command') + } } await CMD.run(['--force']) @@ -426,12 +429,12 @@ describe('command', () => { .stderr() .do(async () => { class CMD extends Command { - static id = 'my:command' - static state = 'deprecated' + static id = 'my:command' + static state = 'deprecated' - async run() { - this.log('running command') - } + async run() { + this.log('running command') + } } await CMD.run([]) @@ -446,16 +449,16 @@ describe('command', () => { .stderr() .do(async () => { class CMD extends Command { - static id = 'my:command' - static state = 'deprecated' - static deprecationOptions = { - version: '2.0.0', - to: 'my:other:command', - } - - async run() { - this.log('running command') - } + static id = 'my:command' + static state = 'deprecated' + static deprecationOptions = { + version: '2.0.0', + to: 'my:other:command', + } + + async run() { + this.log('running command') + } } await CMD.run([]) @@ -503,11 +506,11 @@ describe('command', () => { .stdout() .do(async () => { class CMD extends Command { - static enableJsonFlag = true + static enableJsonFlag = true - async run() { - this.log('not json output') - } + async run() { + this.log('not json output') + } } const cmd = new CMD([], {} as any) @@ -519,9 +522,9 @@ describe('command', () => { .stdout() .do(async () => { class CMD extends Command { - static enableJsonFlag = true + static enableJsonFlag = true - async run() {} + async run() {} } const cmd = new CMD(['--json'], {} as any) @@ -533,8 +536,8 @@ describe('command', () => { .stdout() .do(async () => { class CMD extends Command { - static enableJsonFlag = true - async run() {} + static enableJsonFlag = true + async run() {} } // mock a scopedEnvVar being set to JSON @@ -549,13 +552,13 @@ describe('command', () => { .stdout() .do(async () => { class CMD extends Command { - static enableJsonFlag = true - static '--' = true + static enableJsonFlag = true + static '--' = true - async run() { - const {flags} = await cmd.parse(CMD, ['--json']) - expect(flags.json).to.equal(true, 'json flag should be true') - } + async run() { + const {flags} = await cmd.parse(CMD, ['--json']) + expect(flags.json).to.equal(true, 'json flag should be true') + } } const cmd = new CMD(['--json'], {} as any) @@ -567,14 +570,17 @@ describe('command', () => { .stdout() .do(async () => { class CMD extends Command { - static enableJsonFlag = true - static '--' = true - - async run() { - const {flags} = await cmd.parse(CMD, ['--', '--json']) - expect(flags.json).to.equal(false, 'json flag should be false') - expect(this.passThroughEnabled).to.equal(true, 'pass through should be true') - } + // static initialization block is required whenever using ES2022 + static { + this.enableJsonFlag = true + this['--'] = true + } + + async run() { + const {flags} = await cmd.parse(CMD, ['--', '--json']) + expect(flags.json).to.equal(false, 'json flag should be false') + expect(this.passThroughEnabled).to.equal(true, 'pass through should be true') + } } const cmd = new CMD(['--', '--json'], {} as any) @@ -586,13 +592,13 @@ describe('command', () => { .stdout() .do(async () => { class CMD extends Command { - static enableJsonFlag = true - static '--' = true + static enableJsonFlag = true + static '--' = true - async run() { - const {flags} = await cmd.parse(CMD, ['--foo', '--json']) - expect(flags.json).to.equal(true, 'json flag should be true') - } + async run() { + const {flags} = await cmd.parse(CMD, ['--foo', '--json']) + expect(flags.json).to.equal(true, 'json flag should be true') + } } const cmd = new CMD(['foo', '--json'], {} as any) @@ -604,14 +610,14 @@ describe('command', () => { .stdout() .do(async () => { class CMD extends Command { - static enableJsonFlag = true - static '--' = true - - async run() { - const {flags} = await cmd.parse(CMD, ['--foo', '--', '--json']) - expect(flags.json).to.equal(false, 'json flag should be false') - expect(this.passThroughEnabled).to.equal(true, 'pass through should be true') - } + static enableJsonFlag = true + static '--' = true + + async run() { + const {flags} = await cmd.parse(CMD, ['--foo', '--', '--json']) + expect(flags.json).to.equal(false, 'json flag should be false') + expect(this.passThroughEnabled).to.equal(true, 'pass through should be true') + } } const cmd = new CMD(['--foo', '--', '--json'], {} as any) @@ -623,10 +629,10 @@ describe('command', () => { .stdout() .do(async () => { class CMD extends Command { - static enableJsonFlag = true - static '--' = true + static enableJsonFlag = true + static '--' = true - async run() {} + async run() {} } const cmd = new CMD(['--json', '--'], {} as any) @@ -638,10 +644,10 @@ describe('command', () => { .stdout() .do(async () => { class CMD extends Command { - static enableJsonFlag = false - static '--' = true + static enableJsonFlag = false + static '--' = true - async run() {} + async run() {} } const cmd = new CMD(['--json'], {} as any) diff --git a/test/command/helpers/test-help-in-src/src/test-help-plugin.ts b/test/command/helpers/test-help-in-src/src/test-help-plugin.ts index 721ee7a73..de25f5e50 100644 --- a/test/command/helpers/test-help-in-src/src/test-help-plugin.ts +++ b/test/command/helpers/test-help-in-src/src/test-help-plugin.ts @@ -1,10 +1,9 @@ -import {spy, SinonSpy} from 'sinon' -import {Interfaces, HelpBase} from '../../../../../src' +import {SinonSpy, spy} from 'sinon' +import {HelpBase, Interfaces} from '../../../../../src' export type TestHelpClassConfig = Interfaces.Config & { showCommandHelpSpy?: SinonSpy; showHelpSpy?: SinonSpy } export default class extends HelpBase { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types constructor(config: any, opts: any) { super(config, opts) config.showCommandHelpSpy = this.showCommandHelp diff --git a/test/command/main-esm.test.ts b/test/command/main-esm.test.ts index 9596116ce..d971a48aa 100644 --- a/test/command/main-esm.test.ts +++ b/test/command/main-esm.test.ts @@ -1,13 +1,13 @@ import {expect, fancy} from 'fancy-test' -import * as path from 'path' -import * as url from 'url' +import {resolve} from 'node:path' +import {pathToFileURL} from 'node:url' -import run from '../../src/main' +import {run} from '../../src/main' // This tests file URL / import.meta.url simulation. -const convertToFileURL = (filepath: string) => url.pathToFileURL(filepath).toString() +const convertToFileURL = (filepath: string) => pathToFileURL(filepath).toString() -let root = path.resolve(__dirname, '../../package.json') +let root = resolve(__dirname, '../../package.json') const pjson = require(root) const version = `@oclif/core/${pjson.version} ${process.platform}-${process.arch} node-${process.version}` @@ -16,7 +16,6 @@ root = convertToFileURL(root) describe('main-esm', () => { fancy .stdout() - .skip() // skip until oclif/test is on v3 .do(() => run(['plugins'], root)) .do((output: any) => expect(output.stdout).to.equal('No plugins installed.\n')) .it('runs plugins') @@ -50,7 +49,7 @@ COMMANDS fancy .stdout() - .do(() => run(['--help', 'foo'], convertToFileURL(path.resolve(__dirname, 'fixtures/esm/package.json')))) + .do(() => run(['--help', 'foo'], convertToFileURL(resolve(__dirname, 'fixtures/esm/package.json')))) .do((output: any) => expect(output.stdout).to.equal(`foo topic description USAGE @@ -67,7 +66,7 @@ COMMANDS fancy .stdout() - .do(() => run(['foo', 'bar', '--help'], convertToFileURL(path.resolve(__dirname, 'fixtures/esm/package.json')))) + .do(() => run(['foo', 'bar', '--help'], convertToFileURL(resolve(__dirname, 'fixtures/esm/package.json')))) .do((output: any) => expect(output.stdout).to.equal(`foo bar topic description USAGE @@ -82,13 +81,13 @@ COMMANDS fancy .stdout() - .do(() => run(['foo', 'baz'], convertToFileURL(path.resolve(__dirname, 'fixtures/esm/package.json')))) + .do(() => run(['foo', 'baz'], convertToFileURL(resolve(__dirname, 'fixtures/esm/package.json')))) .do((output: any) => expect(output.stdout).to.equal('running Baz\n')) .it('runs foo:baz with space separator') fancy .stdout() - .do(() => run(['foo', 'bar', 'succeed'], convertToFileURL(path.resolve(__dirname, 'fixtures/esm/package.json')))) + .do(() => run(['foo', 'bar', 'succeed'], convertToFileURL(resolve(__dirname, 'fixtures/esm/package.json')))) .do((output: any) => expect(output.stdout).to.equal('it works!\n')) .it('runs foo:bar:succeed with space separator') }) diff --git a/test/command/main.test.ts b/test/command/main.test.ts index 41116965d..1b9b45583 100644 --- a/test/command/main.test.ts +++ b/test/command/main.test.ts @@ -1,10 +1,10 @@ import {expect} from 'chai' -import * as path from 'path' -import {createSandbox, SinonSandbox, SinonStub} from 'sinon' +import {resolve} from 'node:path' +import {SinonSandbox, SinonStub, createSandbox} from 'sinon' import stripAnsi = require('strip-ansi') import {requireJson} from '../../src/util' -import run from '../../src/main' +import {run} from '../../src/main' import {Interfaces, stdout} from '../../src/index' const pjson = requireJson(__dirname, '..', '..', 'package.json') @@ -23,19 +23,18 @@ describe('main', () => { sandbox.restore() }) - // need to skip until the stdout change is merged and used in plugin-plugins - it.skip('should run plugins', async () => { - await run(['plugins'], path.resolve(__dirname, '../../package.json')) - expect(stdoutStub.firstCall.firstArg).to.equal('No plugins installed.\n') + it('should run plugins', async () => { + const result = await run(['plugins'], resolve(__dirname, '../../package.json')) + expect(result).to.deep.equal([]) }) it('should run version', async () => { - await run(['--version'], path.resolve(__dirname, '../../package.json')) + await run(['--version'], resolve(__dirname, '../../package.json')) expect(stdoutStub.firstCall.firstArg).to.equal(`${version}\n`) }) it('should run help', async () => { - await run(['--help'], path.resolve(__dirname, '../../package.json')) + await run(['--help'], resolve(__dirname, '../../package.json')) expect(stdoutStub.args.map(a => stripAnsi(a[0])).join('')).to.equal(`base library for oclif CLIs VERSION @@ -55,7 +54,7 @@ COMMANDS }) it('should show help for topics with spaces', async () => { - await run(['--help', 'foo'], path.resolve(__dirname, 'fixtures/typescript/package.json')) + await run(['--help', 'foo'], resolve(__dirname, 'fixtures/typescript/package.json')) expect(stdoutStub.args.map(a => stripAnsi(a[0])).join('')).to.equal(`foo topic description USAGE @@ -71,7 +70,7 @@ COMMANDS }) it('should run spaced topic help v2', async () => { - await run(['foo', 'bar', '--help'], path.resolve(__dirname, 'fixtures/typescript/package.json')) + await run(['foo', 'bar', '--help'], resolve(__dirname, 'fixtures/typescript/package.json')) expect(stdoutStub.args.map(a => stripAnsi(a[0])).join('')).to.equal(`foo bar topic description USAGE @@ -86,13 +85,13 @@ COMMANDS it('should run foo:baz with space separator', async () => { const consoleLogStub = sandbox.stub(console, 'log').returns() - await run(['foo', 'baz'], path.resolve(__dirname, 'fixtures/typescript/package.json')) + await run(['foo', 'baz'], resolve(__dirname, 'fixtures/typescript/package.json')) expect(consoleLogStub.firstCall.firstArg).to.equal('running Baz') }) it('should run foo:bar:succeed with space separator', async () => { const consoleLogStub = sandbox.stub(console, 'log').returns() - await run(['foo', 'bar', 'succeed'], path.resolve(__dirname, 'fixtures/typescript/package.json')) + await run(['foo', 'bar', 'succeed'], resolve(__dirname, 'fixtures/typescript/package.json')) expect(consoleLogStub.firstCall.firstArg).to.equal('it works!') }) }) diff --git a/test/config/config.flexible.test.ts b/test/config/config.flexible.test.ts index f8ea56f82..829b00463 100644 --- a/test/config/config.flexible.test.ts +++ b/test/config/config.flexible.test.ts @@ -1,6 +1,3 @@ -import * as os from 'os' -import * as path from 'path' - import {Config} from '../../src/config/config' import {Plugin as IPlugin} from '../../src/interfaces' @@ -8,6 +5,8 @@ import {expect, fancy} from './test' import {Flags, Interfaces} from '../../src' import {Command} from '../../src/command' import {getCommandIdPermutations} from '../../src/config/util' +import * as util from '../../src/util' +import {join} from 'node:path' interface Options { pjson?: any; @@ -45,13 +44,11 @@ describe('Config with flexible taxonomy', () => { let test = fancy .resetConfig() .env(env, {clear: true}) - .stub(os, 'homedir', () => path.join(homedir)) - .stub(os, 'platform', () => platform) + .stub(util, 'getHomeDir', stub => stub.returns(join(homedir))) + .stub(util, 'getPlatform', stub => stub.returns(platform)) const load = async (): Promise => {} - const findCommand = async (): Promise => { - return MyCommandClass - } + const findCommand = async (): Promise => MyCommandClass const commandPluginA: Command.Loadable = { strict: false, @@ -103,6 +100,7 @@ describe('Config with flexible taxonomy', () => { valid: true, tag: 'tag', moduleType: 'commonjs', + hasManifest: false, } const pluginB: IPlugin = { @@ -122,6 +120,7 @@ describe('Config with flexible taxonomy', () => { valid: true, tag: 'tag', moduleType: 'commonjs', + hasManifest: false, } const plugins = new Map().set(pluginA.name, pluginA).set(pluginB.name, pluginB) @@ -161,7 +160,7 @@ describe('Config with flexible taxonomy', () => { }) .it('has populated command permutation index', config => { // @ts-expect-error because private member - const commandPermutations = config.commandPermutations + const {commandPermutations} = config expect(commandPermutations.get('foo')).to.deep.equal(new Set(['foo:bar', 'foo:baz'])) expect(commandPermutations.get('foo:bar')).to.deep.equal(new Set(['foo:bar'])) expect(commandPermutations.get('bar')).to.deep.equal(new Set(['foo:bar'])) diff --git a/test/config/config.test.ts b/test/config/config.test.ts index bafbb852e..cd5aa9971 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -1,8 +1,7 @@ -import * as os from 'os' -import * as path from 'path' +import {join} from 'node:path' import {Plugin as IPlugin} from '../../src/interfaces' -import * as util from '../../src/config/util' +import * as util from '../../src/util' import {expect, fancy} from './test' import {Config, Interfaces} from '../../src' @@ -50,10 +49,9 @@ describe('Config', () => { let test = fancy .resetConfig() .env(env, {clear: true}) - .stub(os, 'homedir', () => path.join(homedir)) - .stub(os, 'platform', () => platform) - - if (pjson) test = test.stub(util, 'loadJSON', () => Promise.resolve(pjson)) + .stub(util, 'getHomeDir', stub => stub.returns(join(homedir))) + .stub(util, 'getPlatform', stub => stub.returns(platform)) + if (pjson) test = test.stub(util, 'readJson', stub => stub.resolves(pjson)) test = test.add('config', () => Config.load()) @@ -65,11 +63,10 @@ describe('Config', () => { // In order to allow prerelease branches to pass, we need to strip the prerelease // tag from the version and switch the channel to stable. // @ts-expect-error because readonly property - config.version = config.version.replace(/-beta\.\d/g, '') + config.version = config.version.replaceAll(/-beta\.\d/g, '') // @ts-expect-error because readonly property config.channel = 'stable' - // eslint-disable-next-line prefer-const let {ext, ...options} = extra options = { bin: 'oclif-cli', @@ -96,11 +93,11 @@ describe('Config', () => { describe('darwin', () => { testConfig() - .hasProperty('cacheDir', path.join('/my/home/Library/Caches/@oclif/core')) - .hasProperty('configDir', path.join('/my/home/.config/@oclif/core')) - .hasProperty('errlog', path.join('/my/home/Library/Caches/@oclif/core/error.log')) - .hasProperty('dataDir', path.join('/my/home/.local/share/@oclif/core')) - .hasProperty('home', path.join('/my/home')) + .hasProperty('cacheDir', join('/my/home/Library/Caches/@oclif/core')) + .hasProperty('configDir', join('/my/home/.config/@oclif/core')) + .hasProperty('errlog', join('/my/home/Library/Caches/@oclif/core/error.log')) + .hasProperty('dataDir', join('/my/home/.local/share/@oclif/core')) + .hasProperty('home', join('/my/home')) }) describe('binAliases', () => { @@ -160,11 +157,11 @@ describe('Config', () => { describe('linux', () => { testConfig({platform: 'linux'}) - .hasProperty('cacheDir', path.join('/my/home/.cache/@oclif/core')) - .hasProperty('configDir', path.join('/my/home/.config/@oclif/core')) - .hasProperty('errlog', path.join('/my/home/.cache/@oclif/core/error.log')) - .hasProperty('dataDir', path.join('/my/home/.local/share/@oclif/core')) - .hasProperty('home', path.join('/my/home')) + .hasProperty('cacheDir', join('/my/home/.cache/@oclif/core')) + .hasProperty('configDir', join('/my/home/.config/@oclif/core')) + .hasProperty('errlog', join('/my/home/.cache/@oclif/core/error.log')) + .hasProperty('dataDir', join('/my/home/.local/share/@oclif/core')) + .hasProperty('home', join('/my/home')) }) describe('win32', () => { @@ -172,11 +169,11 @@ describe('Config', () => { platform: 'win32', env: {LOCALAPPDATA: '/my/home/localappdata'}, }) - .hasProperty('cacheDir', path.join('/my/home/localappdata/@oclif\\core')) - .hasProperty('configDir', path.join('/my/home/localappdata/@oclif\\core')) - .hasProperty('errlog', path.join('/my/home/localappdata/@oclif\\core/error.log')) - .hasProperty('dataDir', path.join('/my/home/localappdata/@oclif\\core')) - .hasProperty('home', path.join('/my/home')) + .hasProperty('cacheDir', join('/my/home/localappdata/@oclif\\core')) + .hasProperty('configDir', join('/my/home/localappdata/@oclif\\core')) + .hasProperty('errlog', join('/my/home/localappdata/@oclif\\core/error.log')) + .hasProperty('dataDir', join('/my/home/localappdata/@oclif\\core')) + .hasProperty('home', join('/my/home')) }) describe('s3Key', () => { @@ -235,23 +232,21 @@ describe('Config', () => { types = [], }: Options = {}) => { class MyCommandClass extends Command { - _base = '' + _base = '' - aliases: string[] = [] + aliases: string[] = [] - hidden = false + hidden = false - id = 'foo:bar' + id = 'foo:bar' - run(): Promise { - return Promise.resolve() - } + run(): Promise { + return Promise.resolve() + } } const load = async (): Promise => {} - const findCommand = async (): Promise => { - return MyCommandClass - } + const findCommand = async (): Promise => MyCommandClass const commandPluginA: Command.Loadable = { strict: false, @@ -286,6 +281,7 @@ describe('Config', () => { valid: true, tag: 'tag', moduleType: 'commonjs', + hasManifest: false, } const pluginB: IPlugin = { @@ -305,15 +301,16 @@ describe('Config', () => { valid: true, tag: 'tag', moduleType: 'commonjs', + hasManifest: false, } const plugins = new Map().set(pluginA.name, pluginA).set(pluginB.name, pluginB) let test = fancy .resetConfig() .env(env, {clear: true}) - .stub(os, 'homedir', () => path.join(homedir)) - .stub(os, 'platform', () => platform) + .stub(util, 'getHomeDir', stub => stub.returns(join(homedir))) + .stub(util, 'getPlatform', stub => stub.returns(platform)) - if (pjson) test = test.stub(util, 'loadJSON', () => Promise.resolve(pjson)) + if (pjson) test = test.stub(util, 'readJson', stub => stub.resolves(pjson)) test = test.add('config', async () => { const config = await Config.load() config.plugins = plugins diff --git a/test/config/esm.test.ts b/test/config/esm.test.ts index 57bfc4709..2d3b1a0d2 100644 --- a/test/config/esm.test.ts +++ b/test/config/esm.test.ts @@ -1,12 +1,12 @@ -import * as url from 'url' -import * as path from 'path' +import * as url from 'node:url' +import {join, resolve} from 'node:path' import {Config} from '../../src/config' import {expect, fancy} from './test' -const root = path.resolve(__dirname, 'fixtures/esm') -const p = (p: string) => path.join(root, p) +const root = resolve(__dirname, 'fixtures/esm') +const p = (p: string) => join(root, p) // This tests file URL / import.meta.url simulation. const rootAsFileURL = url.pathToFileURL(root).toString() diff --git a/test/config/fixtures/typescript/src/hooks/postrun.ts b/test/config/fixtures/typescript/src/hooks/postrun.ts index e88ef9687..f78977809 100644 --- a/test/config/fixtures/typescript/src/hooks/postrun.ts +++ b/test/config/fixtures/typescript/src/hooks/postrun.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + export default function postrun(options: any): void { console.log('running ts postrun hook') if (options.Command.id === 'foo:bar:test-result') { diff --git a/test/config/help.config.test.ts b/test/config/help.config.test.ts index 469230102..3742c9455 100644 --- a/test/config/help.config.test.ts +++ b/test/config/help.config.test.ts @@ -1,5 +1,5 @@ -import * as url from 'url' -import * as path from 'path' +import {pathToFileURL} from 'node:url' +import {resolve} from 'node:path' import {Config} from '../../src/config' @@ -7,11 +7,11 @@ import {expect, fancy} from './test' import {getHelpFlagAdditions} from '../../src/help' import {helpAddition, versionAddition} from '../../src/main' -const root = path.resolve(__dirname, 'fixtures/help') +const root = resolve(__dirname, 'fixtures/help') // const p = (p: string) => path.join(root, p) // This tests file URL / import.meta.url simulation. -const rootAsFileURL = url.pathToFileURL(root).toString() +const rootAsFileURL = pathToFileURL(root).toString() const withConfig = fancy .add('config', () => Config.load(rootAsFileURL)) diff --git a/test/config/mixed-cjs-esm.test.ts b/test/config/mixed-cjs-esm.test.ts index 45053773f..411275fde 100644 --- a/test/config/mixed-cjs-esm.test.ts +++ b/test/config/mixed-cjs-esm.test.ts @@ -1,11 +1,11 @@ -import * as path from 'path' +import {join, resolve} from 'node:path' import {Config} from '../../src/config' import {expect, fancy} from './test' -const root = path.resolve(__dirname, 'fixtures/mixed-cjs-esm') -const p = (p: string) => path.join(root, p) +const root = resolve(__dirname, 'fixtures/mixed-cjs-esm') +const p = (p: string) => join(root, p) const withConfig = fancy .add('config', () => Config.load(root)) diff --git a/test/config/mixed-esm-cjs.test.ts b/test/config/mixed-esm-cjs.test.ts index c9f44daee..4d6ed3703 100644 --- a/test/config/mixed-esm-cjs.test.ts +++ b/test/config/mixed-esm-cjs.test.ts @@ -1,11 +1,11 @@ -import * as path from 'path' +import {join, resolve} from 'node:path' import {Config} from '../../src/config' import {expect, fancy} from './test' -const root = path.resolve(__dirname, 'fixtures/mixed-esm-cjs') -const p = (p: string) => path.join(root, p) +const root = resolve(__dirname, 'fixtures/mixed-esm-cjs') +const p = (p: string) => join(root, p) const withConfig = fancy .add('config', () => Config.load(root)) diff --git a/test/config/test.ts b/test/config/test.ts index a4ac4d66a..1e7866017 100644 --- a/test/config/test.ts +++ b/test/config/test.ts @@ -1,4 +1,4 @@ -import {expect, fancy as base, FancyTypes} from 'fancy-test' +import {fancy as base} from 'fancy-test' import {Interfaces} from '../../src' @@ -10,7 +10,4 @@ export const fancy = base }, })) -export { - expect, - FancyTypes, -} +export {FancyTypes, expect} from 'fancy-test' diff --git a/test/config/ts-node.test.ts b/test/config/ts-node.test.ts index 895c6e7fe..6742238e4 100644 --- a/test/config/ts-node.test.ts +++ b/test/config/ts-node.test.ts @@ -1,18 +1,19 @@ -import * as path from 'path' -import * as proxyquire from 'proxyquire' +import {join, resolve} from 'node:path' +import * as fs from 'node:fs' import * as tsNode from 'ts-node' +import {SinonSandbox, createSandbox} from 'sinon' import {Interfaces, settings} from '../../src' +import * as configTsNode from '../../src/config/ts-node' +import * as util from '../../src/util' +import {expect} from 'chai' -import {expect, fancy} from './test' - -const root = path.resolve(__dirname, 'fixtures/typescript') +const root = resolve(__dirname, 'fixtures/typescript') const tsSource = 'src/hooks/init.ts' // ts-node can load the file as a module (without ts) const tsModule = 'src/hooks/init' const jsCompiledModule = 'lib/hooks/init' const jsCompiled = 'lib/hooks/init.js' -let tsNodeRegisterCallArguments: any[] = [] // Typical root and out options of a typescript project const DEFAULT_TS_CONFIG: Interfaces.TSConfig = { @@ -22,73 +23,69 @@ const DEFAULT_TS_CONFIG: Interfaces.TSConfig = { }, } -const withMockTsConfig = (config: Interfaces.TSConfig = DEFAULT_TS_CONFIG) => { - const tsNodePlugin = proxyquire('../../src/config/ts-node', {fs: { - existsSync: () => true, - readFileSync: () => JSON.stringify(config), - }}) +describe('tsPath', () => { + let sandbox: SinonSandbox - // This prints "loadInterfaces.TSConfig unstubbed" not "loadInterfaces.TSConfig proxyquire"! - tsNodePlugin.tsPath('qwerty', 'asdf') + beforeEach(() => { + sandbox = createSandbox() + sandbox.stub(fs, 'existsSync').returns(true) + sandbox.stub(tsNode, 'register') + }) - return fancy - .add('tsNodePlugin', () => tsNodePlugin) - .stub(tsNode, 'register', ((arg: any) => { - tsNodeRegisterCallArguments.push(arg) - }) as unknown as () => void) - .finally(() => { - tsNodeRegisterCallArguments = [] + afterEach(() => { + sandbox.restore() + // Clear caches so that unit tests don't affect each other + // @ts-expect-error because TS_CONFIGS is not exported + // eslint-disable-next-line import/namespace + configTsNode.TS_CONFIGS = {} + // @ts-expect-error because REGISTERED is not exported + // eslint-disable-next-line import/namespace + configTsNode.REGISTERED = new Set() }) -} -describe('tsPath', () => { - withMockTsConfig() - .it('should resolve a .js file to ts src', (ctx: any) => { - const result = ctx.tsNodePlugin.tsPath(root, jsCompiled) - expect(result).to.equal(path.join(root, tsModule)) + it('should resolve a .js file to ts src', () => { + sandbox.stub(util, 'readJsonSync').returns(JSON.stringify(DEFAULT_TS_CONFIG)) + const result = configTsNode.tsPath(root, jsCompiled) + expect(result).to.equal(join(root, tsModule)) }) - withMockTsConfig() - .it('should resolve a module file to ts src', (ctx: any) => { - const result = ctx.tsNodePlugin.tsPath(root, jsCompiledModule) - expect(result).to.equal(path.join(root, tsModule)) + it('should resolve a module file to ts src', () => { + sandbox.stub(util, 'readJsonSync').returns(JSON.stringify(DEFAULT_TS_CONFIG)) + const result = configTsNode.tsPath(root, jsCompiledModule) + expect(result).to.equal(join(root, tsModule)) }) - withMockTsConfig() - .it('should resolve a .ts file', (ctx: any) => { - const result = ctx.tsNodePlugin.tsPath(root, tsSource) - expect(result).to.equal(path.join(root, tsSource)) + it('should resolve a .ts file', () => { + sandbox.stub(util, 'readJsonSync').returns(JSON.stringify(DEFAULT_TS_CONFIG)) + const result = configTsNode.tsPath(root, tsSource) + expect(result).to.equal(join(root, tsSource)) }) - withMockTsConfig({compilerOptions: {}}) - .it('should resolve .js with no rootDir or outDir', (ctx: any) => { - const result = ctx.tsNodePlugin.tsPath(root, jsCompiled) - expect(result).to.equal(path.join(root, jsCompiled)) + it('should resolve .js with no rootDir or outDir', () => { + sandbox.stub(util, 'readJsonSync').returns({compilerOptions: {}}) + const result = configTsNode.tsPath(root, jsCompiled) + expect(result).to.equal(join(root, jsCompiled)) }) - withMockTsConfig() - .do(() => { + it('should resolve to .ts file if enabled and prod', () => { + sandbox.stub(util, 'readJsonSync').returns(JSON.stringify(DEFAULT_TS_CONFIG)) settings.tsnodeEnabled = true + const originalNodeEnv = process.env.NODE_ENV delete process.env.NODE_ENV - }) - .finally(() => { + + const result = configTsNode.tsPath(root, jsCompiled) + expect(result).to.equal(join(root, tsModule)) + + process.env.NODE_ENV = originalNodeEnv delete settings.tsnodeEnabled - process.env.NODE_ENV = 'development' - }) - .it('should resolve to .ts file if enabled and prod', (ctx: any) => { - const result = ctx.tsNodePlugin.tsPath(root, jsCompiled) - expect(result).to.equal(path.join(root, tsModule)) }) - withMockTsConfig() - .do(() => { + it('should resolve to js if disabled', () => { + sandbox.stub(util, 'readJsonSync').returns(JSON.stringify(DEFAULT_TS_CONFIG)) settings.tsnodeEnabled = false - }) - .finally(() => { + const result = configTsNode.tsPath(root, jsCompiled) + expect(result).to.equal(join(root, jsCompiled)) + delete settings.tsnodeEnabled }) - .it('should resolve to js if disabled', (ctx: any) => { - const result = ctx.tsNodePlugin.tsPath(root, jsCompiled) - expect(result).to.equal(path.join(root, jsCompiled)) - }) }) diff --git a/test/config/typescript.test.ts b/test/config/typescript.test.ts index b8da2b69a..27d20c5cb 100644 --- a/test/config/typescript.test.ts +++ b/test/config/typescript.test.ts @@ -1,11 +1,11 @@ -import * as path from 'path' +import {join, resolve} from 'node:path' import {Config} from '../../src/config' import {expect, fancy} from './test' -const root = path.resolve(__dirname, 'fixtures/typescript') -const p = (p: string) => path.join(root, p) +const root = resolve(__dirname, 'fixtures/typescript') +const p = (p: string) => join(root, p) const withConfig = fancy .add('config', () => Config.load(root)) diff --git a/test/errors/handle.test.ts b/test/errors/handle.test.ts index 29536b888..9582cde10 100644 --- a/test/errors/handle.test.ts +++ b/test/errors/handle.test.ts @@ -1,95 +1,56 @@ import {expect, fancy} from 'fancy-test' import {readFileSync} from 'node:fs' -import * as path from 'path' -import * as process from 'process' +import {join} from 'node:path' +import * as process from 'node:process' -import {CLIError, config, ExitError} from '../../src/errors' -import {handle} from '../../src/errors/handle' -import {exit as exitErrorThrower} from '../../src/errors' +import {CLIError, ExitError, config, exit as exitErrorThrower} from '../../src/errors' +import {Exit, handle} from '../../src/errors/handle' +import {SinonSandbox, SinonStub, createSandbox} from 'sinon' -const errlog = path.join(__dirname, '../tmp/mytest/error.log') +const errlog = join(__dirname, '../tmp/mytest/error.log') const x = process.platform === 'win32' ? '»' : '›' -const originalExit = process.exit -const originalExitCode = process.exitCode - describe('handle', () => { + let sandbox: SinonSandbox + let exitStub: SinonStub + beforeEach(() => { - (process as any).exitCode = undefined; - (process as any).exit = (code: any) => { - (process as any).exitCode = code - } + sandbox = createSandbox() + exitStub = sandbox.stub(Exit, 'exit') }) + afterEach(() => { - (process as any).exit = originalExit; - (process as any).exitCode = originalExitCode + sandbox.restore() }) - // fancy - // .stderr() - // .finally(() => delete process.exitCode) - // .it('displays an error from root handle module', ctx => { - // handle(new Error('x')) - // expect(ctx.stderr).to.contain('Error: x') - // expect(process.exitCode).to.equal(1) - // }) - - // fancy - // .stderr() - // .finally(() => delete process.exitCode) - // .it('shows an unhandled error', ctx => { - // handle(new Error('x')) - // expect(ctx.stderr).to.contain('Error: x') - // expect(process.exitCode).to.equal(1) - // }) - - // fancy - // .stderr() - // .finally(() => delete process.exitCode) - // .it('handles a badly formed error object', () => { - // handle({status: 400} as any) - // expect(process.exitCode).to.equal(1) - // }) - - // fancy - // .stderr() - // .finally(() => delete process.exitCode) - // .it('shows a cli error', ctx => { - // handle(new CLIError('x')) - // expect(ctx.stderr).to.equal(` ${x} Error: x\n`) - // expect(process.exitCode).to.equal(2) - // }) - fancy .stdout() .stderr() - .it('hides an exit error', ctx => { - handle(new ExitError(0)) + .it('hides an exit error', async ctx => { + await handle(new ExitError(0)) expect(ctx.stdout).to.equal('') expect(ctx.stderr).to.equal('') - expect(process.exitCode).to.equal(0) + expect(exitStub.firstCall.firstArg).to.equal(0) }) fancy - .skip() .stdout() .stderr() - .it('prints error', ctx => { + .it('prints error', async ctx => { const error = new Error('foo bar baz') as Error & {skipOclifErrorHandling: boolean} error.skipOclifErrorHandling = false - handle(error) + await handle(error) expect(ctx.stdout).to.equal('') expect(ctx.stderr).to.include('foo bar baz') }) fancy .stdout() - .skip() .stderr() - .it('should not print error when skipOclifErrorHandling is true', ctx => { + .it('should not print error when skipOclifErrorHandling is true', async ctx => { const error = new Error('foo bar baz') as Error & {skipOclifErrorHandling: boolean} error.skipOclifErrorHandling = true - handle(error) + await handle(error) expect(ctx.stdout).to.equal('') expect(ctx.stderr).to.equal('') }) @@ -103,28 +64,71 @@ describe('handle', () => { config.errlog = undefined }) .it('logs when errlog is set', async ctx => { - handle(new CLIError('uh oh!')) + await handle(new CLIError('uh oh!')) expect(ctx.stderr).to.equal(` ${x} Error: uh oh!\n`) await config.errorLogger!.flush() expect(readFileSync(errlog, 'utf8')).to.contain('Error: uh oh!') - expect(process.exitCode).to.equal(2) + expect(exitStub.firstCall.firstArg).to.equal(2) + }) + + fancy + .stdout() + .stderr() + .it('should use default exit code for Error (1)', async ctx => { + const error = new Error('foo bar baz') + await handle(error) + expect(ctx.stdout).to.equal('') + expect(ctx.stderr).to.include('foo bar baz') + expect(exitStub.firstCall.firstArg).to.equal(1) + }) + + fancy + .stdout() + .stderr() + .it('should use default exit code for CLIError (2)', async ctx => { + const error = new CLIError('foo bar baz') + await handle(error) + expect(ctx.stdout).to.equal('') + expect(ctx.stderr).to.include('foo bar baz') + expect(exitStub.firstCall.firstArg).to.equal(2) + }) + + fancy + .stdout() + .stderr() + .it('should use exit code provided by CLIError (0)', async ctx => { + const error = new CLIError('foo bar baz', {exit: 0}) + await handle(error) + expect(ctx.stdout).to.equal('') + expect(ctx.stderr).to.include('foo bar baz') + expect(exitStub.firstCall.firstArg).to.equal(0) + }) + + fancy + .stdout() + .stderr() + .it('should use exit code provided by CLIError (9999)', async ctx => { + const error = new CLIError('foo bar baz', {exit: 9999}) + await handle(error) + expect(ctx.stdout).to.equal('') + expect(ctx.stderr).to.include('foo bar baz') + expect(exitStub.firstCall.firstArg).to.equal(9999) }) describe('exit', () => { fancy .stderr() - .skip() .stdout() - .it('exits without displaying anything', ctx => { + .it('exits without displaying anything', async ctx => { try { exitErrorThrower(9000) } catch (error: any) { - handle(error) + await handle(error) } expect(ctx.stdout).to.equal('') expect(ctx.stderr).to.equal('') - expect(process.exitCode).to.be.equal(9000) + expect(exitStub.firstCall.firstArg).to.equal(9000) }) }) }) diff --git a/test/errors/warn.test.ts b/test/errors/warn.test.ts index 3f807a620..90fb1b7ea 100644 --- a/test/errors/warn.test.ts +++ b/test/errors/warn.test.ts @@ -18,7 +18,6 @@ describe('warn', () => { .it('warns', async ctx => { warn('foo!') expect(ctx.stderr).to.contain('Warning: foo!') - expect(process.exitCode).to.be.undefined await config.errorLogger!.flush() expect(await readFile(errlog, 'utf8')).to.contain('Warning: foo!') }) diff --git a/test/help/fixtures/fixtures.ts b/test/help/fixtures/fixtures.ts index 3a4497f29..4a1661891 100644 --- a/test/help/fixtures/fixtures.ts +++ b/test/help/fixtures/fixtures.ts @@ -8,11 +8,11 @@ export class AppsCreate extends Command { static summary = 'Create an app' - static description = 'this only shows up in command help under DESCRIPTION'; + static description = 'this only shows up in command help under DESCRIPTION' - static flags = {}; + static flags = {} - static args = {}; + static args = {} async run(): Promise { 'run' @@ -23,11 +23,11 @@ export class AppsDestroy extends Command { static id = 'apps:destroy' static description = `Destroy an app - this only shows up in command help under DESCRIPTION`; + this only shows up in command help under DESCRIPTION` - static flags: Record = {}; + static flags: Record = {} - static args = {}; + static args = {} async run(): Promise { 'run' @@ -37,11 +37,11 @@ export class AppsDestroy extends Command { export class AppsIndex extends Command { static id = 'apps' - static summary = 'List all apps (app index command)'; + static summary = 'List all apps (app index command)' - static flags: Record = {}; + static flags: Record = {} - static args = {}; + static args = {} async run(): Promise { 'run' @@ -52,11 +52,11 @@ export class AppsIndexWithDesc extends Command { static id = 'apps' static description = `List all apps (app index command) -this only shows up in command help under DESCRIPTION`; +this only shows up in command help under DESCRIPTION` - static flags: Record = {}; + static flags: Record = {} - static args = {}; + static args = {} async run(): Promise { 'run' @@ -79,11 +79,11 @@ export class AppsAdminIndex extends Command { static id = 'apps:admin' static description = `List of admins for an app - this only shows up in command help under DESCRIPTION`; + this only shows up in command help under DESCRIPTION` - static flags: Record = {}; + static flags: Record = {} - static args = {}; + static args = {} async run(): Promise { 'run' @@ -94,11 +94,11 @@ export class AppsAdminAdd extends Command { static id = 'apps:admin:add' static description = `Add user to an app - this only shows up in command help under DESCRIPTION`; + this only shows up in command help under DESCRIPTION` - static flags: Record = {}; + static flags: Record = {} - static args = {}; + static args = {} async run(): Promise { 'run' @@ -111,11 +111,11 @@ export class DbCreate extends Command { static id = 'db:create' static description = `Create a db - this only shows up in command help under DESCRIPTION`; + this only shows up in command help under DESCRIPTION` - static flags = {}; + static flags = {} - static args = {}; + static args = {} async run(): Promise { 'run' diff --git a/test/help/format-command-with-options.test.ts b/test/help/format-command-with-options.test.ts index 5554567e5..f3a816dc6 100644 --- a/test/help/format-command-with-options.test.ts +++ b/test/help/format-command-with-options.test.ts @@ -1,7 +1,7 @@ -import {expect, test as base} from '@oclif/test' +import {test as base, expect} from '@oclif/test' import {Args, Command as Base, Flags as flags} from '../../src' -import {commandHelp, TestHelpWithOptions as TestHelp} from './help-test-utils' +import {TestHelpWithOptions as TestHelp, commandHelp} from './help-test-utils' const g: any = global g.oclif.columns = 80 @@ -20,26 +20,26 @@ const test = base describe('formatCommand', () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static aliases = ['app:init', 'create'] + static aliases = ['app:init', 'create'] - static description = `first line + static description = `first line multiline help` - static args = { - // eslint-disable-next-line camelcase - app_name: Args.string({description: 'app to use'}), - } - - static flags = { - app: flags.string({char: 'a', hidden: true}), - foo: flags.string({char: 'f', description: 'foobar'.repeat(18)}), - force: flags.boolean({description: 'force it '.repeat(15)}), - ss: flags.boolean({description: 'newliney\n'.repeat(4)}), - remote: flags.string({char: 'r'}), - label: flags.string({char: 'l', helpLabel: '-l'}), - } + static args = { + // eslint-disable-next-line camelcase + app_name: Args.string({description: 'app to use'}), + } + + static flags = { + app: flags.string({char: 'a', hidden: true}), + foo: flags.string({char: 'f', description: 'foobar'.repeat(18)}), + force: flags.boolean({description: 'force it '.repeat(15)}), + ss: flags.boolean({description: 'newliney\n'.repeat(4)}), + remote: flags.string({char: 'r'}), + label: flags.string({char: 'l', helpLabel: '-l'}), + } }) .it('handles multi-line help output', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [APP_NAME] [-f ] [--force] [--ss] [-r @@ -71,24 +71,24 @@ ALIASES describe('arg and flag multiline handling', () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static description = 'description of apps:create' + static description = 'description of apps:create' - static aliases = ['app:init', 'create'] + static aliases = ['app:init', 'create'] - static args = { - // eslint-disable-next-line camelcase - app_name: Args.string({description: 'app to use'.repeat(35)}), - } + static args = { + // eslint-disable-next-line camelcase + app_name: Args.string({description: 'app to use'.repeat(35)}), + } - static flags = { - app: flags.string({char: 'a', hidden: true}), - foo: flags.string({char: 'f', description: 'foobar'.repeat(15)}), - force: flags.boolean({description: 'force it '.repeat(15)}), - ss: flags.boolean({description: 'newliney\n'.repeat(4)}), - remote: flags.string({char: 'r'}), - } + static flags = { + app: flags.string({char: 'a', hidden: true}), + foo: flags.string({char: 'f', description: 'foobar'.repeat(15)}), + force: flags.boolean({description: 'force it '.repeat(15)}), + ss: flags.boolean({description: 'newliney\n'.repeat(4)}), + remote: flags.string({char: 'r'}), + } }) .it('show args and flags side by side when their output do not exceed 4 lines ', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [APP_NAME] [-f ] [--force] [--ss] [-r @@ -120,24 +120,24 @@ ALIASES test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static description = 'description of apps:create' + static description = 'description of apps:create' - static aliases = ['app:init', 'create'] + static aliases = ['app:init', 'create'] - static args = { - // eslint-disable-next-line camelcase - app_name: Args.string({description: 'app to use'.repeat(35)}), - } + static args = { + // eslint-disable-next-line camelcase + app_name: Args.string({description: 'app to use'.repeat(35)}), + } - static flags = { - app: flags.string({char: 'a', hidden: true}), - foo: flags.string({char: 'f', description: 'foobar'.repeat(20)}), - force: flags.boolean({description: 'force it '.repeat(29)}), - ss: flags.boolean({description: 'newliney\n'.repeat(5)}), - remote: flags.string({char: 'r'}), - } + static flags = { + app: flags.string({char: 'a', hidden: true}), + foo: flags.string({char: 'f', description: 'foobar'.repeat(20)}), + force: flags.boolean({description: 'force it '.repeat(29)}), + ss: flags.boolean({description: 'newliney\n'.repeat(5)}), + remote: flags.string({char: 'r'}), + } }) .it('shows stacked args and flags when the lines exceed 4', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [APP_NAME] [-f ] [--force] [--ss] [-r @@ -179,20 +179,20 @@ ALIASES describe('description', () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static description = 'description of apps:create\nthese values are after and will show up in the command description' + static description = 'description of apps:create\nthese values are after and will show up in the command description' - static aliases = ['app:init', 'create'] + static aliases = ['app:init', 'create'] - static args = { - // eslint-disable-next-line camelcase - app_name: Args.string({description: 'app to use'}), - } + static args = { + // eslint-disable-next-line camelcase + app_name: Args.string({description: 'app to use'}), + } - static flags = { - force: flags.boolean({description: 'forces'}), - } + static flags = { + force: flags.boolean({description: 'forces'}), + } }) .it('outputs command description with values after a \\n newline character', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [APP_NAME] [--force] @@ -212,9 +212,9 @@ ALIASES test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static description = 'root part of the description\nThe <%= config.bin %> CLI has <%= command.id %>' + static description = 'root part of the description\nThe <%= config.bin %> CLI has <%= command.id %>' }) .it('renders template string from description', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create @@ -226,13 +226,13 @@ DESCRIPTION describe(('flags'), () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static flags = { - myenum: flags.string({ - options: ['a', 'b', 'c'], - }), - } + static flags = { + myenum: flags.string({ + options: ['a', 'b', 'c'], + }), + } }) .it('outputs flag enum', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [--myenum a|b|c] @@ -271,11 +271,11 @@ OPTIONS test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static flags = { - opt: flags.boolean({allowNo: true}), - } + static flags = { + opt: flags.boolean({allowNo: true}), + } }) .it('outputs with with no options', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [--opt] @@ -287,11 +287,11 @@ OPTIONS describe('args', () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static args = { - arg1: Args.string({description: 'Show the options', options: ['option1', 'option2']}), - } + static args = { + arg1: Args.string({description: 'Show the options', options: ['option1', 'option2']}), + } }) .it('outputs with arg options', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [ARG1] @@ -303,18 +303,18 @@ ARGUMENTS describe('usage', () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static usage = '<%= config.bin %> <%= command.id %> usage' + static usage = '<%= config.bin %> <%= command.id %> usage' }) .it('outputs usage with templates', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif apps:create usage`)) test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static usage = ['<%= config.bin %>', '<%= command.id %> usage'] + static usage = ['<%= config.bin %>', '<%= command.id %> usage'] }) .it('outputs usage arrays with templates', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif @@ -333,7 +333,7 @@ ARGUMENTS describe('examples', () => { test .commandHelp(class extends Command { - static examples = ['it handles a list of examples', 'more example text'] + static examples = ['it handles a list of examples', 'more example text'] }) .it('outputs multiple examples', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif @@ -345,7 +345,7 @@ EXAMPLES test .commandHelp(class extends Command { - static examples = ['it handles a single example'] + static examples = ['it handles a single example'] }) .it('outputs a single example', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif @@ -355,9 +355,9 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = ['the bin is <%= config.bin %>', 'the command id is <%= command.id %>'] + static examples = ['the bin is <%= config.bin %>', 'the command id is <%= command.id %>'] }) .it('outputs examples using templates', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command @@ -369,9 +369,9 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = ['<%= config.bin %> <%= command.id %> --help'] + static examples = ['<%= config.bin %> <%= command.id %> --help'] }) .it('formats if command', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command @@ -381,9 +381,9 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = ['Prints out help.\n<%= config.bin %> <%= command.id %> --help'] + static examples = ['Prints out help.\n<%= config.bin %> <%= command.id %> --help'] }) .it('formats if command with description', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command @@ -395,9 +395,9 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = [{description: 'Prints out help.', command: '<%= config.bin %> <%= command.id %> --help'}] + static examples = [{description: 'Prints out help.', command: '<%= config.bin %> <%= command.id %> --help'}] }) .it('formats example object', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command diff --git a/test/help/format-command.test.ts b/test/help/format-command.test.ts index 3b401412d..72bcfb258 100644 --- a/test/help/format-command.test.ts +++ b/test/help/format-command.test.ts @@ -1,7 +1,7 @@ -import {expect, test as base} from '@oclif/test' +import {test as base, expect} from '@oclif/test' import {Args, Command as Base, Flags as flags} from '../../src' -import {commandHelp, TestHelp} from './help-test-utils' +import {TestHelp, commandHelp} from './help-test-utils' const g: any = global g.oclif.columns = 80 @@ -20,22 +20,23 @@ const test = base describe('formatCommand', () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static { + this.id = 'apps:create' - static aliases = ['app:init', 'create'] + this.aliases = ['app:init', 'create'] - static description = `first line + this.description = `first line -multiline help` + multiline help` - static enableJsonFlag = true + this.enableJsonFlag = true - static args = { + this.args = { // eslint-disable-next-line camelcase app_name: Args.string({description: 'app to use'}), } - static flags = { + this.flags = { app: flags.string({char: 'a', hidden: true}), foo: flags.string({char: 'f', description: 'foobar'.repeat(18)}), force: flags.boolean({description: 'force it '.repeat(15)}), @@ -43,6 +44,7 @@ multiline help` remote: flags.string({char: 'r'}), label: flags.string({char: 'l', helpLabel: '-l'}), } + } }) .it('handles multi-line help output', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [APP_NAME] [--json] [-f ] [--force] [--ss] @@ -79,26 +81,28 @@ ALIASES describe('arg and flag multiline handling', () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static { + this.id = 'apps:create' - static description = 'description of apps:create' + this.description = 'description of apps:create' - static aliases = ['app:init', 'create'] + this.aliases = ['app:init', 'create'] - static enableJsonFlag = true + this.enableJsonFlag = true - static args = { + this.args = { // eslint-disable-next-line camelcase app_name: Args.string({description: 'app to use'.repeat(35)}), } - static flags = { + this.flags = { app: flags.string({char: 'a', hidden: true}), foo: flags.string({char: 'f', description: 'foobar'.repeat(15)}), force: flags.boolean({description: 'force it '.repeat(15)}), ss: flags.boolean({description: 'newliney\n'.repeat(4)}), remote: flags.string({char: 'r'}), } + } }) .it('show args and flags side by side when their output do not exceed 4 lines ', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [APP_NAME] [--json] [-f ] [--force] [--ss] @@ -136,26 +140,28 @@ ALIASES test .commandHelp(class extends Command { - static id = 'apps:create' + static { + this.id = 'apps:create' - static description = 'description of apps:create' + this.description = 'description of apps:create' - static aliases = ['app:init', 'create'] + this.aliases = ['app:init', 'create'] - static enableJsonFlag = true + this.enableJsonFlag = true - static args = { + this.args = { // eslint-disable-next-line camelcase app_name: Args.string({description: 'app to use'.repeat(35)}), } - static flags = { + this.flags = { app: flags.string({char: 'a', hidden: true}), foo: flags.string({char: 'f', description: 'foobar'.repeat(20)}), force: flags.boolean({description: 'force it '.repeat(29)}), ss: flags.boolean({description: 'newliney\n'.repeat(5)}), remote: flags.string({char: 'r'}), } + } }) .it('shows stacked args and flags when the lines exceed 4', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [APP_NAME] [--json] [-f ] [--force] [--ss] @@ -230,22 +236,24 @@ DESCRIPTION describe('description', () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static { + this.id = 'apps:create' - static description = 'description of apps:create\n\nthese values are after and will show up in the command description' + this.description = 'description of apps:create\n\nthese values are after and will show up in the command description' - static aliases = ['app:init', 'create'] + this.aliases = ['app:init', 'create'] - static enableJsonFlag = true + this.enableJsonFlag = true - static args = { + this.args = { // eslint-disable-next-line camelcase app_name: Args.string({description: 'app to use'}), } - static flags = { + this.flags = { force: flags.boolean({description: 'forces'}), } + } }) .it('outputs command description with values after a \\n newline character', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [APP_NAME] [--json] [--force] @@ -270,9 +278,9 @@ ALIASES test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static description = 'root part of the description\n\nThe <%= config.bin %> CLI has <%= command.id %>' + static description = 'root part of the description\n\nThe <%= config.bin %> CLI has <%= command.id %>' }) .it('renders template string from description', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create @@ -284,9 +292,9 @@ DESCRIPTION test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static description = 'root part of the description\r\n\nusing both carriage \n\nreturn and new line' + static description = 'root part of the description\r\n\nusing both carriage \n\nreturn and new line' }) .it('splits on carriage return and new lines', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create @@ -303,14 +311,16 @@ DESCRIPTION describe(('flags'), () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static { + this.id = 'apps:create' - static flags = { + this.flags = { myenum: flags.string({ description: 'the description', options: myEnumValues, }), } + } }) .it('outputs flag enum', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [--myenum a|b|c] @@ -321,14 +331,14 @@ FLAGS test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static flags = { - myenum: flags.string({ - options: myEnumValues, - helpValue: myEnumValues.join('|'), - }), - } + static flags = { + myenum: flags.string({ + options: myEnumValues, + helpValue: myEnumValues.join('|'), + }), + } }) .it('outputs flag enum with helpValue', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [--myenum a|b|c] @@ -367,11 +377,11 @@ FLAGS test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static flags = { - opt: flags.boolean({allowNo: true}), - } + static flags = { + opt: flags.boolean({allowNo: true}), + } }) .it('outputs with with no options', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [--opt] @@ -381,14 +391,14 @@ FLAGS test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static flags = { - opt: flags.string({ - summary: 'one line summary', - description: 'multiline\ndescription', - }), - } + static flags = { + opt: flags.string({ + summary: 'one line summary', + description: 'multiline\ndescription', + }), + } }) .it('outputs flag summary and description', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [--opt ] @@ -404,14 +414,14 @@ FLAG DESCRIPTIONS test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static flags = { - opt: flags.string({ - summary: 'one line summary', - description: 'single line description', - }), - } + static flags = { + opt: flags.string({ + summary: 'one line summary', + description: 'single line description', + }), + } }) .it('outputs flag summary and single line description', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [--opt ] @@ -426,14 +436,14 @@ FLAG DESCRIPTIONS test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static flags = { - opt: flags.string({ - summary: 'one line summary'.repeat(15), - description: 'single line description', - }), - } + static flags = { + opt: flags.string({ + summary: 'one line summary'.repeat(15), + description: 'single line description', + }), + } }) .it('outputs long flag summary and single line description', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [--opt ] @@ -458,11 +468,11 @@ FLAG DESCRIPTIONS describe('args', () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static args = { - arg1: Args.string({description: 'Show the options', options: ['option1', 'option2']}), - } + static args = { + arg1: Args.string({description: 'Show the options', options: ['option1', 'option2']}), + } }) .it('outputs with arg options', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [ARG1] @@ -474,18 +484,18 @@ ARGUMENTS describe('usage', () => { test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static usage = '<%= config.bin %> <%= command.id %> usage' + static usage = '<%= config.bin %> <%= command.id %> usage' }) .it('outputs usage with templates', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif apps:create usage`)) test .commandHelp(class extends Command { - static id = 'apps:create' + static id = 'apps:create' - static usage = ['<%= config.bin %>', '<%= command.id %> usage'] + static usage = ['<%= config.bin %>', '<%= command.id %> usage'] }) .it('outputs usage arrays with templates', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif @@ -504,7 +514,7 @@ ARGUMENTS describe('examples', () => { test .commandHelp(class extends Command { - static examples = ['it handles a list of examples', 'more example text'] + static examples = ['it handles a list of examples', 'more example text'] }) .it('outputs multiple examples', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif @@ -516,7 +526,7 @@ EXAMPLES test .commandHelp(class extends Command { - static examples = ['it handles a single example'] + static examples = ['it handles a single example'] }) .it('outputs a single example', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif @@ -526,9 +536,9 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = ['the bin is <%= config.bin %>', 'the command id is <%= command.id %>'] + static examples = ['the bin is <%= config.bin %>', 'the command id is <%= command.id %>'] }) .it('outputs examples using templates', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command @@ -540,9 +550,9 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = ['<%= config.bin %> <%= command.id %> --help'] + static examples = ['<%= config.bin %> <%= command.id %> --help'] }) .it('formats if command', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command @@ -552,9 +562,9 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = ['Prints out help.\n<%= config.bin %> <%= command.id %> --help'] + static examples = ['Prints out help.\n<%= config.bin %> <%= command.id %> --help'] }) .it('formats if command with description', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command @@ -566,9 +576,9 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = ['Prints out help.\n<%= config.bin %> <%= command.id %> --help\n<%= config.bin %> <%= command.id %> --help'] + static examples = ['Prints out help.\n<%= config.bin %> <%= command.id %> --help\n<%= config.bin %> <%= command.id %> --help'] }) .it('formats if multiple command with description', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command @@ -581,9 +591,9 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = [{description: 'Prints out help.', command: '<%= config.bin %> <%= command.id %> --help'}] + static examples = [{description: 'Prints out help.', command: '<%= config.bin %> <%= command.id %> --help'}] }) .it('formats example object', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command @@ -595,9 +605,9 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = [{description: 'force it '.repeat(15), command: '<%= config.bin %> <%= command.id %> --help'}] + static examples = [{description: 'force it '.repeat(15), command: '<%= config.bin %> <%= command.id %> --help'}] }) .it('formats example object with long description', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command @@ -610,15 +620,15 @@ EXAMPLES test .commandHelp(class extends Command { - static id = 'oclif:command' + static id = 'oclif:command' - static examples = [{description: 'Prints out help.', command: '<%= config.bin %> <%= command.id %> ' + 'force it '.repeat(15)}] + static examples = [{description: 'Prints out help.', command: '<%= config.bin %> <%= command.id %> ' + 'force it '.repeat(15)}] }) .it('formats example object with long command', (ctx: any) => { - const multilineSeparator = - ctx.config.platform === 'win32' ? - (ctx.config.shell.includes('powershell') ? '`' : '^') : - '\\' + const multilineSeparator + = ctx.config.platform === 'win32' + ? (ctx.config.shell.includes('powershell') ? '`' : '^') + : '\\' expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command diff --git a/test/help/format-commands.test.ts b/test/help/format-commands.test.ts index ec88b81a3..9ae47a0ba 100644 --- a/test/help/format-commands.test.ts +++ b/test/help/format-commands.test.ts @@ -1,11 +1,11 @@ import {Command} from '../../src/command' -import {expect, test as base} from '@oclif/test' +import {test as base, expect} from '@oclif/test' import stripAnsi = require('strip-ansi') const g: any = global g.oclif.columns = 80 import {Help} from '../../src/help' -import {AppsDestroy, AppsCreate} from './fixtures/fixtures' +import {AppsCreate, AppsDestroy} from './fixtures/fixtures' // extensions to expose method as public for testing class TestHelp extends Help { diff --git a/test/help/format-root.test.ts b/test/help/format-root.test.ts index 017039a9c..c8d18a956 100644 --- a/test/help/format-root.test.ts +++ b/test/help/format-root.test.ts @@ -1,4 +1,4 @@ -import {expect, test as base} from '@oclif/test' +import {test as base, expect} from '@oclif/test' import stripAnsi = require('strip-ansi') import {Help} from '../../src/help' @@ -48,15 +48,13 @@ USAGE describe('description', () => { test - .rootHelp(config => { - return { - ...config, - pjson: { - ...config.pjson, - description: 'This is the top-level description that appears in the root\nThis appears in the description section after usage', - }, - } - }) + .rootHelp(config => ({ + ...config, + pjson: { + ...config.pjson, + description: 'This is the top-level description that appears in the root\nThis appears in the description section after usage', + }, + })) .it('splits on \\n for the description into the top-level and description sections', ctx => { expect(ctx.commandHelp).to.equal(`This is the top-level description that appears in the root @@ -71,15 +69,13 @@ DESCRIPTION }) test - .rootHelp(config => { - return { - ...config, - pjson: { - ...config.pjson, - description: 'This is the top-level description for <%= config.bin %>\nThis <%= config.bin %> appears in the description section after usage', - }, - } - }) + .rootHelp(config => ({ + ...config, + pjson: { + ...config.pjson, + description: 'This is the top-level description for <%= config.bin %>\nThis <%= config.bin %> appears in the description section after usage', + }, + })) .it('shows description from a template', ctx => { expect(ctx.commandHelp).to.equal(`This is the top-level description for oclif @@ -94,19 +90,17 @@ DESCRIPTION }) test - .rootHelp(config => { - return { - ...config, - pjson: { - ...config.pjson, - description: 'THIS IS THE PJSON DESCRIPTION', - oclif: { - ...config.pjson?.oclif, - description: 'THIS IS THE OCLIF DESCRIPTION IN PJSON', - }, + .rootHelp(config => ({ + ...config, + pjson: { + ...config.pjson, + description: 'THIS IS THE PJSON DESCRIPTION', + oclif: { + ...config.pjson?.oclif, + description: 'THIS IS THE OCLIF DESCRIPTION IN PJSON', }, - } - }) + }, + })) .it('prefers the oclif description over the package.json description', ctx => { expect(ctx.commandHelp).to.equal(`THIS IS THE OCLIF DESCRIPTION IN PJSON @@ -118,19 +112,17 @@ USAGE }) test - .rootHelp(config => { - return { - ...config, - pjson: { - ...config.pjson, - description: 'THIS IS THE PJSON DESCRIPTION', - oclif: { - ...config.pjson?.oclif, - description: undefined, - }, + .rootHelp(config => ({ + ...config, + pjson: { + ...config.pjson, + description: 'THIS IS THE PJSON DESCRIPTION', + oclif: { + ...config.pjson?.oclif, + description: undefined, }, - } - }) + }, + })) .it('uses package.json description when the oclif description is not set', ctx => { expect(ctx.commandHelp).to.equal(`THIS IS THE PJSON DESCRIPTION diff --git a/test/help/format-topic.test.ts b/test/help/format-topic.test.ts index 73a331164..4dfcf01ed 100644 --- a/test/help/format-topic.test.ts +++ b/test/help/format-topic.test.ts @@ -1,4 +1,4 @@ -import {expect, test as base} from '@oclif/test' +import {test as base, expect} from '@oclif/test' import {TestHelp, topicHelp} from './help-test-utils' const g: any = global @@ -6,9 +6,7 @@ g.oclif.columns = 80 const test = base .loadConfig() -.add('help', ctx => { - return new TestHelp(ctx.config as any) -}) +.add('help', ctx => new TestHelp(ctx.config as any)) .register('topicHelp', topicHelp) describe('formatHelp', () => { diff --git a/test/help/format-topics.test.ts b/test/help/format-topics.test.ts index 22f064f6f..4787c4f77 100644 --- a/test/help/format-topics.test.ts +++ b/test/help/format-topics.test.ts @@ -1,4 +1,4 @@ -import {expect, test as base} from '@oclif/test' +import {test as base, expect} from '@oclif/test' import {TestHelp, topicsHelp} from './help-test-utils' diff --git a/test/help/help-test-utils.ts b/test/help/help-test-utils.ts index e9542dc16..cf94558b2 100644 --- a/test/help/help-test-utils.ts +++ b/test/help/help-test-utils.ts @@ -1,9 +1,9 @@ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + import {Command} from '../../src/command' import stripAnsi = require('strip-ansi') import {Interfaces, toCached} from '../../src' -import {Help, CommandHelp} from '../../src/help' +import {CommandHelp, Help} from '../../src/help' export class TestCommandHelp extends CommandHelp { protected sections() { diff --git a/test/help/show-customized-help.test.ts b/test/help/show-customized-help.test.ts index e8d5586a6..1b0b47547 100644 --- a/test/help/show-customized-help.test.ts +++ b/test/help/show-customized-help.test.ts @@ -1,10 +1,10 @@ -import {expect, test as base} from '@oclif/test' -import {stub, SinonStub} from 'sinon' -import * as path from 'path' +import {test as base, expect} from '@oclif/test' +import {SinonStub, stub} from 'sinon' +import {resolve} from 'node:path' import {CommandHelp, Help} from '../../src/help' -import {AppsIndexWithDesc, AppsDestroy, AppsCreate, AppsTopic, AppsAdminTopic, AppsAdminAdd} from './fixtures/fixtures' -import {Interfaces, Config} from '../../src' +import {AppsAdminAdd, AppsAdminTopic, AppsCreate, AppsDestroy, AppsIndexWithDesc, AppsTopic} from './fixtures/fixtures' +import {Config, Interfaces} from '../../src' import {Command} from '../../src/command' import {monkeyPatchCommands} from './help-test-utils' @@ -32,7 +32,7 @@ ${this.indent(this.wrap('force it '.repeat(29)))}`, class TestHelp extends Help { CommandHelpClass = TestCommandHelp - public config: any; + public declare config: any constructor(config: Interfaces.Config, opts: Partial = {}) { super(config, opts) @@ -63,7 +63,7 @@ const test = base } // use devPlugins: true to bring in plugins-plugin with topic commands for testing - const config = await Config.load({devPlugins: true, root: path.resolve(__dirname, '..')}) + const config = await Config.load({devPlugins: true, root: resolve(__dirname, '..')}) ctx.help = new TestHelp(config) }, finally(ctx) { @@ -87,7 +87,7 @@ describe('showHelp for root', () => { .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', @@ -119,7 +119,7 @@ COMMANDS .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', @@ -150,7 +150,7 @@ describe('showHelp for a command', () => { .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', @@ -180,7 +180,7 @@ CUSTOM .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', diff --git a/test/help/show-help.test.ts b/test/help/show-help.test.ts index 976089a32..2f7617369 100644 --- a/test/help/show-help.test.ts +++ b/test/help/show-help.test.ts @@ -1,10 +1,10 @@ import {test as base} from '@oclif/test' -import {stub, SinonStub} from 'sinon' -import * as path from 'path' +import {SinonStub, stub} from 'sinon' +import {resolve} from 'node:path' import {Help} from '../../src/help' -import {AppsIndex, AppsDestroy, AppsCreate, AppsTopic, AppsAdminTopic, AppsAdminAdd, AppsAdminIndex, DbCreate, DbTopic} from './fixtures/fixtures' -import {Interfaces, Config} from '../../src' +import {AppsAdminAdd, AppsAdminIndex, AppsAdminTopic, AppsCreate, AppsDestroy, AppsIndex, AppsTopic, DbCreate, DbTopic} from './fixtures/fixtures' +import {Config, Interfaces} from '../../src' import {monkeyPatchCommands} from './help-test-utils' import {expect} from 'chai' @@ -13,7 +13,7 @@ g.oclif.columns = 80 // extension makes previously protected methods public class TestHelp extends Help { - public config: any; + public declare config: any public async showRootHelp() { return super.showRootHelp() @@ -34,7 +34,7 @@ const test = base } // use devPlugins: true to bring in plugins-plugin with topic commands for testing - const config = await Config.load({devPlugins: true, root: path.resolve(__dirname, '..')}) + const config = await Config.load({devPlugins: true, root: resolve(__dirname, '..')}) ctx.help = new TestHelp(config) }, finally(ctx) { @@ -58,7 +58,7 @@ describe('showHelp for root', () => { .loadConfig() .stdout() .do(async () => { - const config = await Config.load({root: path.resolve(__dirname, '..')}); + const config = await Config.load({root: resolve(__dirname, '..')}); (config as any).plugins = [{ commands: [AppsIndex, AppsCreate, AppsDestroy], @@ -97,7 +97,7 @@ COMMANDS .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', @@ -127,7 +127,7 @@ describe('showHelp for a topic', () => { .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', @@ -153,7 +153,7 @@ COMMANDS .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', @@ -182,7 +182,7 @@ COMMANDS .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', @@ -212,7 +212,7 @@ COMMANDS .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', commands: [AppsCreate, AppsDestroy, AppsAdminAdd, DbCreate], @@ -242,7 +242,7 @@ describe('showHelp for a command', () => { .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', commands: [AppsCreate], @@ -268,7 +268,7 @@ DESCRIPTION .loadConfig() .stdout() .do(async ctx => { - const config = ctx.config + const {config} = ctx monkeyPatchCommands(config, [{ name: 'plugin-1', commands: [AppsIndex, AppsCreate, AppsAdminAdd], diff --git a/test/help/util.test.ts b/test/help/util.test.ts index 29cfc8fa0..c25aa0b3f 100644 --- a/test/help/util.test.ts +++ b/test/help/util.test.ts @@ -1,4 +1,4 @@ -import {resolve} from 'path' +import {resolve} from 'node:path' import {Config, Interfaces} from '../../src' import {test} from '@oclif/test' import {loadHelpClass, standardizeIDFromArgv} from '../../src/help' diff --git a/test/integration/esm-cjs.ts b/test/integration/esm-cjs.ts index afbd5c7eb..c557cee2a 100644 --- a/test/integration/esm-cjs.ts +++ b/test/integration/esm-cjs.ts @@ -6,11 +6,11 @@ * Instead of spending more time diagnosing the root cause, we are just going to * run these integration tests using ts-node and a lightweight homemade test runner. */ -import * as fs from 'fs/promises' -import * as path from 'path' +import * as fs from 'node:fs/promises' +import * as path from 'node:path' import {Executor, setup} from './util' import {expect} from 'chai' -import {bold, green, red} from 'chalk' +import chalk from 'chalk' const FAILED: string[] = [] const PASSED: string[] = [] @@ -19,28 +19,28 @@ async function test(name: string, fn: () => Promise) { try { await fn() PASSED.push(name) - console.log(green('✓'), name) + console.log(chalk.green('✓'), name) } catch (error) { FAILED.push(name) - console.log(red('𐄂'), name) + console.log(chalk.red('𐄂'), name) console.log(error) } } function exit(): never { console.log() - console.log(bold('#### Summary ####')) + console.log(chalk.bold('#### Summary ####')) for (const name of PASSED) { - console.log(green('✓'), name) + console.log(chalk.green('✓'), name) } for (const name of FAILED) { - console.log(red('𐄂'), name) + console.log(chalk.red('𐄂'), name) } - console.log(`${green('Passed:')} ${PASSED.length}`) - console.log(`${red('Failed:')} ${FAILED.length}`) + console.log(`${chalk.green('Passed:')} ${PASSED.length}`) + console.log(`${chalk.red('Failed:')} ${FAILED.length}`) // eslint-disable-next-line no-process-exit, unicorn/no-process-exit process.exit(FAILED.length) @@ -65,14 +65,17 @@ type LinkPluginOptions = { executor: Executor; plugin: Plugin; script: Script; + noLinkCore?: boolean; } type RunCommandOptions = { executor: Executor; plugin: Plugin; script: Script; - expectStrings?: string[]; + expectStrings?: string[] + expectJson?: Record env?: Record; + args?: Array; } type ModifyCommandOptions = { @@ -88,8 +91,57 @@ type CleanUpOptions = { plugin: Plugin; } +type PluginConfig = { + name: string; + command: string; + package: string; + repo: string; + commandText: string; + hookText: string; + expectJson: { + whenProvided: { + args: Record; + flags: Record; + }; + whenNotProvided: { + args: Record; + flags: Record; + }; + } +} + +// eslint-disable-next-line unicorn/prefer-top-level-await (async () => { - const PLUGINS = { + const commonProps = { + expectJson: { + whenProvided: { + args: { + optionalArg: 'arg1', + defaultArg: 'arg2', + defaultFnArg: 'arg3', + }, + flags: { + optionalString: 'flag1', + defaultString: 'flag2', + defaultFnString: 'flag3', + json: true, + }, + }, + whenNotProvided: { + args: { + defaultArg: 'simple string default', + defaultFnArg: 'async fn default', + }, + flags: { + defaultString: 'simple string default', + defaultFnString: 'async fn default', + json: true, + }, + }, + }, + } + + const PLUGINS: Record = { esm1: { name: 'plugin-test-esm-1', command: 'esm1', @@ -97,6 +149,7 @@ type CleanUpOptions = { repo: 'https://github.com/oclif/plugin-test-esm-1', commandText: 'hello I am an ESM plugin', hookText: 'Greetings! from plugin-test-esm-1 init hook', + ...commonProps, }, esm2: { name: 'plugin-test-esm-2', @@ -105,6 +158,7 @@ type CleanUpOptions = { repo: 'https://github.com/oclif/plugin-test-esm-2', commandText: 'hello I am an ESM plugin', hookText: 'Greetings! from plugin-test-esm-2 init hook', + ...commonProps, }, cjs1: { name: 'plugin-test-cjs-1', @@ -113,6 +167,7 @@ type CleanUpOptions = { repo: 'https://github.com/oclif/plugin-test-cjs-1', commandText: 'hello I am a CJS plugin', hookText: 'Greetings! from plugin-test-cjs-1 init hook', + ...commonProps, }, cjs2: { name: 'plugin-test-cjs-2', @@ -121,6 +176,47 @@ type CleanUpOptions = { repo: 'https://github.com/oclif/plugin-test-cjs-2', commandText: 'hello I am a CJS plugin', hookText: 'Greetings! from plugin-test-cjs-2 init hook', + ...commonProps, + }, + precore: { + name: 'plugin-test-pre-core', + command: 'pre-core', + package: '@oclif/plugin-test-pre-core', + repo: 'https://github.com/oclif/plugin-test-pre-core', + commandText: 'hello I am a pre-core plugin', + hookText: 'Greetings! from plugin-test-pre-core init hook', + expectJson: { + whenProvided: commonProps.expectJson.whenProvided, + whenNotProvided: { + args: { + defaultArg: 'simple string default', + defaultFnArg: 'fn default', + }, + flags: { + defaultString: 'simple string default', + defaultFnString: 'fn default', + json: true, + }, + }, + }, + }, + coreV1: { + name: 'plugin-test-core-v1', + command: 'core-v1', + package: '@oclif/plugin-test-core-v1', + repo: 'https://github.com/oclif/plugin-test-core-v1', + commandText: 'hello I am an @oclif/core@v1 plugin', + hookText: 'Greetings! from plugin-test-core-v1 init hook', + ...commonProps, + }, + coreV2: { + name: 'plugin-test-core-v2', + command: 'core-v2', + package: '@oclif/plugin-test-core-v2', + repo: 'https://github.com/oclif/plugin-test-core-v2', + commandText: 'hello I am an @oclif/core@v2 plugin', + hookText: 'Greetings! from plugin-test-core-v2 init hook', + ...commonProps, }, } @@ -136,6 +232,7 @@ type CleanUpOptions = { const pluginExecutor = await setup(__filename, { repo: options.plugin.repo, subDir: options.executor.parentDir, + noLinkCore: options.noLinkCore ?? false, }) const result = await options.executor.executeCommand(`plugins:link ${pluginExecutor.pluginDir}`, options.script) @@ -156,7 +253,11 @@ type CleanUpOptions = { async function runCommand(options: RunCommandOptions): Promise { const env = {...process.env, ...options.env} - const result = await options.executor.executeCommand(options.plugin.command, options.script, {env}) + const result = await options.executor.executeCommand( + `${options.plugin.command} ${options.args?.join(' ') ?? ''}`, + options.script, + {env}, + ) expect(result.code).to.equal(0) if (options.expectStrings) { @@ -164,24 +265,39 @@ type CleanUpOptions = { expect(result.stdout).to.include(expectString) } } + + if (options.expectJson && options.args?.includes('--json')) { + // clear any non-json output from hooks + const split = result.stdout?.split('\n') ?? [] + const idx = split.findIndex(i => i.startsWith('{')) + const json = JSON.parse(split.slice(idx).join('\n')) + expect(json).to.deep.equal(options.expectJson) + } } async function cleanUp(options: CleanUpOptions): Promise { - await options.executor.executeCommand(`plugins:uninstall @oclif/${options.plugin.name}`) - expect((await options.executor.executeCommand('plugins')).stdout).to.not.include(options.plugin.name) + await options.executor.executeCommand(`plugins:uninstall ${options.plugin.package}`) + const {stdout} = await options.executor.executeCommand('plugins') + expect(stdout).to.not.include(options.plugin.package) } const args = process.argv.slice(process.argv.indexOf(__filename) + 1) - const runInParallel = args.includes('--parallel') - const skip = args.find(arg => arg.startsWith('--skip=')) - - const skips = skip ? skip.split('=')[1].split(',') : [] - const runEsmTests = !skips.includes('esm') - const runCjsTests = !skips.includes('cjs') + const providedSkips = args.find(arg => arg.startsWith('--skip=')) + const providedTests = args.find(arg => arg.startsWith('--test=')) ?? '=cjs,esm,precore,coreV1,coreV2' + + const skips = providedSkips ? providedSkips.split('=')[1].split(',') : [] + const tests = providedTests ? providedTests.split('=')[1].split(',') : [] + + const runTests = { + esm: tests.includes('esm') && !skips.includes('esm'), + cjs: tests.includes('cjs') && !skips.includes('cjs'), + precore: tests.includes('precore') && !skips.includes('precore'), + coreV1: tests.includes('coreV1') && !skips.includes('coreV1'), + coreV2: tests.includes('coreV2') && !skips.includes('coreV2'), + } console.log('Node version:', process.version) - console.log(runInParallel ? '🐇 Running tests in parallel' : '🐢 Running tests sequentially') - if (skips.length > 0) console.log(`🚨 Skipping ${skips.join(', ')} tests 🚨`) + console.log('Running tests:', runTests) let cjsExecutor: Executor let esmExecutor: Executor @@ -194,79 +310,116 @@ type CleanUpOptions = { esmExecutor = await setup(__filename, {repo: PLUGINS.esm1.repo, subDir: 'esm'}) } - const cjsTests = async () => { - await test('Install CJS plugin to CJS root plugin', async () => { - const plugin = PLUGINS.cjs2 + const precoreBefore = async () => { + if (!cjsExecutor) await cjsBefore() + if (!esmExecutor) await esmBefore() + } - await installPlugin({executor: cjsExecutor, plugin, script: 'run'}) - await runCommand({ - executor: cjsExecutor, - plugin, - script: 'run', - expectStrings: [plugin.commandText, plugin.hookText], - }) - await runCommand({ - executor: cjsExecutor, - plugin, - script: 'dev', - expectStrings: [plugin.commandText, plugin.hookText], - }) - await cleanUp({executor: cjsExecutor, plugin, script: 'run'}) + const coreV1Before = async () => { + if (!cjsExecutor) await cjsBefore() + if (!esmExecutor) await esmBefore() + } + + const coreV2Before = async () => { + if (!cjsExecutor) await cjsBefore() + if (!esmExecutor) await esmBefore() + } + + const installTest = async (plugin: PluginConfig, executor: Executor) => { + await installPlugin({executor, plugin, script: 'run'}) + + // test that the root plugin's bin/run can execute the installed plugin + await runCommand({ + executor, + plugin, + script: 'run', + expectStrings: [plugin.commandText], }) - await test('Install ESM plugin to CJS root plugin', async () => { - const plugin = PLUGINS.esm1 + // test that the root plugin's bin/run can execute the installed plugin + // and that args and flags work as expected when no values are provided + await runCommand({ + executor, + plugin, + script: 'run', + args: ['--json'], + expectJson: plugin.expectJson.whenNotProvided, + }) - await installPlugin({executor: cjsExecutor, plugin, script: 'run'}) - await runCommand({ - executor: cjsExecutor, - plugin, - script: 'run', - expectStrings: [plugin.commandText, plugin.hookText], - }) - await runCommand({ - executor: cjsExecutor, - plugin, - script: 'dev', - expectStrings: [plugin.commandText, plugin.hookText], - }) - await cleanUp({executor: cjsExecutor, plugin, script: 'run'}) + // test that the root plugin's bin/run can execute the installed plugin + // and that args and flags work as expected when values are provided + await runCommand({ + executor, + plugin, + script: 'run', + args: [ + ...Object.values(plugin.expectJson.whenProvided.args), + ...Object.entries(plugin.expectJson.whenProvided.flags).map(([flag, value]) => { + if (flag === 'json') return '--json' + return `--${flag} ${value}` + }), + ], + expectJson: plugin.expectJson.whenProvided, }) - await test('Link CJS plugin to CJS root plugin', async () => { - const plugin = PLUGINS.cjs2 + // test that the root plugin's bin/dev can execute the installed plugin + await runCommand({ + executor, + plugin, + script: 'dev', + expectStrings: [plugin.commandText], + }) - const linkedPlugin = await linkPlugin({executor: cjsExecutor, plugin, script: 'run'}) + await cleanUp({executor, plugin, script: 'run'}) + } - // test bin/run - await runCommand({ - executor: cjsExecutor, - plugin, - script: 'run', - expectStrings: [plugin.commandText, plugin.hookText], - }) - // test un-compiled changes with bin/run - await modifyCommand({executor: linkedPlugin, plugin, from: 'hello', to: 'howdy'}) - await runCommand({ - executor: cjsExecutor, - plugin, - script: 'run', - expectStrings: ['howdy', plugin.hookText], - }) + const linkTest = async (plugin: PluginConfig, executor: Executor, noLinkCore = false) => { + const linkedPlugin = await linkPlugin({executor, plugin, script: 'run', noLinkCore}) - // test un-compiled changes with bin/dev - await modifyCommand({executor: linkedPlugin, plugin, from: 'howdy', to: 'cheers'}) - await runCommand({ - executor: cjsExecutor, - plugin, - script: 'dev', - expectStrings: ['cheers', plugin.hookText], - }) + // test bin/run + await runCommand({ + executor, + plugin, + script: 'run', + expectStrings: [plugin.commandText, plugin.hookText], + }) + // test un-compiled changes with bin/run + await modifyCommand({executor: linkedPlugin, plugin, from: 'hello', to: 'howdy'}) + await runCommand({ + executor, + plugin, + script: 'run', + expectStrings: ['howdy', plugin.hookText], + }) - await cleanUp({executor: cjsExecutor, plugin, script: 'run'}) + // test un-compiled changes with bin/dev + await modifyCommand({executor: linkedPlugin, plugin, from: 'howdy', to: 'cheers'}) + await runCommand({ + executor, + plugin, + script: 'dev', + expectStrings: ['cheers', plugin.hookText], + }) + + await cleanUp({executor, plugin, script: 'run'}) + } + + const cjsTests = async () => { + await test('Install CJS plugin to CJS root plugin', async () => { + await installTest(PLUGINS.cjs2, cjsExecutor) + }) + + await test('Install ESM plugin to CJS root plugin', async () => { + await installTest(PLUGINS.esm1, cjsExecutor) + }) + + await test('Link CJS plugin to CJS root plugin', async () => { + await linkTest(PLUGINS.cjs2, cjsExecutor) }) await test('Link ESM plugin to CJS root plugin', async () => { + // We don't use linkTest here because that would test that the + // ESM plugin is auto-transpiled which we're not supporting at the moment. const plugin = PLUGINS.esm2 await linkPlugin({executor: cjsExecutor, plugin, script: 'run'}) @@ -293,73 +446,15 @@ type CleanUpOptions = { const esmTests = async () => { await test('Install CJS plugin to ESM root plugin', async () => { - const plugin = PLUGINS.cjs1 - - await installPlugin({executor: esmExecutor, plugin, script: 'run'}) - await runCommand({ - executor: esmExecutor, - plugin, - script: 'run', - expectStrings: [plugin.commandText, plugin.hookText], - }) - await runCommand({ - executor: esmExecutor, - plugin, - script: 'dev', - expectStrings: [plugin.commandText, plugin.hookText], - }) - await cleanUp({executor: esmExecutor, plugin, script: 'run'}) + await installTest(PLUGINS.cjs1, esmExecutor) }) await test('Install ESM plugin to ESM root plugin', async () => { - const plugin = PLUGINS.esm2 - - await installPlugin({executor: esmExecutor, plugin, script: 'run'}) - await runCommand({ - executor: esmExecutor, - plugin, - script: 'run', - expectStrings: [plugin.commandText, plugin.hookText], - }) - await runCommand({ - executor: esmExecutor, - plugin, - script: 'dev', - expectStrings: [plugin.commandText, plugin.hookText], - }) - await cleanUp({executor: esmExecutor, plugin, script: 'run'}) + await installTest(PLUGINS.esm2, esmExecutor) }) await test('Link CJS plugin to ESM root plugin', async () => { - const plugin = PLUGINS.cjs1 - - const linkedPlugin = await linkPlugin({executor: esmExecutor, plugin, script: 'run'}) - // test bin/run - await runCommand({ - executor: esmExecutor, - plugin, - script: 'run', - expectStrings: [plugin.commandText, plugin.hookText], - }) - // test un-compiled changes with bin/run - await modifyCommand({executor: linkedPlugin, plugin, from: 'hello', to: 'howdy'}) - await runCommand({ - executor: esmExecutor, - plugin, - script: 'run', - expectStrings: ['howdy', plugin.hookText], - }) - - // test un-compiled changes with bin/dev - await modifyCommand({executor: linkedPlugin, plugin, from: 'howdy', to: 'cheers'}) - await runCommand({ - executor: esmExecutor, - plugin, - script: 'dev', - expectStrings: ['cheers', plugin.hookText], - }) - - await cleanUp({executor: esmExecutor, plugin, script: 'run'}) + await linkTest(PLUGINS.cjs1, esmExecutor) }) await test('Link ESM plugin to ESM root plugin', async () => { @@ -398,24 +493,84 @@ type CleanUpOptions = { }) } - if (runInParallel) { - await Promise.all([ - runCjsTests ? cjsBefore() : Promise.resolve(), - runEsmTests ? esmBefore() : Promise.resolve(), - ]) - - await Promise.all([ - runCjsTests ? cjsTests() : Promise.resolve(), - runEsmTests ? esmTests() : Promise.resolve(), - ]) - } else { - if (runCjsTests) await cjsBefore() - if (runEsmTests) await esmBefore() - - if (runCjsTests) await cjsTests() - if (runEsmTests) await esmTests() + const preCoreTests = async () => { + await test('Install pre-core plugin to ESM root plugin', async () => { + await installTest(PLUGINS.precore, esmExecutor) + }) + + await test('Install pre-core plugin to CJS root plugin', async () => { + await installTest(PLUGINS.precore, cjsExecutor) + }) + + await test('Link pre-core plugin to CJS root plugin', async () => { + // Pass in true to skip linking the local version of @oclif/core + // to the test pre-core plugin since it doesn't use core. + await linkTest(PLUGINS.precore, cjsExecutor, true) + }) + + await test('Link pre-core plugin to ESM root plugin', async () => { + // Pass in true to skip linking the local version of @oclif/core + // to the test pre-core plugin since it doesn't use core. + await linkTest(PLUGINS.precore, esmExecutor, true) + }) + } + + const coreV1Tests = async () => { + await test('Install core v1 plugin to ESM root plugin', async () => { + await installTest(PLUGINS.coreV1, esmExecutor) + }) + + await test('Install core v1 plugin to CJS root plugin', async () => { + await installTest(PLUGINS.coreV1, cjsExecutor) + }) + + await test('Link core v1 plugin to CJS root plugin', async () => { + // Pass in true to skip linking the local version of @oclif/core + // to plugin-test-core-v1. There are breaking changes to how + // args are defined in a command so the plugin won't compile if + // we link the local version of core. + await linkTest(PLUGINS.coreV1, cjsExecutor, true) + }) + + await test('Link core v1 plugin to ESM root plugin', async () => { + // Pass in true to skip linking the local version of @oclif/core + // to plugin-test-core-v1. There are breaking changes to how + // args are defined in a command so the plugin won't compile if + // we link the local version of core. + await linkTest(PLUGINS.coreV1, esmExecutor, true) + }) } + const coreV2Tests = async () => { + await test('Install core v2 plugin to ESM root plugin', async () => { + await installTest(PLUGINS.coreV2, esmExecutor) + }) + + await test('Install core v2 plugin to CJS root plugin', async () => { + await installTest(PLUGINS.coreV2, cjsExecutor) + }) + + await test('Link core v2 plugin to CJS root plugin', async () => { + await linkTest(PLUGINS.coreV2, cjsExecutor) + }) + + await test('Link core v2 plugin to ESM root plugin', async () => { + await linkTest(PLUGINS.coreV2, esmExecutor) + }) + } + + if (runTests.cjs) await cjsBefore() + if (runTests.esm) await esmBefore() + if (runTests.precore) await precoreBefore() + if (runTests.coreV1) await coreV1Before() + if (runTests.coreV2) await coreV2Before() + + if (runTests.cjs) await cjsTests() + if (runTests.esm) await esmTests() + if (runTests.precore) await preCoreTests() + if (runTests.coreV1) await coreV1Tests() + if (runTests.coreV2) await coreV2Tests() + exit() })() diff --git a/test/integration/plugins.e2e.ts b/test/integration/plugins.e2e.ts index b8206320e..56c42940f 100644 --- a/test/integration/plugins.e2e.ts +++ b/test/integration/plugins.e2e.ts @@ -1,5 +1,5 @@ -import * as os from 'os' -import {expect, config as chaiConfig} from 'chai' +import {arch} from 'node:os' +import {config as chaiConfig, expect} from 'chai' import {Executor, Result, setup} from './util' chaiConfig.truncateThreshold = 0 @@ -197,7 +197,7 @@ describe('oclif plugins', () => { it('should install the plugin', async () => { const result = await executor.executeCommand('plugins:install @oclif/plugin-warn-if-update-available 2>&1') expect(result.code).to.equal(0) - expect(result.stdout).to.include('@oclif/plugin-warn-if-update-available... installed v') + expect(result.stdout).to.include('@oclif/plugin-warn-if-update-available@latest... installed v') const pluginsResult = await executor.executeCommand('plugins') expect(pluginsResult.code).to.equal(0) @@ -224,7 +224,7 @@ describe('oclif plugins', () => { it('should install the plugin', async () => { const result = await executor.executeCommand('plugins:install @oclif/plugin-warn-if-update-available --force 2>&1') expect(result.code).to.equal(0) - expect(result.stdout).to.include('@oclif/plugin-warn-if-update-available... installed v') + expect(result.stdout).to.include('@oclif/plugin-warn-if-update-available@latest... installed v') const pluginsResult = await executor.executeCommand('plugins') expect(pluginsResult.code).to.equal(0) @@ -257,7 +257,7 @@ describe('oclif plugins', () => { it('should show version', () => expect(version.stdout).to.include('oclif-hello-world/0.0.0')) it('should show platform', () => expect(version.stdout).to.include(process.platform)) - it('should show arch', () => expect(version.stdout).to.include(os.arch())) + it('should show arch', () => expect(version.stdout).to.include(arch())) it('should show node version', () => expect(version.stdout).to.include(process.version)) }) diff --git a/test/integration/sf.e2e.ts b/test/integration/sf.e2e.ts index 69ea75431..27636093c 100644 --- a/test/integration/sf.e2e.ts +++ b/test/integration/sf.e2e.ts @@ -1,4 +1,4 @@ -import * as os from 'os' +import {arch} from 'node:os' import {expect} from 'chai' import {Executor, setup} from './util' import StripAnsi = require('strip-ansi') @@ -17,12 +17,7 @@ describe('Salesforce CLI (sf)', () => { process.env.SFDX_TELEMETRY_DISABLE_ACKNOWLEDGEMENT = 'true' executor = await setup(__filename, { repo: 'https://github.com/salesforcecli/cli', - // Allowing failed install here because it let's attempt to run the tests - // even if there's a linting error caused by the code changes. This is only - // acceptable since we're crossing major versions at the moment. It should - // be removed as soon as sf uses v3. - allowFailedInstall: true, - compileCmd: 'yarn compile', + branch: 'mdonnalley/esm', }) }) @@ -87,7 +82,7 @@ describe('Salesforce CLI (sf)', () => { const version = await executor.executeCommand('-v') expect(version.stdout).to.include('@salesforce/cli') expect(version.stdout).to.include(process.platform) - expect(version.stdout).to.include(os.arch()) + expect(version.stdout).to.include(arch()) expect(version.stdout).to.include(process.version) }) diff --git a/test/integration/util.ts b/test/integration/util.ts index 98d4e588f..d051f15ef 100644 --- a/test/integration/util.ts +++ b/test/integration/util.ts @@ -1,16 +1,15 @@ -import {rm} from 'shelljs' -import {mkdir} from 'node:fs/promises' -import * as cp from 'child_process' -import * as chalk from 'chalk' -import * as fs from 'fs' -import * as os from 'os' -import * as path from 'path' +import {mkdir, rm} from 'node:fs/promises' +import {ExecException, ExecSyncOptionsWithBufferEncoding, execSync} from 'node:child_process' +import chalk from 'chalk' +import {existsSync, readFileSync, writeFileSync} from 'node:fs' +import {tmpdir} from 'node:os' +import {basename, dirname, join, resolve} from 'node:path' import {Interfaces} from '../../src' const debug = require('debug')('e2e') -export type ExecError = cp.ExecException & { stderr: string; stdout: string }; +export type ExecError = ExecException & { stderr: string; stdout: string }; export type Result = { code: number; @@ -21,10 +20,10 @@ export type Result = { export type SetupOptions = { repo: string; + branch?: string; plugins?: string[]; subDir?: string; - allowFailedInstall?: boolean; - compileCmd?: string; + noLinkCore?: boolean; } export type ExecutorOptions = { @@ -32,15 +31,15 @@ export type ExecutorOptions = { testFileName: string; } -export type ExecOptions = cp.ExecSyncOptionsWithBufferEncoding & {silent?: boolean} +export type ExecOptions = ExecSyncOptionsWithBufferEncoding & {silent?: boolean} function updatePkgJson(testDir: string, obj: Record): Interfaces.PJSON { - const pkgJsonFile = path.join(testDir, 'package.json') - const pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile, 'utf-8')) + const pkgJsonFile = join(testDir, 'package.json') + const pkgJson = JSON.parse(readFileSync(pkgJsonFile, 'utf8')) obj.dependencies = Object.assign(pkgJson.dependencies || {}, obj.dependencies || {}) obj.resolutions = Object.assign(pkgJson.resolutions || {}, obj.resolutions || {}) const updated = Object.assign(pkgJson, obj) - fs.writeFileSync(pkgJsonFile, JSON.stringify(updated, null, 2)) + writeFileSync(pkgJsonFile, JSON.stringify(updated, null, 2)) return updated } @@ -55,15 +54,16 @@ export class Executor { public constructor(options: ExecutorOptions) { this.pluginDir = options.pluginDir this.testFileName = options.testFileName - this.parentDir = path.basename(path.dirname(this.pluginDir)) - this.pluginName = path.basename(this.pluginDir) - + this.parentDir = basename(dirname(this.pluginDir)) + this.pluginName = basename(this.pluginDir) + this.usesJsScript = existsSync(join(this.pluginDir, 'bin', 'run.js')) this.debug = debug.extend(`${this.testFileName}:${this.parentDir}:${this.pluginName}`) } - public clone(repo: string): Promise { - const result = this.exec(`git clone ${repo} ${this.pluginDir} --depth 1`) - this.usesJsScript = fs.existsSync(path.join(this.pluginDir, 'bin', 'run.js')) + public clone(repo: string, branch?: string): Promise { + const cmd = branch ? `git clone --branch ${branch} ${repo} ${this.pluginDir} --depth 1` : `git clone ${repo} ${this.pluginDir} --depth 1` + const result = this.exec(cmd) + this.usesJsScript = existsSync(join(this.pluginDir, 'bin', 'run.js')) return result } @@ -72,9 +72,9 @@ export class Executor { } public executeCommand(cmd: string, script: 'run' | 'dev' = 'run', options: ExecOptions = {}): Promise { - const executable = process.platform === 'win32' ? - path.join('bin', `${script}.cmd`) : - path.join('bin', `${script}${this.usesJsScript ? '.js' : ''}`) + const executable = process.platform === 'win32' + ? join('bin', `${script}.cmd`) + : join('bin', `${script}${this.usesJsScript ? '.js' : ''}`) return this.executeInTestDir(`${executable} ${cmd}`, options) } @@ -85,11 +85,7 @@ export class Executor { this.debug(cmd, chalk.dim(`(cwd: ${cwd})`)) if (silent) { try { - const r = cp.execSync(cmd, { - stdio: 'pipe', - ...options, - cwd, - }) + const r = execSync(cmd, {...options, stdio: 'pipe', cwd}) const stdout = r.toString() this.debug(stdout) resolve({code: 0, stdout}) @@ -105,7 +101,7 @@ export class Executor { }) } } else { - cp.execSync(cmd, {stdio: 'inherit', cwd}) + execSync(cmd, {...options, stdio: 'inherit', cwd}) resolve({code: 0}) } }) @@ -128,12 +124,12 @@ export class Executor { * - OCLIF_CORE_E2E_SKIP_SETUP: skip all the setup steps (useful if iterating on tests) */ export async function setup(testFile: string, options: SetupOptions): Promise { - const testFileName = path.basename(testFile) - const dir = process.env.OCLIF_CORE_E2E_TEST_DIR || os.tmpdir() - const testDir = options.subDir ? path.join(dir, testFileName, options.subDir) : path.join(dir, testFileName) + const testFileName = basename(testFile) + const dir = process.env.OCLIF_CORE_E2E_TEST_DIR || tmpdir() + const testDir = options.subDir ? join(dir, testFileName, options.subDir) : join(dir, testFileName) const name = options.repo.slice(options.repo.lastIndexOf('/') + 1) - const pluginDir = path.join(testDir, name) + const pluginDir = join(testDir, name) const executor = new Executor({pluginDir, testFileName}) executor.debug('plugin directory:', pluginDir) @@ -144,25 +140,25 @@ export async function setup(testFile: string, options: SetupOptions): Promise ({...x, [y]: 'latest'}), {}) pjson = updatePkgJson(pluginDir, { - resolutions: {'@oclif/core': path.resolve('.')}, - dependencies: Object.assign(dependencies, pluginDeps), + ...(options.noLinkCore ? {} : {resolutions: {'@oclif/core': resolve('.')}}), + dependencies: {...dependencies, ...pluginDeps}, oclif: {plugins: options.plugins}, }) } else { pjson = updatePkgJson(pluginDir, { - resolutions: {'@oclif/core': path.resolve('.')}, + ...(options.noLinkCore ? {} : {resolutions: {'@oclif/core': resolve('.')}}), dependencies, }) } @@ -171,10 +167,10 @@ export async function setup(testFile: string, options: SetupOptions): Promise diff --git a/test/interfaces/flags.test-types.ts b/test/interfaces/flags.test-types.ts index cfd0c4e23..141a01dcd 100644 --- a/test/interfaces/flags.test-types.ts +++ b/test/interfaces/flags.test-types.ts @@ -3,8 +3,9 @@ */ import {Command, Flags, Interfaces} from '../../src' -import {expectType, expectNotType} from 'tsd' -import {URL} from 'url' + +import {expectNotType, expectType} from 'tsd' +import {URL} from 'node:url' abstract class BaseCommand extends Command { static enableJsonFlag = true @@ -23,9 +24,9 @@ type MyType = { } export const customFlagWithRequiredProp = Flags.custom({ - parse: async (input, _, opts) => { + async parse(input, _, opts) { const value = opts.unit === 'minutes' ? new Date(input).getMinutes() : new Date(input).getSeconds() - return Promise.resolve(value) + return value }, default: async _ctx => _ctx.options.unit === 'minutes' ? 1 : 2, defaultHelp: async _ctx => _ctx.options.unit === 'minutes' ? '1 minute' : '2 seconds', @@ -37,6 +38,61 @@ export const arrayFlag = Flags.custom({ multiple: true, }) +const options = ['foo', 'bar'] as const + +Flags.option({ + options, + multiple: true, + // @ts-expect-error because multiple is true, default must be an array + default: 'foo', +}) + +Flags.option({options})({ + multiple: true, + // @ts-expect-error because multiple is true, default must be an array + default: 'foo', +}) + +// @ts-expect-error because multiple is false, default must be a single value +Flags.option({ + options, + default: ['foo'], +}) + +// @ts-expect-error because multiple is false, default must be a single value +Flags.option({options, multiple: false})({ + default: ['foo'], +}) + +Flags.custom({ + options, + multiple: true, + // @ts-expect-error because multiple is true, default must be an array + default: 'foo', +}) + +Flags.custom()({ + multiple: true, + // @ts-expect-error because multiple is true, default must be an array + default: 'foo', +}) + +Flags.custom({multiple: true})({ + // @ts-expect-error because multiple is true, default must be an array + default: 'foo', +}) + +// @ts-expect-error because multiple is false, default must be a single value +Flags.custom({ + options, + default: ['foo'], +}) + +// @ts-expect-error because multiple is false, default must be a single value +Flags.custom({multiple: false})({ + default: ['foo'], +}) + class MyCommand extends BaseCommand { static description = 'describe the command here' @@ -45,85 +101,242 @@ class MyCommand extends BaseCommand { ] static flags = { - requiredString: Flags.string({required: true}), - optionalString: Flags.string(), - defaultString: Flags.string({default: 'default'}), + string: Flags.string(), + 'string#opts:required': Flags.string({required: true}), + 'string#opts:default': Flags.string({default: 'default'}), - requiredMultiString: Flags.string({required: true, multiple: true}), - optionalMultiString: Flags.string({multiple: true}), - defaultMultiString: Flags.string({ + 'string#opts:multiple,required': Flags.string({required: true, multiple: true}), + 'string#opts:multiple': Flags.string({multiple: true}), + 'string#opts:multiple,default': Flags.string({ multiple: true, default: ['default'], defaultHelp: async _ctx => 'defaultHelp', }), - requiredBoolean: Flags.boolean({required: true}), - optionalBoolean: Flags.boolean(), - defaultBoolean: Flags.boolean({default: true}), + boolean: Flags.boolean(), + 'boolean#opts:required': Flags.boolean({required: true}), + 'boolean#opts:default': Flags.boolean({default: true}), - optionalInteger: Flags.integer(), - requiredInteger: Flags.integer({required: true}), - defaultInteger: Flags.integer({default: 1}), + integer: Flags.integer(), + 'integer#opts:required': Flags.integer({required: true}), + 'integer#opts:default': Flags.integer({default: 1}), - optionalMultiInteger: Flags.integer({multiple: true}), - requiredMultiInteger: Flags.integer({multiple: true, required: true}), - defaultMultiInteger: Flags.integer({multiple: true, default: [1]}), + 'integer#opts:multiple': Flags.integer({multiple: true}), + 'integer#opts:multiple,required': Flags.integer({multiple: true, required: true}), + 'integer#opts:multiple,default': Flags.integer({multiple: true, default: [1]}), - optionalDirectory: Flags.directory(), - requiredDirectory: Flags.directory({required: true}), - defaultDirectory: Flags.directory({default: 'my-dir'}), + directory: Flags.directory(), + 'directory#opts:required': Flags.directory({required: true}), + 'directory#opts:default': Flags.directory({default: 'my-dir'}), - optionalMultiDirectory: Flags.directory({multiple: true}), - requiredMultiDirectory: Flags.directory({multiple: true, required: true}), - defaultMultiDirectory: Flags.directory({multiple: true, default: ['my-dir']}), + 'directory#opts:multiple': Flags.directory({multiple: true}), + 'directory#opts:multiple,required': Flags.directory({multiple: true, required: true}), + 'directory#opts:multiple,default': Flags.directory({multiple: true, default: ['my-dir']}), - optionalFile: Flags.file(), - requiredFile: Flags.file({required: true}), - defaultFile: Flags.file({default: 'my-file.json'}), + file: Flags.file(), + 'file#opts:required': Flags.file({required: true}), + 'file#opts:default': Flags.file({default: 'my-file.json'}), - optionalMultiFile: Flags.file({multiple: true}), - requiredMultiFile: Flags.file({multiple: true, required: true}), - defaultMultiFile: Flags.file({multiple: true, default: ['my-file.json']}), + 'file#opts:multiple': Flags.file({multiple: true}), + 'file#opts:multiple,required': Flags.file({multiple: true, required: true}), + 'file#opts:multiple,default': Flags.file({multiple: true, default: ['my-file.json']}), - optionalUrl: Flags.url(), - requiredUrl: Flags.url({required: true}), - defaultUrl: Flags.url({ + url: Flags.url(), + 'url#opts:required': Flags.url({required: true}), + 'url#opts:default': Flags.url({ default: new URL('http://example.com'), defaultHelp: async _ctx => 'Example URL', }), - optionalMultiUrl: Flags.url({multiple: true}), - requiredMultiUrl: Flags.url({multiple: true, required: true}), - defaultMultiUrl: Flags.url({multiple: true, default: [new URL('http://example.com')]}), + 'url#opts:multiple': Flags.url({multiple: true}), + 'url#opts:multiple,required': Flags.url({multiple: true, required: true}), + 'url#opts:multiple,default': Flags.url({multiple: true, default: [new URL('http://example.com')]}), - optionalCustom: Flags.custom({ + custom: Flags.custom({ parse: async () => ({foo: true}), })(), - requiredCustom: Flags.custom({ + 'custom#opts:required': Flags.custom({ parse: async () => ({foo: true}), })({required: true}), - defaultCustom: Flags.custom({ + 'custom#opts:default': Flags.custom({ parse: async () => ({foo: true}), + })({ default: async _ctx => ({foo: true}), - })({default: {foo: true}}), + }), - optionalMultiCustom: Flags.custom({ + 'custom#opts:multiple': Flags.custom({ parse: async () => ({foo: true}), })({multiple: true}), - requiredMultiCustom: Flags.custom({ + 'custom#opts:multiple,required': Flags.custom({ parse: async () => ({foo: true}), })({required: true, multiple: true}), - defaultMultiCustom: Flags.custom({ + 'custom#opts:multiple,default': Flags.custom({ parse: async () => ({foo: true}), })({default: [{foo: true}], multiple: true}), - optionalCustomFlagWithRequiredProp: customFlagWithRequiredProp({unit: 'minutes'}), - requiredCustomFlagWithRequiredProp: customFlagWithRequiredProp({unit: 'minutes', required: true}), - defaultCustomFlagWithRequiredProp: customFlagWithRequiredProp({unit: 'minutes', default: 23}), + 'custom#opts:custom-prop': customFlagWithRequiredProp({unit: 'minutes'}), + 'custom#opts:custom-prop,required': customFlagWithRequiredProp({unit: 'minutes', required: true}), + 'custom#opts:custom-prop,default': customFlagWithRequiredProp({unit: 'minutes', default: 23}), + + 'custom#defs:multiple,delimiter': arrayFlag(), + 'custom#defs:multiple,delimiter;opts:required': arrayFlag({required: true}), + 'custom#defs:multiple,delimiter;opts:default': arrayFlag({default: ['foo', 'bar']}), + + option: Flags.option({ + options, + })(), + 'option#opts:required': Flags.option({ + options, + })({required: true}), + 'option#opts:default': Flags.option({ + options, + })({default: 'foo'}), + + 'option#opts:multiple': Flags.option({ + options, + })({multiple: true}), + 'option#opts:multiple,required': Flags.option({ + options, + })({required: true, multiple: true}), + 'option#opts:multiple,default': Flags.option({ + options, + })({default: async _ctx => ['foo'], multiple: true}), + + 'custom#defs:required': Flags.custom({ + required: true, + })(), + 'custom#defs:default': Flags.custom({ + default: 'foo', + })(), + 'custom#defs:multiple': Flags.custom({ + multiple: true, + })(), + 'custom#defs:multiple,required': Flags.custom({ + multiple: true, + required: true, + })(), + 'custom#defs:multiple,default': Flags.custom({ + multiple: true, + default: ['foo'], + })(), + + 'option#defs:required': Flags.option({ + options, + required: true, + })(), + 'option#defs:default': Flags.option({ + options, + default: async _ctx => 'foo', + })(), + 'option#defs:multiple': Flags.option({ + options, + multiple: true, + })(), + 'option#defs:multiple,required': Flags.option({ + options, + multiple: true, + required: true, + })(), + 'option#defs,multiple,default': Flags.option({ + options, + multiple: true, + default: async _ctx => ['foo'], + })(), + + 'option#defs:multiple;opts:default': Flags.option({ + options, + multiple: true, + })({ + default: ['foo'], + }), + + 'option#defs:multiple;opts:default-callback': Flags.option({ + options, + multiple: true, + })({ + default: async _ctx => ['foo'], + }), + + 'custom#defs:multiple;opts:default-callback': Flags.custom({ + options, + multiple: true, + })({ + default: async _ctx => ['foo'], + }), + + 'custom#defs:multiple,parse': Flags.custom({ + multiple: true, + parse: async (input, _ctx, _opts) => input, + })(), + + 'option#defs:multiple,prase': Flags.option({ + options, + multiple: true, + parse: async (input, _ctx, _opts) => input as typeof options[number], + })(), - optionalArrayFlag: arrayFlag(), - requiredArrayFlag: arrayFlag({required: true}), - defaultArrayFlag: arrayFlag({default: ['foo', 'bar']}), + 'custom#defs:multiple=true;opts:multiple=false': Flags.custom({ + multiple: true, + })({ + multiple: false, + }), + 'custom#defs:multiple=false;opts:multiple=true': Flags.custom({ + multiple: false, + })({ + multiple: true, + }), + 'custom#defs:required=true;opts:required=false': Flags.custom({ + required: true, + })({ + required: false, + }), + 'custom#defs:required=false;opts:required=true': Flags.custom({ + required: false, + })({ + required: true, + }), + 'custom#defs:multiple=true;opts:multiple=false,required=true': Flags.custom({ + multiple: true, + })({ + multiple: false, + required: true, + }), + 'custom#defs:required=true;opts:multiple=true,required=false': Flags.custom({ + required: true, + })({ + multiple: true, + required: false, + }), + 'custom#defs:required=false;opts:multiple=true,required=true': Flags.custom({ + required: false, + })({ + multiple: true, + required: true, + }), + + 'custom#defs:multiple=true,required=true;opts:multiple=false,required=false': Flags.custom({ + multiple: true, + required: true, + })({ + multiple: false, + required: false, + }), + + 'custom#defs:multiple=false,required=false;opts:multiple=true,required=true': Flags.custom({ + multiple: false, + required: false, + })({ + multiple: true, + required: true, + }), + + 'custom#defs:multiple=true;opts:multiple=false,default': Flags.custom({ + multiple: true, + })({ + multiple: false, + // TODO: THIS IS A BUG. It should enforce a single value instead of allowing a single value or an array + default: ['foo'], + }), } public static '--' = true @@ -141,92 +354,161 @@ class MyCommand extends BaseCommand { expectNotType(this.flags.defaultGlobalFlag) expectType(this.flags.optionalGlobalFlag) - expectType(this.flags.requiredString) - expectNotType(this.flags.requiredString) - - expectType(this.flags.defaultString) - expectNotType(this.flags.defaultString) - - expectType(this.flags.optionalString) - - expectType(this.flags.requiredMultiString) - expectNotType(this.flags.requiredMultiString) - - expectType(this.flags.optionalMultiString) - expectType(this.flags.defaultMultiString) - expectNotType(this.flags.defaultMultiString) - - expectType(this.flags.requiredBoolean) - expectNotType(this.flags.requiredBoolean) - expectType(this.flags.defaultBoolean) - expectNotType(this.flags.defaultBoolean) - expectType(this.flags.optionalBoolean) - - expectType(this.flags.requiredInteger) - expectNotType(this.flags.requiredInteger) - expectType(this.flags.defaultInteger) - expectNotType(this.flags.defaultInteger) - expectType(this.flags.optionalInteger) - - expectType(this.flags.requiredMultiInteger) - expectNotType(this.flags.requiredMultiInteger) - expectType(this.flags.defaultMultiInteger) - expectNotType(this.flags.defaultMultiInteger) - expectType(this.flags.optionalMultiInteger) - - expectType(this.flags.requiredDirectory) - expectNotType(this.flags.requiredDirectory) - expectType(this.flags.defaultDirectory) - expectNotType(this.flags.defaultDirectory) - expectType(this.flags.optionalDirectory) - - expectType(this.flags.requiredMultiDirectory) - expectNotType(this.flags.requiredMultiDirectory) - expectType(this.flags.defaultMultiDirectory) - expectNotType(this.flags.defaultMultiDirectory) - expectType(this.flags.optionalMultiDirectory) - - expectType(this.flags.requiredFile) - expectNotType(this.flags.requiredFile) - expectType(this.flags.defaultFile) - expectNotType(this.flags.defaultFile) - expectType(this.flags.optionalFile) - - expectType(this.flags.requiredMultiFile) - expectNotType(this.flags.requiredMultiFile) - expectType(this.flags.defaultMultiFile) - expectNotType(this.flags.defaultMultiFile) - expectType(this.flags.optionalMultiFile) - - expectType(this.flags.requiredUrl) - expectNotType(this.flags.requiredUrl) - expectType(this.flags.defaultUrl) - expectNotType(this.flags.defaultUrl) - expectType(this.flags.optionalUrl) - - expectType(this.flags.requiredMultiUrl) - expectNotType(this.flags.requiredMultiUrl) - expectType(this.flags.defaultMultiUrl) - expectNotType(this.flags.defaultMultiUrl) - expectType(this.flags.optionalMultiUrl) - - expectType(this.flags.requiredCustom) - expectNotType(this.flags.requiredCustom) - expectType(this.flags.defaultCustom) - expectNotType(this.flags.defaultCustom) - expectType(this.flags.optionalCustom) - - expectType(this.flags.requiredMultiCustom) - expectNotType(this.flags.requiredMultiCustom) - expectType(this.flags.defaultMultiCustom) - expectNotType(this.flags.defaultMultiCustom) - expectType(this.flags.optionalMultiCustom) - - expectType(this.flags.optionalCustomFlagWithRequiredProp) - expectType(this.flags.requiredCustomFlagWithRequiredProp) - expectNotType(this.flags.requiredCustomFlagWithRequiredProp) - expectType(this.flags.defaultCustomFlagWithRequiredProp) - expectNotType(this.flags.defaultCustomFlagWithRequiredProp) + expectType(this.flags['string#opts:required']) + expectNotType(this.flags['string#opts:required']) + + expectType(this.flags['string#opts:default']) + expectNotType(this.flags['string#opts:default']) + + expectType(this.flags.string) + + expectType(this.flags['string#opts:multiple,required']) + expectNotType(this.flags['string#opts:multiple,required']) + + expectType(this.flags['string#opts:multiple']) + expectType(this.flags['string#opts:multiple,default']) + expectNotType(this.flags['string#opts:multiple,default']) + + expectType(this.flags['boolean#opts:required']) + expectNotType(this.flags['boolean#opts:required']) + expectType(this.flags['boolean#opts:default']) + expectNotType(this.flags['boolean#opts:default']) + expectType(this.flags.boolean) + + expectType(this.flags['integer#opts:required']) + expectNotType(this.flags['integer#opts:required']) + expectType(this.flags['integer#opts:default']) + expectNotType(this.flags['integer#opts:default']) + expectType(this.flags.integer) + + expectType(this.flags['integer#opts:multiple,required']) + expectNotType(this.flags['integer#opts:multiple,required']) + expectType(this.flags['integer#opts:multiple,default']) + expectNotType(this.flags['integer#opts:multiple,default']) + expectType(this.flags['integer#opts:multiple']) + + expectType(this.flags['directory#opts:required']) + expectNotType(this.flags['directory#opts:required']) + expectType(this.flags['directory#opts:default']) + expectNotType(this.flags['directory#opts:default']) + expectType(this.flags.directory) + + expectType(this.flags['directory#opts:multiple,required']) + expectNotType(this.flags['directory#opts:multiple,required']) + expectType(this.flags['directory#opts:multiple,default']) + expectNotType(this.flags['directory#opts:multiple,default']) + expectType(this.flags['directory#opts:multiple']) + + expectType(this.flags['file#opts:required']) + expectNotType(this.flags['file#opts:required']) + expectType(this.flags['file#opts:default']) + expectNotType(this.flags['file#opts:default']) + expectType(this.flags.file) + + expectType(this.flags['file#opts:multiple,required']) + expectNotType(this.flags['file#opts:multiple,required']) + expectType(this.flags['file#opts:multiple,default']) + expectNotType(this.flags['file#opts:multiple,default']) + expectType(this.flags['file#opts:multiple']) + + expectType(this.flags['url#opts:required']) + expectNotType(this.flags['url#opts:required']) + expectType(this.flags['url#opts:default']) + expectNotType(this.flags['url#opts:default']) + expectType(this.flags.url) + + expectType(this.flags['url#opts:multiple,required']) + expectNotType(this.flags['url#opts:multiple,required']) + expectType(this.flags['url#opts:multiple,default']) + expectNotType(this.flags['url#opts:multiple,default']) + expectType(this.flags['url#opts:multiple']) + + expectType(this.flags['custom#opts:required']) + expectNotType(this.flags['custom#opts:required']) + expectType(this.flags['custom#opts:default']) + expectNotType(this.flags['custom#opts:default']) + expectType(this.flags.custom) + + expectType(this.flags['custom#opts:multiple,required']) + expectNotType(this.flags['custom#opts:multiple,required']) + expectType(this.flags['custom#opts:multiple,default']) + expectNotType(this.flags['custom#opts:multiple,default']) + expectType(this.flags['custom#opts:multiple']) + + expectType(this.flags['custom#opts:custom-prop']) + expectType(this.flags['custom#opts:custom-prop,required']) + expectNotType(this.flags['custom#opts:custom-prop,required']) + expectType(this.flags['custom#opts:custom-prop,default']) + expectNotType(this.flags['custom#opts:custom-prop,default']) + + expectType(this.flags['custom#defs:multiple,delimiter;opts:required']) + expectNotType(this.flags['custom#defs:multiple,delimiter;opts:required']) + expectType(this.flags['custom#defs:multiple,delimiter;opts:default']) + expectNotType(this.flags['custom#defs:multiple,delimiter;opts:default']) + expectType(this.flags['custom#defs:multiple,delimiter']) + + expectType(this.flags['option#opts:required']) + expectNotType(this.flags['option#opts:required']) + expectType(this.flags['option#opts:default']) + expectNotType(this.flags['option#opts:default']) + expectType(this.flags.option) + + expectType(this.flags['option#opts:multiple,required']) + expectNotType(this.flags['option#opts:multiple,required']) + expectType(this.flags['option#opts:multiple,default']) + expectNotType(this.flags['option#opts:multiple,default']) + expectType(this.flags['option#opts:multiple']) + + expectType(this.flags['custom#defs:required']) + expectNotType(this.flags['custom#defs:required']) + expectType(this.flags['custom#defs:default']) + expectNotType(this.flags['custom#defs:default']) + expectType(this.flags['custom#defs:multiple']) + expectNotType(this.flags['custom#defs:multiple']) + expectType(this.flags['custom#defs:multiple,required']) + expectNotType(this.flags['custom#defs:multiple,required']) + expectType(this.flags['custom#defs:multiple,default']) + expectNotType(this.flags['custom#defs:multiple,default']) + + expectType(this.flags['option#defs:required']) + expectNotType(this.flags['option#defs:required']) + expectType(this.flags['option#defs:default']) + expectNotType(this.flags['option#defs:default']) + expectType(this.flags['option#defs:multiple']) + expectNotType(this.flags['option#defs:multiple']) + expectType(this.flags['option#defs:multiple,required']) + expectNotType(this.flags['option#defs:multiple,required']) + expectType(this.flags['option#defs,multiple,default']) + expectNotType(this.flags['option#defs,multiple,default']) + + expectType(this.flags['option#defs:multiple;opts:default']) + expectNotType(this.flags['option#defs:multiple;opts:default']) + + expectType(this.flags['option#defs:multiple;opts:default-callback']) + expectNotType(this.flags['option#defs:multiple;opts:default-callback']) + + expectType(this.flags['custom#defs:multiple;opts:default-callback']) + + expectType(this.flags['custom#defs:multiple,parse']) + + expectType<(typeof options[number])[] | undefined>(this.flags['option#defs:multiple,prase']) + + expectType(this.flags['custom#defs:multiple=true;opts:multiple=false']) + expectType(this.flags['custom#defs:multiple=false;opts:multiple=true']) + expectType(this.flags['custom#defs:required=true;opts:required=false']) + expectType(this.flags['custom#defs:required=false;opts:required=true']) + expectNotType(this.flags['custom#defs:required=false;opts:required=true']) + expectType(this.flags['custom#defs:multiple=true;opts:multiple=false,required=true']) + expectNotType(this.flags['custom#defs:multiple=true;opts:multiple=false,required=true']) + expectType(this.flags['custom#defs:required=true;opts:multiple=true,required=false']) + expectType(this.flags['custom#defs:required=false;opts:multiple=true,required=true']) + expectNotType(this.flags['custom#defs:required=false;opts:multiple=true,required=true']) + expectType(this.flags['custom#defs:multiple=true,required=true;opts:multiple=false,required=false']) + expectType(this.flags['custom#defs:multiple=false,required=false;opts:multiple=true,required=true']) + expectNotType(this.flags['custom#defs:multiple=false,required=false;opts:multiple=true,required=true']) + + // TODO: Known issue with `default` not enforcing the correct type whenever multiple is defaulted to true but then overridden to false + // expectType(this.flags['custom#defs:multiple=true;opts:multiple=false,default']) return result.flags } diff --git a/test/module-loader/module-loader.test.ts b/test/module-loader/module-loader.test.ts index f22035049..47135c0d1 100644 --- a/test/module-loader/module-loader.test.ts +++ b/test/module-loader/module-loader.test.ts @@ -1,9 +1,9 @@ -import * as path from 'path' +import {resolve} from 'node:path' import {assert, expect} from 'chai' import {Config} from '../../src' -import ModuleLoader from '../../src/module-loader' +import {isPathModule, load, loadWithData} from '../../src/module-loader' import {ModuleLoadError} from '../../src/errors' // The following data object contains an array of module loading data for errors and successful loading conditions and @@ -15,14 +15,14 @@ const data = { { path: './test/module-loader/fixtures/esm/errors/bad_path.js', type: ModuleLoadError, - message: `[MODULE_NOT_FOUND] import() failed to load ${path.resolve('./test/module-loader/fixtures/esm/errors/bad_path.js')}`, + message: `[MODULE_NOT_FOUND] import() failed to load ${resolve('./test/module-loader/fixtures/esm/errors/bad_path.js')}`, isESM: true, }, // Non-existent path / no extension { path: './test/module-loader/fixtures/esm/errors/bad_path', type: ModuleLoadError, - message: `[MODULE_NOT_FOUND] require failed to load ${path.resolve('./test/module-loader/fixtures/esm/errors/bad_path')}`, + message: `[MODULE_NOT_FOUND] require failed to load ${resolve('./test/module-loader/fixtures/esm/errors/bad_path')}`, isESM: true, }, @@ -30,14 +30,14 @@ const data = { { path: './test/module-loader/fixtures/cjs/errors/bad_path.cjs', type: ModuleLoadError, - message: `[MODULE_NOT_FOUND] require failed to load ${path.resolve('./test/module-loader/fixtures/cjs/errors/bad_path.cjs')}`, + message: `[MODULE_NOT_FOUND] require failed to load ${resolve('./test/module-loader/fixtures/cjs/errors/bad_path.cjs')}`, isESM: false, }, // Non-existent path / no extension { path: './test/module-loader/fixtures/cjs/errors/bad_path', type: ModuleLoadError, - message: `[MODULE_NOT_FOUND] require failed to load ${path.resolve('./test/module-loader/fixtures/cjs/errors/bad_path')}`, + message: `[MODULE_NOT_FOUND] require failed to load ${resolve('./test/module-loader/fixtures/cjs/errors/bad_path')}`, isESM: false, }, @@ -62,21 +62,21 @@ const data = { { path: './test/module-loader/fixtures/esm/success.js', defaultModule: '{"default":"SUCCESS","namedExport":"SUCCESS_NAMED"}', - filePath: `${path.resolve('./test/module-loader/fixtures/esm/success.js')}`, + filePath: `${resolve('./test/module-loader/fixtures/esm/success.js')}`, isESM: true, }, // ESM source file loaded due to mjs file type. { path: './test/module-loader/fixtures/esm/empty-package/success-ext.mjs', defaultModule: '{"default":"SUCCESS_MJS","namedExport":"SUCCESS_NAMED_MJS"}', - filePath: `${path.resolve('./test/module-loader/fixtures/esm/empty-package/success-ext.mjs')}`, + filePath: `${resolve('./test/module-loader/fixtures/esm/empty-package/success-ext.mjs')}`, isESM: true, }, // No extension / ESM source file. Loads package.json in './test/module-loader/fixtures/esm/' for getPackageType check. { path: './test/module-loader/fixtures/esm/success', defaultModule: '{"default":"SUCCESS","namedExport":"SUCCESS_NAMED"}', - filePath: `${path.resolve('./test/module-loader/fixtures/esm/success.js')}`, + filePath: `${resolve('./test/module-loader/fixtures/esm/success.js')}`, isESM: true, isESMOverride: false, // With no extension `ModuleLoader.isPathModule` will return CJS }, @@ -84,7 +84,7 @@ const data = { { path: './test/module-loader/fixtures/esm/empty-package/success-ext', defaultModule: '{"default":"SUCCESS_MJS","namedExport":"SUCCESS_NAMED_MJS"}', - filePath: `${path.resolve('./test/module-loader/fixtures/esm/empty-package/success-ext.mjs')}`, + filePath: `${resolve('./test/module-loader/fixtures/esm/empty-package/success-ext.mjs')}`, isESM: true, isESMOverride: false, // With no extension `ModuleLoader.isPathModule` will return CJS }, @@ -93,28 +93,28 @@ const data = { { path: './test/module-loader/fixtures/cjs/success.js', defaultModule: '["SUCCESS"]', - filePath: `${path.resolve('./test/module-loader/fixtures/cjs/success.js')}`, + filePath: `${resolve('./test/module-loader/fixtures/cjs/success.js')}`, isESM: false, }, // CJS source file loaded due to cjs file type. { path: './test/module-loader/fixtures/cjs/success-ext.cjs', defaultModule: '["SUCCESS_CJS"]', - filePath: `${path.resolve('./test/module-loader/fixtures/cjs/success-ext.cjs')}`, + filePath: `${resolve('./test/module-loader/fixtures/cjs/success-ext.cjs')}`, isESM: false, }, // No extension / CJS source loaded from package.json in './test/module-loader/fixtures/cjs/' which doesn't have "type": "module". { path: './test/module-loader/fixtures/cjs/success', defaultModule: '["SUCCESS"]', - filePath: `${path.resolve('./test/module-loader/fixtures/cjs/success.js')}`, + filePath: `${resolve('./test/module-loader/fixtures/cjs/success.js')}`, isESM: false, }, // No extension / CJS source file loaded due to cjs file type. { path: './test/module-loader/fixtures/cjs/success-ext', defaultModule: '["SUCCESS_CJS"]', - filePath: `${path.resolve('./test/module-loader/fixtures/cjs/success-ext.cjs')}`, + filePath: `${resolve('./test/module-loader/fixtures/cjs/success-ext.cjs')}`, isESM: false, }, @@ -122,7 +122,7 @@ const data = { { path: './test/module-loader/fixtures/esm/index/js', defaultModule: '{"default":"SUCCESS","namedExport":"SUCCESS_NAMED"}', - filePath: `${path.resolve('./test/module-loader/fixtures/esm/index/js/index.js')}`, + filePath: `${resolve('./test/module-loader/fixtures/esm/index/js/index.js')}`, isESM: true, isESMOverride: false, // With no extension `ModuleLoader.isPathModule` will return CJS }, @@ -130,7 +130,7 @@ const data = { { path: './test/module-loader/fixtures/esm/index/mjs', defaultModule: '{"default":"SUCCESS","namedExport":"SUCCESS_NAMED"}', - filePath: `${path.resolve('./test/module-loader/fixtures/esm/index/mjs/index.mjs')}`, + filePath: `${resolve('./test/module-loader/fixtures/esm/index/mjs/index.mjs')}`, isESM: true, isESMOverride: false, // With no extension `ModuleLoader.isPathModule` will return CJS }, @@ -151,7 +151,7 @@ describe('ModuleLoader:', () => { const config = new Config({root: process.cwd()}) await config.load() - const result = await ModuleLoader.load(config, module.path) + const result = await load(config, module.path) // Test that the default module as a string. if (module.defaultModule) { @@ -167,7 +167,7 @@ describe('ModuleLoader:', () => { const config = new Config({root: process.cwd()}) await config.load() - const result = await ModuleLoader.loadWithData(config, module.path) + const result = await loadWithData(config, module.path) // Test the exported module as a string. if (module.defaultModule) { @@ -188,7 +188,7 @@ describe('ModuleLoader:', () => { describe('isPathModule:', () => { for (const module of data.modules) { it(`${module.path}`, () => { - const result = ModuleLoader.isPathModule(module.path) + const result = isPathModule(module.path) // For extensionless ESM data `isPathModule` will return false const test = typeof module.isESMOverride === 'boolean' ? module.isESMOverride : module.isESM @@ -208,7 +208,7 @@ describe('ModuleLoader Failures:', () => { const config = new Config({root: process.cwd()}) await config.load() - await expect(ModuleLoader.load( + await expect(load( config, error.path)).to.eventually.be.rejectedWith(error.message).and.be.an.instanceOf(error.type) }) } @@ -220,7 +220,7 @@ describe('ModuleLoader Failures:', () => { const config = new Config({root: process.cwd()}) await config.load() - await expect(ModuleLoader.loadWithData( + await expect(loadWithData( config, error.path)).to.eventually.be.rejectedWith(error.message).and.be.an.instanceOf(error.type) }) } diff --git a/test/parser/parse.test.ts b/test/parser/parse.test.ts index fb28808c1..642a46f91 100644 --- a/test/parser/parse.test.ts +++ b/test/parser/parse.test.ts @@ -1,11 +1,11 @@ -import {assert, expect, config} from 'chai' -import * as fs from 'fs' +import {assert, config, expect} from 'chai' +import * as fs from 'node:fs' import {parse} from '../../src/parser' import {Args, Flags} from '../../src' import {FlagDefault} from '../../src/interfaces/parser' -import {URL} from 'url' -import * as sinon from 'sinon' +import {URL} from 'node:url' +import {createSandbox, SinonStub} from 'sinon' import {CLIError} from '../../src/errors' config.truncateThreshold = 0 @@ -109,7 +109,7 @@ describe('parse', () => { const out = await parse(['--foo', 'baz'], { flags: { foo: Flags.custom({ - defaultHelp: async () => { + async defaultHelp() { throw new Error('failed to get default help value') }, })(), @@ -831,7 +831,7 @@ See more help with --help`) describe('parse with a default/value of another type (class)', async () => { class TestClass { - public prop: string; + public prop: string constructor(input: string) { this.prop = input } @@ -1310,11 +1310,17 @@ See more help with --help`) }) }) - it('parses multiple flags', async () => { - const out = await parse(['--foo=a', '--foo', 'b'], { - flags: {foo: Flags.string()}, - }) - expect(out.flags.foo).to.equal('b') + it('throws an error when multiple flags of non-multiple flag is provided', async () => { + let message = '' + try { + await parse(['--foo=a', '--foo', 'b'], { + flags: {foo: Flags.string()}, + }) + } catch (error: any) { + message = error.message + } + + expect(message).to.include('can only be specified once') }) describe('dependsOn', () => { @@ -1555,12 +1561,12 @@ See more help with --help`) }) describe('fs flags', () => { - const sandbox = sinon.createSandbox() - let existsStub: sinon.SinonStub - let statStub: sinon.SinonStub + const sandbox = createSandbox() + let accessStub: SinonStub + let statStub: SinonStub beforeEach(() => { - existsStub = sandbox.stub(fs, 'existsSync') + accessStub = sandbox.stub(fs.promises, 'access') statStub = sandbox.stub(fs.promises, 'stat') }) @@ -1574,18 +1580,18 @@ See more help with --help`) const out = await parse([`--dir=${testDir}`], { flags: {dir: Flags.directory({exists: false})}, }) - expect(existsStub.callCount).to.equal(0) + expect(accessStub.callCount).to.equal(0) expect(out.flags).to.deep.include({dir: testDir}) }) it('passes if dir !exists but exists not defined', async () => { const out = await parse([`--dir=${testDir}`], { flags: {dir: Flags.directory()}, }) - expect(existsStub.callCount).to.equal(0) + expect(accessStub.callCount).to.equal(0) expect(out.flags).to.deep.include({dir: testDir}) }) it('passes when dir exists', async () => { - existsStub.returns(true) + accessStub.resolves() statStub.returns({isDirectory: () => true}) const out = await parse([`--dir=${testDir}`], { flags: {dir: Flags.directory({exists: true})}, @@ -1593,7 +1599,7 @@ See more help with --help`) expect(out.flags).to.deep.include({dir: testDir}) }) it("fails when dir doesn't exist", async () => { - existsStub.returns(false) + accessStub.throws() try { const out = await parse([`--dir=${testDir}`], { flags: {dir: Flags.directory({exists: true})}, @@ -1607,7 +1613,7 @@ See more help with --help`) } }) it('fails when dir exists but is not a dir', async () => { - existsStub.returns(true) + accessStub.resolves() statStub.returns({isDirectory: () => false}) try { const out = await parse([`--dir=${testDir}`], { @@ -1623,7 +1629,7 @@ See more help with --help`) describe('custom parse functions', () => { const customParseException = 'NOT_OK' it('accepts custom parse that passes', async () => { - existsStub.returns(true) + accessStub.resolves() statStub.returns({isDirectory: () => true}) const out = await parse([`--dir=${testDir}`], { flags: {dir: Flags.directory({exists: true, parse: async input => input.includes('some') ? input : assert.fail(customParseException)})}, @@ -1632,7 +1638,7 @@ See more help with --help`) }) it('accepts custom parse that fails', async () => { - existsStub.returns(true) + accessStub.resolves() statStub.returns({isDirectory: () => true}) try { const out = await parse([`--dir=${testDir}`], { @@ -1655,17 +1661,17 @@ See more help with --help`) flags: {file: Flags.file({exists: false})}, }) expect(out.flags).to.deep.include({file: testFile}) - expect(existsStub.callCount).to.equal(0) + expect(accessStub.callCount).to.equal(0) }) it('passes if file doesn\'t exist but not exists not defined', async () => { const out = await parse([`--file=${testFile}`], { flags: {file: Flags.file()}, }) expect(out.flags).to.deep.include({file: testFile}) - expect(existsStub.callCount).to.equal(0) + expect(accessStub.callCount).to.equal(0) }) it('passes when file exists', async () => { - existsStub.returns(true) + accessStub.resolves() statStub.returns({isFile: () => true}) const out = await parse([`--file=${testFile}`], { flags: {file: Flags.file({exists: true})}, @@ -1673,7 +1679,7 @@ See more help with --help`) expect(out.flags).to.deep.include({file: testFile}) }) it("fails when dir doesn't exist", async () => { - existsStub.returns(false) + accessStub.throws() try { const out = await parse([`--file=${testFile}`], { flags: {file: Flags.file({exists: true})}, @@ -1685,7 +1691,7 @@ See more help with --help`) } }) it('fails when file exists but is not a file', async () => { - existsStub.returns(true) + accessStub.resolves() statStub.returns({isFile: () => false}) try { const out = await parse([`--file=${testFile}`], { @@ -1700,7 +1706,7 @@ See more help with --help`) describe('custom parse functions', () => { const customParseException = 'NOT_OK' it('accepts custom parse that passes', async () => { - existsStub.returns(true) + accessStub.resolves() statStub.returns({isFile: () => true}) const out = await parse([`--dir=${testFile}`], { flags: {dir: Flags.file({exists: false, parse: async input => input.includes('some') ? input : assert.fail(customParseException)})}, @@ -1709,7 +1715,7 @@ See more help with --help`) }) it('accepts custom parse that fails', async () => { - existsStub.returns(true) + accessStub.resolves() statStub.returns({isFile: () => true}) try { const out = await parse([`--dir=${testFile}`], { @@ -1748,5 +1754,71 @@ See more help with --help`) }) expect(out.flags.foo).to.equal(true) }) + + describe('aliased short char', () => { + it('boolean', async () => { + const out = await parse(['-b'], { + flags: { + foo: Flags.boolean({ + charAliases: ['b'], + }), + }, + }) + expect(out.flags.foo).to.equal(true) + }) + it('string', async () => { + const out = await parse(['-b', 'hello'], { + flags: { + foo: Flags.string({ + charAliases: ['b'], + }), + }, + }) + expect(out.flags.foo).to.equal('hello') + }) + it('empty charAliases', async () => { + const out = await parse(['--foo', 'hello'], { + flags: { + foo: Flags.string({ + charAliases: [], + }), + }, + }) + expect(out.flags.foo).to.equal('hello') + }) + it('duplicated flag via charAliases and full name throws error', async () => { + let message = '' + try { + await parse(['--foo', 'hello', '--foo', 'hi'], { + flags: { + foo: Flags.string({ + charAliases: ['b'], + }), + }, + }) + } catch (error: any) { + message = error.message + } + + expect(message).to.include('can only be specified once') + }) + it('duplicated via aliases charAliases throws error', async () => { + let message = '' + try { + await parse(['-b', 'hello', '-b', 'hi'], { + flags: { + foo: Flags.string({ + aliases: ['b'], + charAliases: ['b'], + }), + }, + }) + } catch (error: any) { + message = error.message + } + + expect(message).to.include('can only be specified once') + }) + }) }) }) diff --git a/test/parser/validate.test.ts b/test/parser/validate.test.ts index c637dc311..c5c1513c9 100644 --- a/test/parser/validate.test.ts +++ b/test/parser/validate.test.ts @@ -1,4 +1,4 @@ -import * as assert from 'assert' +import {fail} from 'node:assert' import {expect} from 'chai' import {CLIError} from '../../src/errors' @@ -53,7 +53,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('--dessert=cheesecake cannot also be provided when using --dinner') @@ -145,7 +145,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('Missing required flag') @@ -207,7 +207,7 @@ describe('validate', () => { type: 'all', flags: [ 'cookies', - {name: 'sprinkles', when: async () => Promise.resolve(false)}, + {name: 'sprinkles', when: async () => false}, ], }, ], @@ -268,7 +268,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('All of the following must be provided when using --dessert: --cookies, --sprinkles') @@ -292,7 +292,7 @@ describe('validate', () => { 'cookies', { name: 'sprinkles', - when: async (flags: {birthday: boolean}) => Promise.resolve(flags.birthday), + when: async (flags: {birthday: boolean}) => flags.birthday, }, ], }, @@ -316,7 +316,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('All of the following must be provided when using --dessert: --cookies, --sprinkles') @@ -340,7 +340,7 @@ describe('validate', () => { 'cookies', { name: 'sprinkles', - when: async (flags: {birthday: boolean}) => Promise.resolve(flags.birthday), + when: async (flags: {birthday: boolean}) => flags.birthday, }, ], }, @@ -364,7 +364,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('All of the following must be provided when using --dessert: --cookies') @@ -445,7 +445,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('One of the following must be provided when using --dessert: --cookies, --sprinkles') @@ -469,7 +469,7 @@ describe('validate', () => { 'cookies', { name: 'sprinkles', - when: async (flags: {birthday: boolean}) => Promise.resolve(flags.birthday), + when: async (flags: {birthday: boolean}) => flags.birthday, }, ], }, @@ -493,7 +493,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('One of the following must be provided when using --dessert: --cookies, --sprinkles') @@ -517,7 +517,7 @@ describe('validate', () => { 'cookies', { name: 'sprinkles', - when: async (flags: {birthday: boolean}) => Promise.resolve(flags.birthday), + when: async (flags: {birthday: boolean}) => flags.birthday, }, ], }, @@ -541,7 +541,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('One of the following must be provided when using --dessert: --cookies') @@ -629,7 +629,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('--sprinkles=true cannot also be provided when using --dessert') @@ -652,7 +652,7 @@ describe('validate', () => { flags: [ { name: 'sprinkles', - when: async (flags: {birthday: boolean}) => Promise.resolve(flags.birthday), + when: async (flags: {birthday: boolean}) => flags.birthday, }, ], }, @@ -686,7 +686,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('--sprinkles=true cannot also be provided when using --dessert') @@ -709,7 +709,7 @@ describe('validate', () => { flags: [ { name: 'sprinkles', - when: async (flags: {birthday: boolean}) => Promise.resolve(flags.birthday), + when: async (flags: {birthday: boolean}) => flags.birthday, }, ], }, @@ -759,7 +759,7 @@ describe('validate', () => { type: 'all', flags: [ 'sprinkles', - {name: 'cookies', when: async () => Promise.resolve(true)}, + {name: 'cookies', when: async () => true}, ], }, ], @@ -796,7 +796,7 @@ describe('validate', () => { dessert: { input: [], name: 'dessert', - exclusive: [{name: 'cookies', when: async () => Promise.resolve(true)}], + exclusive: [{name: 'cookies', when: async () => true}], }, }, args: [], @@ -820,7 +820,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('--cookies=false cannot also be provided when using --dessert') @@ -848,24 +848,24 @@ describe('validate', () => { type: 'all', flags: [ 'cookies', - {name: 'sprinkles', when: async () => Promise.resolve(false)}, - {name: 'cake', when: async () => Promise.resolve(true)}, + {name: 'sprinkles', when: async () => false}, + {name: 'cake', when: async () => true}, ], }, { type: 'some', flags: [ 'brownies', - {name: 'pie', when: async () => Promise.resolve(false)}, - {name: 'fudge', when: async () => Promise.resolve(true)}, + {name: 'pie', when: async () => false}, + {name: 'fudge', when: async () => true}, ], }, { type: 'none', flags: [ 'cupcake', - {name: 'muffin', when: async () => Promise.resolve(false)}, - {name: 'scone', when: async () => Promise.resolve(true)}, + {name: 'muffin', when: async () => false}, + {name: 'scone', when: async () => true}, ], }, ], @@ -926,7 +926,7 @@ describe('validate', () => { try { // @ts-expect-error await validate({input, output}) - assert.fail('should have thrown') + fail('should have thrown') } catch (error) { const err = error as CLIError expect(err.message).to.include('All of the following must be provided when using --dessert: --cookies, --cake') diff --git a/test/perf/parser.perf.ts b/test/perf/parser.perf.ts index da529f3c1..55e0ec213 100644 --- a/test/perf/parser.perf.ts +++ b/test/perf/parser.perf.ts @@ -17,7 +17,7 @@ suite .add('simple', { defer: true, - fn: function (deferred: { resolve: () => any }) { + fn(deferred: { resolve: () => any }) { parse(['--bool'], { flags: { bool: Flags.boolean(), @@ -27,17 +27,17 @@ suite }) .add('multiple async flags that take time', { defer: true, - fn: function (deferred: { resolve: () => any }) { + fn(deferred: { resolve: () => any }) { parse(['--flagA', 'foo', '--flagA', 'bar'], { flags: { flagA: Flags.string({ - parse: async input => { + async parse(input) { await delay100() return input }, }), flagB: Flags.string({ - parse: async input => { + async parse(input) { await delay100() return input }, @@ -49,7 +49,7 @@ suite .add('flagstravaganza', { defer: true, - fn: function (deferred: { resolve: () => any }) { + fn(deferred: { resolve: () => any }) { const flags = [ ['--bool'], ['-S', 'foo'], diff --git a/test/util.test.ts b/test/util.test.ts index 42a42e0f8..4820f23a6 100644 --- a/test/util.test.ts +++ b/test/util.test.ts @@ -1,5 +1,5 @@ import {expect} from 'chai' -import {maxBy, sumBy, capitalize, ensureArgObject, last} from '../src/util' +import {capitalize, ensureArgObject, last, maxBy, sumBy} from '../src/util' describe('capitalize', () => { it('capitalizes the string', () => { diff --git a/tsconfig.json b/tsconfig.json index cd1e62b7d..0aed879ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,14 +3,14 @@ "declaration": true, "forceConsistentCasingInFileNames": true, "importHelpers": true, - "module": "commonjs", + "module": "Node16", "outDir": "./lib", "pretty": true, "rootDirs": [ "./src" ], "strict": true, - "target": "es2020", + "target": "ES2021", "allowSyntheticDefaultImports": true, "noErrorTruncation": true, "moduleResolution": "Node16" diff --git a/yarn.lock b/yarn.lock index 5da8d009e..16a9fccd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,176 +2,29 @@ # yarn lockfile v1 -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.14.5": +"@babel/code-frame@^7.0.0": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== dependencies: "@babel/highlight" "^7.14.5" -"@babel/compat-data@^7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" - integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== - -"@babel/core@^7.12.16": - version "7.15.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.5.tgz#f8ed9ace730722544609f90c9bb49162dc3bf5b9" - integrity sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.15.4" - "@babel/helper-compilation-targets" "^7.15.4" - "@babel/helper-module-transforms" "^7.15.4" - "@babel/helpers" "^7.15.4" - "@babel/parser" "^7.15.5" - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.1.2" - semver "^6.3.0" - source-map "^0.5.0" - -"@babel/eslint-parser@^7.12.16": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.15.7.tgz#2dc3d0ff0ea22bb1e08d93b4eeb1149bf1c75f2d" - integrity sha512-yJkHyomClm6A2Xzb8pdAo4HzYMSXFn1O5zrCYvbFP0yQFvHueLedV8WiEno8yJOKStjUXzBZzJFeWQ7b3YMsqQ== - dependencies: - eslint-scope "^5.1.1" - eslint-visitor-keys "^2.1.0" - semver "^6.3.0" - -"@babel/generator@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.4.tgz#85acb159a267ca6324f9793986991ee2022a05b0" - integrity sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw== - dependencies: - "@babel/types" "^7.15.4" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/helper-compilation-targets@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz#cf6d94f30fbefc139123e27dd6b02f65aeedb7b9" - integrity sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ== - dependencies: - "@babel/compat-data" "^7.15.0" - "@babel/helper-validator-option" "^7.14.5" - browserslist "^4.16.6" - semver "^6.3.0" - -"@babel/helper-function-name@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz#845744dafc4381a4a5fb6afa6c3d36f98a787ebc" - integrity sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw== - dependencies: - "@babel/helper-get-function-arity" "^7.15.4" - "@babel/template" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/helper-get-function-arity@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz#098818934a137fce78b536a3e015864be1e2879b" - integrity sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-hoist-variables@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz#09993a3259c0e918f99d104261dfdfc033f178df" - integrity sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-member-expression-to-functions@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz#bfd34dc9bba9824a4658b0317ec2fd571a51e6ef" - integrity sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-module-imports@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz#e18007d230632dea19b47853b984476e7b4e103f" - integrity sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-module-transforms@^7.15.4": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.7.tgz#7da80c8cbc1f02655d83f8b79d25866afe50d226" - integrity sha512-ZNqjjQG/AuFfekFTY+7nY4RgBSklgTu970c7Rj3m/JOhIu5KPBUuTA9AY6zaKcUvk4g6EbDXdBnhi35FAssdSw== - dependencies: - "@babel/helper-module-imports" "^7.15.4" - "@babel/helper-replace-supers" "^7.15.4" - "@babel/helper-simple-access" "^7.15.4" - "@babel/helper-split-export-declaration" "^7.15.4" - "@babel/helper-validator-identifier" "^7.15.7" - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.6" - -"@babel/helper-optimise-call-expression@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz#f310a5121a3b9cc52d9ab19122bd729822dee171" - integrity sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-replace-supers@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz#52a8ab26ba918c7f6dee28628b07071ac7b7347a" - integrity sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.15.4" - "@babel/helper-optimise-call-expression" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/helper-simple-access@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz#ac368905abf1de8e9781434b635d8f8674bcc13b" - integrity sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-split-export-declaration@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz#aecab92dcdbef6a10aa3b62ab204b085f776e257" - integrity sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7": +"@babel/helper-validator-identifier@^7.14.5": version "7.15.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== -"@babel/helper-validator-option@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" - integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== +"@babel/helper-validator-identifier@^7.22.5": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/helpers@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.15.4.tgz#5f40f02050a3027121a3cf48d497c05c555eaf43" - integrity sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ== - dependencies: - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5": +"@babel/highlight@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== @@ -180,43 +33,6 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.15.4", "@babel/parser@^7.15.5": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.7.tgz#0c3ed4a2eb07b165dfa85b3cc45c727334c4edae" - integrity sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g== - -"@babel/template@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194" - integrity sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/parser" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/traverse@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.4.tgz#ff8510367a144bfbff552d9e18e28f3e2889c22d" - integrity sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.15.4" - "@babel/helper-function-name" "^7.15.4" - "@babel/helper-hoist-variables" "^7.15.4" - "@babel/helper-split-export-declaration" "^7.15.4" - "@babel/parser" "^7.15.4" - "@babel/types" "^7.15.4" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.15.4", "@babel/types@^7.15.6": - version "7.15.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.6.tgz#99abdc48218b2881c058dd0a7ab05b99c9be758f" - integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig== - dependencies: - "@babel/helper-validator-identifier" "^7.14.9" - to-fast-properties "^2.0.0" - "@colors/colors@1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" @@ -366,34 +182,56 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@eslint/eslintrc@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" - integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== +"@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" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.1.tgz#8c4bb756cc2aa7eaf13cfa5e69c83afb3260c20c" + integrity sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ== + +"@eslint/eslintrc@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== dependencies: ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^13.9.0" - ignore "^4.0.6" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" + js-yaml "^4.1.0" + minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" - integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== +"@eslint/js@8.49.0": + version "8.49.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333" + integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w== + +"@humanwhocodes/config-array@^0.11.11": + version "0.11.11" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" + integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== dependencies: - "@humanwhocodes/object-schema" "^1.2.0" + "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" - minimatch "^3.0.4" + minimatch "^3.0.5" -"@humanwhocodes/object-schema@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" - integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -412,6 +250,13 @@ resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@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" @@ -443,7 +288,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -747,6 +592,11 @@ "@sigstore/protobuf-specs" "^0.2.0" tuf-js "^1.1.7" +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.3": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -754,6 +604,27 @@ dependencies: type-detect "4.0.8" +"@sinonjs/commons@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" + 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/fake-timers@^7.0.4", "@sinonjs/fake-timers@^7.1.0", "@sinonjs/fake-timers@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" @@ -770,6 +641,15 @@ lodash.get "^4.4.2" type-detect "^4.0.8" +"@sinonjs/samsam@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60" + 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.1" resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" @@ -800,10 +680,10 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== -"@tsd/typescript@~4.9.3": - version "4.9.3" - resolved "https://registry.yarnpkg.com/@tsd/typescript/-/typescript-4.9.3.tgz#87beef743237707b02b88b10056fcf9f58840954" - integrity sha512-iyi45620s9QN62rW90KelRJLG7p15g4NhTezxYCHmE5RY/BI270Sn7E8vWtiLzB+9mQgy71sYXcgyDKjtJamSQ== +"@tsd/typescript@~5.2.2": + version "5.2.2" + resolved "https://registry.yarnpkg.com/@tsd/typescript/-/typescript-5.2.2.tgz#b3a11006737a41abb492fe5a16de17a1c3a0126e" + integrity sha512-VtjHPAKJqLJoHHKBDNofzvQB2+ZVxjXU/Gw6INAS9aINLQYVsxfzrQ2s84huCeYWZRTtrr7R0J7XgpZHjNwBCw== "@tufjs/canonical-json@1.0.0": version "1.0.0" @@ -879,22 +759,6 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== -"@types/glob@^8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-8.1.0.tgz#b63e70155391b0584dce44e7ea25190bbc38f2fc" - integrity sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w== - dependencies: - "@types/minimatch" "^5.1.2" - "@types/node" "*" - -"@types/glob@~7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" - integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - "@types/indent-string@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/indent-string/-/indent-string-4.0.1.tgz#fb4e6b8cdd8e94f70c105e78fb5d357a767b7193" @@ -907,26 +771,26 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.7.tgz#330c5d97a3500e9c903210d6e49f02964af04a0e" integrity sha512-S6+8JAYTE1qdsc9HMVsfY7+SgSuUU/Tp6TYTmITW0PZxiyIMvol3Gy//y69Wkhs0ti4py5qgR3uZH6uz/DNzJQ== -"@types/json-schema@*", "@types/json-schema@^7.0.7": +"@types/json-schema@*": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-schema@^7.0.12": + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + "@types/lodash@*": version "4.14.182" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== -"@types/minimatch@*": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" - integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== - -"@types/minimatch@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" - integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== - "@types/minimist@^1.2.0": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" @@ -951,11 +815,16 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@^16": +"@types/node@*": version "16.18.31" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.31.tgz#7de39c2b9363f0d95b129cc969fcbf98e870251c" integrity sha512-KPXltf4z4g517OlVJO9XQ2357CYw7fvuJ3ZuBynjXC5Jos9i+K7LvFb7bUIwtJXSZj0vTp9Q6NJBSQpkwwO8Zw== +"@types/node@^18": + version "18.17.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.17.tgz#53cc07ce582c9d7c5850702a3c2cb0af0d7b0ca1" + integrity sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -966,18 +835,10 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/proxyquire@^1.3.28": - version "1.3.28" - resolved "https://registry.yarnpkg.com/@types/proxyquire/-/proxyquire-1.3.28.tgz#05a647bb0d8fe48fc8edcc193e43cc79310faa7d" - integrity sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q== - -"@types/shelljs@^0.8.12": - version "0.8.12" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.12.tgz#79dc9632af7d5ca1b5afb65a6bfc1422d79b5fa0" - integrity sha512-ZA8U81/gldY+rR5zl/7HSHrG2KDfEb3lzG6uCUDhW1DTQE9yC/VBQ45fXnXq8f3CgInfhZmjtdu/WOUlrXRQUg== - dependencies: - "@types/glob" "~7.2.0" - "@types/node" "*" +"@types/semver@^7.5.0": + version "7.5.2" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564" + integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw== "@types/sinon@*": version "10.0.2" @@ -1013,74 +874,90 @@ resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== -"@typescript-eslint/eslint-plugin@^4.31.2": - version "4.31.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.2.tgz#9f41efaee32cdab7ace94b15bd19b756dd099b0a" - integrity sha512-w63SCQ4bIwWN/+3FxzpnWrDjQRXVEGiTt9tJTRptRXeFvdZc/wLiz3FQUwNQ2CVoRGI6KUWMNUj/pk63noUfcA== +"@typescript-eslint/eslint-plugin@^6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.2.tgz#f18cc75c9cceac8080a9dc2e7d166008c5207b9f" + integrity sha512-ooaHxlmSgZTM6CHYAFRlifqh1OAr3PAQEwi7lhYhaegbnXrnh7CDcHmc3+ihhbQC7H0i4JF0psI5ehzkF6Yl6Q== dependencies: - "@typescript-eslint/experimental-utils" "4.31.2" - "@typescript-eslint/scope-manager" "4.31.2" - debug "^4.3.1" - functional-red-black-tree "^1.0.1" - regexpp "^3.1.0" - semver "^7.3.5" - tsutils "^3.21.0" - -"@typescript-eslint/experimental-utils@4.31.2": - version "4.31.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.2.tgz#98727a9c1e977dd5d20c8705e69cd3c2a86553fa" - integrity sha512-3tm2T4nyA970yQ6R3JZV9l0yilE2FedYg8dcXrTar34zC9r6JB7WyBQbpIVongKPlhEMjhQ01qkwrzWy38Bk1Q== - dependencies: - "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.31.2" - "@typescript-eslint/types" "4.31.2" - "@typescript-eslint/typescript-estree" "4.31.2" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.7.2" + "@typescript-eslint/type-utils" "6.7.2" + "@typescript-eslint/utils" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^4.31.2": - version "4.31.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.31.2.tgz#54aa75986e3302d91eff2bbbaa6ecfa8084e9c34" - integrity sha512-EcdO0E7M/sv23S/rLvenHkb58l3XhuSZzKf6DBvLgHqOYdL6YFMYVtreGFWirxaU2mS1GYDby3Lyxco7X5+Vjw== - dependencies: - "@typescript-eslint/scope-manager" "4.31.2" - "@typescript-eslint/types" "4.31.2" - "@typescript-eslint/typescript-estree" "4.31.2" - debug "^4.3.1" - -"@typescript-eslint/scope-manager@4.31.2": - version "4.31.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.31.2.tgz#1d528cb3ed3bcd88019c20a57c18b897b073923a" - integrity sha512-2JGwudpFoR/3Czq6mPpE8zBPYdHWFGL6lUNIGolbKQeSNv4EAiHaR5GVDQaLA0FwgcdcMtRk+SBJbFGL7+La5w== - dependencies: - "@typescript-eslint/types" "4.31.2" - "@typescript-eslint/visitor-keys" "4.31.2" - -"@typescript-eslint/types@4.31.2": - version "4.31.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.2.tgz#2aea7177d6d744521a168ed4668eddbd912dfadf" - integrity sha512-kWiTTBCTKEdBGrZKwFvOlGNcAsKGJSBc8xLvSjSppFO88AqGxGNYtF36EuEYG6XZ9vT0xX8RNiHbQUKglbSi1w== - -"@typescript-eslint/typescript-estree@4.31.2": - version "4.31.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.2.tgz#abfd50594d8056b37e7428df3b2d185ef2d0060c" - integrity sha512-ieBq8U9at6PvaC7/Z6oe8D3czeW5d//Fo1xkF/s9394VR0bg/UaMYPdARiWyKX+lLEjY3w/FNZJxitMsiWv+wA== - dependencies: - "@typescript-eslint/types" "4.31.2" - "@typescript-eslint/visitor-keys" "4.31.2" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" - semver "^7.3.5" - tsutils "^3.21.0" +"@typescript-eslint/parser@^6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.2.tgz#e0ae93771441b9518e67d0660c79e3a105497af4" + integrity sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw== + dependencies: + "@typescript-eslint/scope-manager" "6.7.2" + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/typescript-estree" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + debug "^4.3.4" -"@typescript-eslint/visitor-keys@4.31.2": - version "4.31.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.2.tgz#7d5b4a4705db7fe59ecffb273c1d082760f635cc" - integrity sha512-PrBId7EQq2Nibns7dd/ch6S6/M4/iwLM9McbgeEbCXfxdwRUNxJ4UNreJ6Gh3fI2GNKNrWnQxKL7oCPmngKBug== +"@typescript-eslint/scope-manager@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.2.tgz#cf59a2095d2f894770c94be489648ad1c78dc689" + integrity sha512-bgi6plgyZjEqapr7u2mhxGR6E8WCzKNUFWNh6fkpVe9+yzRZeYtDTbsIBzKbcxI+r1qVWt6VIoMSNZ4r2A+6Yw== dependencies: - "@typescript-eslint/types" "4.31.2" - eslint-visitor-keys "^2.0.0" + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + +"@typescript-eslint/type-utils@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.2.tgz#ed921c9db87d72fa2939fee242d700561454f367" + integrity sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ== + dependencies: + "@typescript-eslint/typescript-estree" "6.7.2" + "@typescript-eslint/utils" "6.7.2" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.2.tgz#75a615a6dbeca09cafd102fe7f465da1d8a3c066" + integrity sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg== + +"@typescript-eslint/typescript-estree@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.2.tgz#ce5883c23b581a5caf878af641e49dd0349238c7" + integrity sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ== + dependencies: + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.2.tgz#b9ef0da6f04932167a9222cb4ac59cb187165ebf" + integrity sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.7.2" + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/typescript-estree" "6.7.2" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.2.tgz#4cb2bd786f1f459731b0ad1584c9f73e1c7a4d5c" + integrity sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ== + dependencies: + "@typescript-eslint/types" "6.7.2" + eslint-visitor-keys "^3.4.1" JSONStream@^1.0.4: version "1.3.5" @@ -1107,7 +984,7 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -1117,16 +994,16 @@ acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^7.4.0: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - acorn@^8.4.1: version "8.8.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== +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== + agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1149,7 +1026,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.10.0, ajv@^6.12.4: +ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1159,17 +1036,7 @@ ajv@^6.10.0, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.1: - version "8.6.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" - integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-colors@4.1.1, ansi-colors@^4.1.1: +ansi-colors@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== @@ -1215,6 +1082,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.3.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + ansi-styles@^6.1.0: version "6.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" @@ -1276,16 +1148,79 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + array-ify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= +array-includes@^3.1.6: + version "3.1.7" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" + integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-string "^1.0.7" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array.prototype.findlastindex@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" + integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" + +array.prototype.flat@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -1311,6 +1246,11 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -1371,17 +1311,6 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.16.6: - version "4.17.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9" - integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ== - dependencies: - caniuse-lite "^1.0.30001259" - electron-to-chromium "^1.3.846" - escalade "^3.1.1" - nanocolors "^0.1.5" - node-releases "^1.1.76" - buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" @@ -1390,10 +1319,10 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -builtin-modules@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" - integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== builtins@^5.0.0: version "5.0.1" @@ -1420,6 +1349,14 @@ cacache@^17.0.0, cacache@^17.0.4, cacache@^17.1.3: tar "^6.1.11" unique-filename "^3.0.0" +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1444,13 +1381,6 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001259: - version "1.0.30001260" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001260.tgz#e3be3f34ddad735ca4a2736fa9e768ef34316270" - integrity sha512-Fhjc/k8725ItmrvW5QomzxLeojewxvqiYCKeFcfFEhut28IVLdpHU19dneOmltZQIE5HNbawj1HYD+1f2bM1Dg== - dependencies: - nanocolors "^0.1.0" - cardinal@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" @@ -1526,11 +1456,6 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -ci-info@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" - integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== - ci-info@^3.6.1, ci-info@^3.7.1, ci-info@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" @@ -1675,10 +1600,10 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -confusing-browser-globals@1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" - integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== +confusing-browser-globals@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" + integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== console-control-strings@^1.1.0: version "1.1.0" @@ -1720,13 +1645,6 @@ conventional-commits-parser@^3.0.0: through2 "^4.0.0" trim-off-newlines "^1.0.0" -convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" - cosmiconfig@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" @@ -1780,13 +1698,20 @@ 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, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, 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== dependencies: ms "2.1.2" +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" @@ -1824,11 +1749,34 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" + integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + diff@5.0.0, diff@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" @@ -1851,6 +1799,13 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -1877,11 +1832,6 @@ ejs@^3.1.8: dependencies: jake "^10.8.5" -electron-to-chromium@^1.3.846: - version "1.3.848" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.848.tgz#94cc196e496f33c0d71cd98561448f10018584cc" - integrity sha512-wchRyBcdcmibioggdO7CbMT5QQ4lXlN/g7Mkpf1K2zINidnqij6EVu94UIZ+h5nB2S9XD4bykqFv9LonAWLFyw== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -1899,12 +1849,13 @@ encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== +enhanced-resolve@^5.12.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== dependencies: - ansi-colors "^4.1.1" + graceful-fs "^4.2.4" + tapable "^2.2.0" env-paths@^2.2.0: version "2.2.1" @@ -1923,6 +1874,76 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +es-abstract@^1.22.1: + version "1.22.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.2.tgz#90f7282d91d0ad577f505e423e52d4c1d93c1b8a" + integrity sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA== + dependencies: + array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.2" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.1" + get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-typed-array "^1.1.12" + is-weakref "^1.0.2" + object-inspect "^1.12.3" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" + typed-array-length "^1.0.4" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.11" + +es-set-tostringtag@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" + integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== + dependencies: + get-intrinsic "^1.1.3" + has "^1.0.3" + has-tostringtag "^1.0.0" + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -1943,54 +1964,42 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -eslint-config-oclif-typescript@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/eslint-config-oclif-typescript/-/eslint-config-oclif-typescript-1.0.3.tgz#0061a810bf8f69571ad3c70368badcc018c3358e" - integrity sha512-TeJKXWBQ3uKMtzgz++UFNWpe1WCx8mfqRuzZy1LirREgRlVv656SkVG4gNZat5rRNIQgfDmTS+YebxK02kfylA== - dependencies: - "@typescript-eslint/eslint-plugin" "^4.31.2" - "@typescript-eslint/parser" "^4.31.2" - eslint-config-xo-space "^0.29.0" - eslint-plugin-mocha "^9.0.0" +eslint-config-oclif-typescript@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/eslint-config-oclif-typescript/-/eslint-config-oclif-typescript-2.0.1.tgz#bdaca00f53ee27ff6930673082a00a03d6cf8dd1" + integrity sha512-Z0U0KVNXGcTzYdSoDsrHulkspS1ZW/NrNgv/IAvpd7F2ZdrLUEmRlJn7Kwnk8CdUufJb/GsW+qVKIG/fPhwKpg== + dependencies: + "@typescript-eslint/eslint-plugin" "^6.7.2" + "@typescript-eslint/parser" "^6.7.2" + eslint-config-xo-space "^0.34.0" + eslint-import-resolver-typescript "^3.6.0" + eslint-plugin-import "^2.28.1" + eslint-plugin-mocha "^10.1.0" eslint-plugin-node "^11.1.0" -eslint-config-oclif@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-oclif/-/eslint-config-oclif-4.0.0.tgz#90a07587f7be7c92ae3ce750ae11cf1e864239eb" - integrity sha512-5tkUQeC33rHAhJxaGeBGYIflDLumeV2qD/4XLBdXhB/6F/+Jnwdce9wYHSvkx0JUqUQShpQv8JEVkBp/zzD7hg== +eslint-config-oclif@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-oclif/-/eslint-config-oclif-5.0.0.tgz#69c5cc8a19025e71fc49a10f47475bb8dd18ba24" + integrity sha512-yPxtUzU6eFL+WoW8DbRf7uoHxgiu0B/uY7k7rgHwFHij66WoI3qhBNhKI5R5FS5JeTuBOXKrNqQVDsSH0D/JvA== dependencies: - eslint-config-xo-space "^0.27.0" - eslint-plugin-mocha "^9.0.0" + eslint-config-xo-space "^0.34.0" + eslint-plugin-mocha "^10.1.0" eslint-plugin-node "^11.1.0" - eslint-plugin-unicorn "^36.0.0" - -eslint-config-xo-space@^0.27.0: - version "0.27.0" - resolved "https://registry.yarnpkg.com/eslint-config-xo-space/-/eslint-config-xo-space-0.27.0.tgz#9663e41d7bedc0f345488377770565aa9b0085e0" - integrity sha512-b8UjW+nQyOkhiANVpIptqlKPyE7XRyQ40uQ1NoBhzVfu95gxfZGrpliq8ZHBpaOF2wCLZaexTSjg7Rvm99vj4A== - dependencies: - eslint-config-xo "^0.35.0" - -eslint-config-xo-space@^0.29.0: - version "0.29.0" - resolved "https://registry.yarnpkg.com/eslint-config-xo-space/-/eslint-config-xo-space-0.29.0.tgz#5bbd2d0ecb172c4e65022b8543ecb1f7d199b8e2" - integrity sha512-emUZVHjmzl3I1aO2M/2gEpqa/GHXTl7LF/vQeAX4W+mQIU+2kyqY97FkMnSc2J8Osoq+vCSXCY/HjFUmFIF/Ag== - dependencies: - eslint-config-xo "^0.38.0" + eslint-plugin-unicorn "^48.0.1" -eslint-config-xo@^0.35.0: - version "0.35.0" - resolved "https://registry.yarnpkg.com/eslint-config-xo/-/eslint-config-xo-0.35.0.tgz#8b5afca244c44129c32386c28602f973ad5cb762" - integrity sha512-+WyZTLWUJlvExFrBU/Ldw8AB/S0d3x+26JQdBWbcqig2ZaWh0zinYcHok+ET4IoPaEcRRf3FE9kjItNVjBwnAg== +eslint-config-xo-space@^0.34.0: + version "0.34.0" + resolved "https://registry.yarnpkg.com/eslint-config-xo-space/-/eslint-config-xo-space-0.34.0.tgz#974db7f7091edc23e2f29cc98acaa1be78ac87e5" + integrity sha512-8ZI0Ta/loUIL1Wk/ouWvk2ZWN8X6Un49MqnBf2b6uMjC9c5Pcfr1OivEOrvd3niD6BKgMNH2Q9nG0CcCWC+iVA== dependencies: - confusing-browser-globals "1.0.10" + eslint-config-xo "^0.43.0" -eslint-config-xo@^0.38.0: - version "0.38.0" - resolved "https://registry.yarnpkg.com/eslint-config-xo/-/eslint-config-xo-0.38.0.tgz#50cbe676a90d5582e1bbf1de750286e7cf09378e" - integrity sha512-G2jL+VyfkcZW8GoTmqLsExvrWssBedSoaQQ11vyhflDeT3csMdBVp0On+AVijrRuvgmkWeDwwUL5Rj0qDRHK6g== +eslint-config-xo@^0.43.0: + version "0.43.1" + resolved "https://registry.yarnpkg.com/eslint-config-xo/-/eslint-config-xo-0.43.1.tgz#c2ac8993f6e429048c813f599265d1636a0bc060" + integrity sha512-azv1L2PysRA0NkZOgbndUpN+581L7wPqkgJOgxxw3hxwXAbJgD6Hqb/SjHRiACifXt/AvxCzE/jIKFAlI7XjvQ== dependencies: - confusing-browser-globals "1.0.10" + confusing-browser-globals "1.0.11" eslint-formatter-pretty@^4.1.0: version "4.1.0" @@ -2006,6 +2015,35 @@ eslint-formatter-pretty@^4.1.0: string-width "^4.2.0" supports-hyperlinks "^2.0.0" +eslint-import-resolver-node@^0.3.7: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-import-resolver-typescript@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.0.tgz#36f93e1eb65a635e688e16cae4bead54552e3bbd" + integrity sha512-QTHR9ddNnn35RTxlaEnx2gCxqFlF2SEN0SE2d17SqwyM7YOSI2GHWRYp5BiRkObTUNYPupC/3Fq2a0PpT+EKpg== + dependencies: + debug "^4.3.4" + enhanced-resolve "^5.12.0" + eslint-module-utils "^2.7.4" + fast-glob "^3.3.1" + get-tsconfig "^4.5.0" + is-core-module "^2.11.0" + is-glob "^4.0.3" + +eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== + dependencies: + debug "^3.2.7" + eslint-plugin-es@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" @@ -2014,13 +2052,36 @@ eslint-plugin-es@^3.0.0: eslint-utils "^2.0.0" regexpp "^3.0.0" -eslint-plugin-mocha@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-9.0.0.tgz#b4457d066941eecb070dc06ed301c527d9c61b60" - integrity sha512-d7knAcQj1jPCzZf3caeBIn3BnW6ikcvfz0kSqQpwPYcVGLoJV5sz0l0OJB2LR8I7dvTDbqq1oV6ylhSgzA10zg== +eslint-plugin-import@^2.28.1: + version "2.28.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz#63b8b5b3c409bfc75ebaf8fb206b07ab435482c4" + integrity sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A== + dependencies: + array-includes "^3.1.6" + array.prototype.findlastindex "^1.2.2" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.1" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.8.0" + has "^1.0.3" + is-core-module "^2.13.0" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.6" + object.groupby "^1.0.0" + object.values "^1.1.6" + semver "^6.3.1" + tsconfig-paths "^3.14.2" + +eslint-plugin-mocha@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-10.1.0.tgz#69325414f875be87fb2cb00b2ef33168d4eb7c8d" + integrity sha512-xLqqWUF17llsogVOC+8C6/jvQ+4IoOREbN7ZCHuOHuD6cT5cDD4h7f2LgsZuzMAiwswWE21tO7ExaknHVDrSkw== dependencies: eslint-utils "^3.0.0" - ramda "^0.27.1" + rambda "^7.1.0" eslint-plugin-node@^11.1.0: version "11.1.0" @@ -2034,49 +2095,41 @@ eslint-plugin-node@^11.1.0: resolve "^1.10.1" semver "^6.1.0" -eslint-plugin-unicorn@^36.0.0: - version "36.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-36.0.0.tgz#db50e1426839e401d33c5a279f49d4a5bbb640d8" - integrity sha512-xxN2vSctGWnDW6aLElm/LKIwcrmk6mdiEcW55Uv5krcrVcIFSWMmEgc/hwpemYfZacKZ5npFERGNz4aThsp1AA== +eslint-plugin-unicorn@^48.0.1: + version "48.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-48.0.1.tgz#a6573bc1687ae8db7121fdd8f92394b6549a6959" + integrity sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw== dependencies: - "@babel/helper-validator-identifier" "^7.14.9" - ci-info "^3.2.0" + "@babel/helper-validator-identifier" "^7.22.5" + "@eslint-community/eslint-utils" "^4.4.0" + ci-info "^3.8.0" clean-regexp "^1.0.0" - eslint-template-visitor "^2.3.2" - eslint-utils "^3.0.0" - is-builtin-module "^3.1.0" + esquery "^1.5.0" + indent-string "^4.0.0" + is-builtin-module "^3.2.1" + jsesc "^3.0.2" lodash "^4.17.21" pluralize "^8.0.0" read-pkg-up "^7.0.1" - regexp-tree "^0.1.23" - safe-regex "^2.1.1" - semver "^7.3.5" + regexp-tree "^0.1.27" + regjsparser "^0.10.0" + semver "^7.5.4" + strip-indent "^3.0.0" eslint-rule-docs@^1.1.5: version "1.1.235" resolved "https://registry.yarnpkg.com/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz#be6ef1fc3525f17b3c859ae2997fedadc89bfb9b" integrity sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A== -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-template-visitor@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/eslint-template-visitor/-/eslint-template-visitor-2.3.2.tgz#b52f96ff311e773a345d79053ccc78275bbc463d" - integrity sha512-3ydhqFpuV7x1M9EK52BPNj6V0Kwu0KKkcIAfpUhwHbR8ocRln/oUHgfxQupY8O1h4Qv/POHDumb/BwwNfxbtnA== - dependencies: - "@babel/core" "^7.12.16" - "@babel/eslint-parser" "^7.12.16" - eslint-visitor-keys "^2.0.0" - esquery "^1.3.1" - multimap "^1.1.0" + estraverse "^5.2.0" -eslint-utils@^2.0.0, eslint-utils@^2.1.0: +eslint-utils@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== @@ -2090,80 +2143,82 @@ eslint-utils@^3.0.0: dependencies: eslint-visitor-keys "^2.0.0" -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: +eslint-visitor-keys@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: +eslint-visitor-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint@^7.32.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.49.0: + version "8.49.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.49.0.tgz#09d80a89bdb4edee2efcf6964623af1054bf6d42" + integrity sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.2" + "@eslint/js" "8.49.0" + "@humanwhocodes/config-array" "^0.11.11" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" - debug "^4.0.1" + debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - js-yaml "^3.13.1" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" + optionator "^0.9.3" + strip-ansi "^6.0.1" text-table "^0.2.0" - v8-compile-cache "^2.0.3" -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" + acorn "^8.9.0" + 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.3.1, esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== +esquery@^1.4.2, esquery@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" @@ -2174,11 +2229,6 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - estraverse@^5.1.0, estraverse@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" @@ -2204,10 +2254,10 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== -fancy-test@^2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/fancy-test/-/fancy-test-2.0.18.tgz#36f938a7207c90b2bf2f85b9c7d0cb6de8444635" - integrity sha512-wA9xzWMJ4L51Jcr9k06koPwi58bbUkTZrqqNYd6z7DHky1jW+D5jc/q86zPmkVdnjOrCg91VOeEzyOjTLIlD8A== +fancy-test@^2.0.34: + version "2.0.35" + resolved "https://registry.yarnpkg.com/fancy-test/-/fancy-test-2.0.35.tgz#18c0ccd767a7332bea186369a7e7a79a3b4582bb" + integrity sha512-XE0L7yAFOKY2jDnkBDDQ3JBD7xbBFwAFl1Z/5WNKBeVNlaEP08wuRTPE3xj2k+fnUp2CMUfD+6aiIS+4pcrjwg== dependencies: "@types/chai" "*" "@types/lodash" "*" @@ -2215,13 +2265,13 @@ fancy-test@^2.0.16: "@types/sinon" "*" lodash "^4.17.13" mock-stdin "^1.0.0" - nock "^13.3.0" + nock "^13.3.3" stdout-stderr "^0.1.9" -fancy-test@^2.0.34: - version "2.0.35" - resolved "https://registry.yarnpkg.com/fancy-test/-/fancy-test-2.0.35.tgz#18c0ccd767a7332bea186369a7e7a79a3b4582bb" - integrity sha512-XE0L7yAFOKY2jDnkBDDQ3JBD7xbBFwAFl1Z/5WNKBeVNlaEP08wuRTPE3xj2k+fnUp2CMUfD+6aiIS+4pcrjwg== +fancy-test@^3.0.0-beta.2: + version "3.0.0-beta.2" + resolved "https://registry.yarnpkg.com/fancy-test/-/fancy-test-3.0.0-beta.2.tgz#a6b6b4f3ae30200ce64ff8dd79c73fcefba0058f" + integrity sha512-bXffX78q50U/dm9E0RVesZUQ0IZPmRkMhKJdbipuVOm/WuzdoYXZnT/sr0uyzyGaxtjFcatKhZgPRDk49DlXTw== dependencies: "@types/chai" "*" "@types/lodash" "*" @@ -2230,6 +2280,7 @@ fancy-test@^2.0.34: lodash "^4.17.13" mock-stdin "^1.0.0" nock "^13.3.3" + sinon "^16.0.0" stdout-stderr "^0.1.9" fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: @@ -2248,6 +2299,17 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2284,14 +2346,6 @@ filelist@^1.0.1: dependencies: minimatch "^3.0.4" -fill-keys@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" - integrity sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA= - dependencies: - is-object "~1.0.1" - merge-descriptors "~1.0.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -2333,6 +2387,13 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" @@ -2380,10 +2441,20 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== gauge@^4.0.3: version "4.0.4" @@ -2413,11 +2484,6 @@ gauge@^5.0.0: strip-ansi "^6.0.1" wide-align "^1.1.5" -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -2428,11 +2494,36 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= +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" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +get-tsconfig@^4.5.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.0.tgz#06ce112a1463e93196aa90320c35df5039147e34" + integrity sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw== + dependencies: + resolve-pkg-maps "^1.0.0" + git-raw-commits@^2.0.0: version "2.0.10" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.10.tgz#e2255ed9563b1c9c3ea6bd05806410290297bbc1" @@ -2451,6 +2542,13 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob@7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -2505,19 +2603,21 @@ global-dirs@^0.1.1: dependencies: ini "^1.3.4" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^13.6.0, globals@^13.9.0: - version "13.10.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.10.0.tgz#60ba56c3ac2ca845cfbf4faeca727ad9dd204676" - integrity sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g== +globals@^13.19.0: + version "13.21.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571" + integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg== dependencies: type-fest "^0.20.2" -globby@^11.0.1, globby@^11.0.3, globby@^11.1.0: +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@^11.0.1, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -2529,21 +2629,38 @@ globby@^11.0.1, globby@^11.0.3, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== -graceful-fs@^4.2.11, graceful-fs@^4.2.6: +graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -2554,6 +2671,30 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2660,16 +2801,16 @@ ignore-walk@^6.0.0: dependencies: minimatch "^9.0.0" -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - ignore@^5.1.1, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -2729,6 +2870,15 @@ init-package-json@^5.0.0: validate-npm-package-license "^3.0.4" validate-npm-package-name "^5.0.0" +internal-slot@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" + integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== + dependencies: + get-intrinsic "^1.2.0" + has "^1.0.3" + side-channel "^1.0.4" + interpret@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" @@ -2749,11 +2899,27 @@ irregular-plurals@^3.2.0: resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-3.3.0.tgz#67d0715d4361a60d9fd9ee80af3881c631a31ee2" integrity sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g== +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -2761,12 +2927,25 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-builtin-module@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.1.0.tgz#6fdb24313b1c03b75f8b9711c0feb8c30b903b00" - integrity sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg== +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== dependencies: - builtin-modules "^3.0.0" + builtin-modules "^3.3.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== is-cidr@^4.0.2: version "4.0.2" @@ -2775,6 +2954,13 @@ is-cidr@^4.0.2: dependencies: cidr-regex "^3.1.1" +is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.8.1: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + is-core-module@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" @@ -2782,12 +2968,12 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" -is-core-module@^2.8.1: - version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: - has "^1.0.3" + has-tostringtag "^1.0.0" is-docker@^2.0.0: version "2.2.1" @@ -2811,11 +2997,30 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -2826,10 +3031,10 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-object@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" - integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-plain-obj@^1.1.0: version "1.1.0" @@ -2841,16 +3046,45 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-retry-allowed@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + is-text-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" @@ -2858,11 +3092,25 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== + dependencies: + which-typed-array "^1.1.11" + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -2875,6 +3123,11 @@ isarray@0.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -2899,19 +3152,34 @@ jake@^10.8.5: filelist "^1.0.1" minimatch "^3.0.4" +jest-diff@^29.0.3: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@4.1.0: +js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" -js-yaml@^3.13.1, js-yaml@^3.14.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== @@ -2919,10 +3187,15 @@ js-yaml@^3.13.1, js-yaml@^3.14.1: argparse "^1.0.7" esprima "^4.0.0" -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== json-parse-better-errors@^1.0.1: version "1.0.2" @@ -2944,11 +3217,6 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -2964,10 +3232,12 @@ json-stringify-safe@^5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5@^2.1.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.2.tgz#64471c5bdcc564c18f7c1d4df2e2297f2457c5ab" - integrity sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ== +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" jsonfile@^6.0.1: version "6.1.0" @@ -3154,11 +3424,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -3169,11 +3434,6 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= - lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -3282,11 +3542,6 @@ meow@^9.0.0: type-fest "^0.18.0" yargs-parser "^20.2.3" -merge-descriptors@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -3319,7 +3574,7 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^3.1.1: +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -3342,6 +3597,11 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + minimist@^1.2.3: version "1.2.7" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" @@ -3456,36 +3716,21 @@ mock-stdin@^1.0.0: resolved "https://registry.yarnpkg.com/mock-stdin/-/mock-stdin-1.0.0.tgz#efcfaf4b18077e14541742fd758b9cae4e5365ea" integrity sha512-tukRdb9Beu27t6dN+XztSRHq9J0B/CoAOySGzHfn8UTfmqipA5yNT/sDUEyYdAV3Hpka6Wx6kOMxuObdOex60Q== -module-not-found-error@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" - integrity sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA= - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0, ms@^2.1.2: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multimap@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multimap/-/multimap-1.1.0.tgz#5263febc085a1791c33b59bb3afc6a76a2a10ca8" - integrity sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw== - mute-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== -nanocolors@^0.1.0, nanocolors@^0.1.5: - version "0.1.12" - resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.12.tgz#8577482c58cbd7b5bb1681db4cf48f11a87fd5f6" - integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ== - nanoid@3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" @@ -3522,6 +3767,17 @@ nise@^5.1.0: just-extend "^4.0.2" path-to-regexp "^1.7.0" +nise@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.4.tgz#491ce7e7307d4ec546f5a659b2efe94a18b4bbc0" + integrity sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg== + 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@*, nock@^13.3.0: version "13.3.0" resolved "https://registry.yarnpkg.com/nock/-/nock-13.3.0.tgz#b13069c1a03f1ad63120f994b04bfd2556925768" @@ -3559,11 +3815,6 @@ node-gyp@^9.0.0, node-gyp@^9.4.0: tar "^6.1.2" which "^2.0.2" -node-releases@^1.1.76: - version "1.1.76" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e" - integrity sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA== - nopt@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" @@ -3791,11 +4042,59 @@ npmlog@^7.0.1: gauge "^5.0.0" set-blocking "^2.0.0" +object-inspect@^1.12.3, object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +object-keys@^1.1.1: + version "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" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.fromentries@^2.0.6: + version "2.0.7" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" + integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +object.groupby@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.1.tgz#d41d9f3c8d6c778d9cbac86b4ee9f5af103152ee" + integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + +object.values@^1.1.6: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" + integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3803,17 +4102,17 @@ once@^1.3.0: dependencies: wrappy "1" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.3" p-limit@^2.2.0: version "2.3.0" @@ -3941,7 +4240,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.6, path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -4011,6 +4310,15 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + proc-log@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8" @@ -4021,11 +4329,6 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-all-reject-late@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" @@ -4061,15 +4364,6 @@ propagate@^2.0.0: resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== -proxyquire@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-2.1.3.tgz#2049a7eefa10a9a953346a18e54aab2b4268df39" - integrity sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg== - dependencies: - fill-keys "^1.0.2" - module-not-found-error "^1.0.1" - resolve "^1.11.1" - punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -4095,10 +4389,10 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -ramda@^0.27.1: - version "0.27.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" - integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== +rambda@^7.1.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/rambda/-/rambda-7.5.0.tgz#1865044c59bc0b16f63026c6e5a97e4b1bbe98fe" + integrity sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA== randombytes@^2.1.0: version "2.1.0" @@ -4107,6 +4401,11 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + read-cmd-shim@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz#640a08b473a49043e394ae0c7a34dd822c73b9bb" @@ -4214,26 +4513,37 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" -regexp-tree@^0.1.23, regexp-tree@~0.1.1: - version "0.1.24" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.24.tgz#3d6fa238450a4d66e5bc9c4c14bb720e2196829d" - integrity sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw== +regexp-tree@^0.1.27: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== -regexpp@^3.0.0, regexpp@^3.1.0: +regexp.prototype.flags@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + set-function-name "^2.0.0" + +regexpp@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== +regjsparser@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.10.0.tgz#b1ed26051736b436f22fdec1c8f72635f9f44892" + integrity sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA== + dependencies: + jsesc "~0.5.0" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - resolve-from@5.0.0, resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" @@ -4251,7 +4561,12 @@ resolve-global@1.0.0, resolve-global@^1.0.0: dependencies: global-dirs "^0.1.1" -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.20.0: +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -4259,6 +4574,15 @@ resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.20 is-core-module "^2.2.0" path-parse "^1.0.6" +resolve@^1.22.4: + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -4283,22 +4607,29 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +safe-array-concat@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2" - integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A== +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== dependencies: - regexp-tree "~0.1.1" + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" @@ -4317,11 +4648,16 @@ semver@7.3.5: dependencies: lru-cache "^6.0.0" -semver@^6.1.0, semver@^6.3.0: +semver@^6.1.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + semver@^7.0.0, semver@^7.1.1, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" @@ -4329,7 +4665,7 @@ semver@^7.0.0, semver@^7.1.1, semver@^7.5.4: dependencies: lru-cache "^6.0.0" -semver@^7.2.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3: +semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3: version "7.5.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== @@ -4348,6 +4684,15 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -4389,6 +4734,15 @@ shx@^0.3.4: minimist "^1.2.3" shelljs "^0.8.5" +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -4422,6 +4776,18 @@ sinon@^11.1.2: nise "^5.1.0" supports-color "^7.2.0" +sinon@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-16.0.0.tgz#06da4e63624b946c9d7e67cce21c2f67f40f23a9" + integrity sha512-B8AaZZm9CT5pqe4l4uWJztfD/mOTa7dL8Qo0W4+s+t74xECOgSZDDQCBjNgIK3+n4kyxQrSTv2V5ul8K25qkiQ== + 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" @@ -4458,11 +4824,6 @@ socks@^2.6.2: ip "^2.0.0" smart-buffer "^4.2.0" -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -4534,6 +4895,33 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -4574,7 +4962,7 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@3.1.1, strip-json-comments@^3.1.1: version "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== @@ -4613,17 +5001,15 @@ supports-hyperlinks@^2.0.0, supports-hyperlinks@^2.2.0: has-flag "^4.0.0" supports-color "^7.0.0" -table@^6.0.9: - version "6.7.1" - resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" - integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== - dependencies: - ajv "^8.0.1" - lodash.clonedeep "^4.5.0" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.0" - strip-ansi "^6.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== tar@^6.1.11, tar@^6.1.13, tar@^6.1.15, tar@^6.1.2: version "6.1.15" @@ -4664,11 +5050,6 @@ tiny-relative-date@^1.3.0: resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -4691,6 +5072,11 @@ trim-off-newlines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= +ts-api-utils@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" + integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -4710,23 +5096,29 @@ ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tsd@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/tsd/-/tsd-0.25.0.tgz#bed2a937ab414e1c5ae1ca687bb44ea3a6f549ba" - integrity sha512-liUlvKtsdr+70XEZP/kkF6U8+Q9URZi4Pw58ih7a9x3kjJblG8rdVgvG62xcvkgRva1q3yWX5qAxfYZuYiC5CA== +tsconfig-paths@^3.14.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== dependencies: - "@tsd/typescript" "~4.9.3" + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tsd@^0.29.0: + version "0.29.0" + resolved "https://registry.yarnpkg.com/tsd/-/tsd-0.29.0.tgz#eed29647bf67ae77e4d686e9fdde5dd27bd1529f" + integrity sha512-5B7jbTj+XLMg6rb9sXRBGwzv7h8KJlGOkTHxY63eWpZJiQ5vJbXEjL0u7JkIxwi5EsrRE1kRVUWmy6buK/ii8A== + dependencies: + "@tsd/typescript" "~5.2.2" eslint-formatter-pretty "^4.1.0" globby "^11.0.1" + jest-diff "^29.0.3" meow "^9.0.0" path-exists "^4.0.0" read-pkg-up "^7.0.0" -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" @@ -4737,13 +5129,6 @@ tslib@^2.6.2: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - tuf-js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/tuf-js/-/tuf-js-1.1.7.tgz#21b7ae92a9373015be77dfe0cb282a80ec3bbe43" @@ -4802,10 +5187,59 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -typescript@^4.9.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + +typescript@^5: + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" unique-filename@^3.0.0: version "3.0.0" @@ -4843,11 +5277,6 @@ v8-compile-cache-lib@^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== -v8-compile-cache@^2.0.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -4875,6 +5304,28 @@ wcwidth@^1.0.0: dependencies: defaults "^1.0.3" +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" + integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -4910,11 +5361,6 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"