From 2a3b0846aad89b6e475b1a1ea4547695609ca09c Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 28 Sep 2023 11:18:50 -0700 Subject: [PATCH 01/13] fix: use ES2022 (#793) * fix: use ES2022 * test: use --json for config unset * feat: stop using getters and setters for flags * chore: clean up * feat: expose json flag * feat: remove pass through getter and setter * fix: correct order of flags in toCached * chore: clean up * fix: flag merge order * chore: documentation * test: use oclif/test v3 * feat: set spinner style on windows too * fix: handle cmd with baseFlags but no flags * fix: some circular deps * fix: circular deps in help * fix: ts-node and config circular deps * fix: toCached circular dep in help * chore: organize utils * test: enforce no circular deps * chore: remove Flags.json * chore: add prettier config * test: add nyc * test: improve test coverage * test: windows unit tests * chore: revert change to automerge.yml * chore: code review * perf: parallelize cacheCommand --- .nycrc.json | 11 + .prettierrc.json | 1 + guides/PRE_CORE_MIGRATION.md | 45 + guides/V3_MIGRATION.md | 31 +- package.json | 12 +- src/cli-ux/action/spinner.ts | 2 +- src/cli-ux/flush.ts | 5 +- src/cli-ux/styled/json.ts | 8 +- src/command.ts | 112 +- src/config/config.ts | 153 +-- src/config/index.ts | 1 - src/config/plugin-loader.ts | 2 +- src/config/plugin.ts | 17 +- src/config/ts-node.ts | 12 +- src/config/util.ts | 12 - src/errors/handle.ts | 2 +- src/flags.ts | 6 +- src/help/index.ts | 42 +- src/help/root.ts | 2 +- src/help/util.ts | 28 +- src/index.ts | 4 +- src/interfaces/parser.ts | 15 +- src/interfaces/plugin.ts | 6 + src/module-loader.ts | 2 +- src/parser/parse.ts | 7 +- src/util/aggregate-flags.ts | 18 + src/util/cache-command.ts | 111 ++ src/util/cache-default-value.ts | 23 + src/{util.ts => util/index.ts} | 30 +- test/command/command.test.ts | 218 +--- test/config/config.flexible.test.ts | 2 + test/config/config.test.ts | 2 + test/help/help-test-utils.ts | 5 +- test/help/util.test.ts | 23 +- test/util.test.ts | 70 -- test/util/cache-command.test.ts | 298 +++++ test/util/cache-default-value.test.ts | 42 + test/util/index.test.ts | 141 +++ tsconfig.json | 2 +- yarn.lock | 1476 ++++++++++++++++++++++++- 40 files changed, 2326 insertions(+), 673 deletions(-) create mode 100644 .nycrc.json create mode 100644 .prettierrc.json create mode 100644 src/util/aggregate-flags.ts create mode 100644 src/util/cache-command.ts create mode 100644 src/util/cache-default-value.ts rename src/{util.ts => util/index.ts} (88%) delete mode 100644 test/util.test.ts create mode 100644 test/util/cache-command.test.ts create mode 100644 test/util/cache-default-value.test.ts create mode 100644 test/util/index.test.ts diff --git a/.nycrc.json b/.nycrc.json new file mode 100644 index 000000000..d6c706920 --- /dev/null +++ b/.nycrc.json @@ -0,0 +1,11 @@ +{ + "check-coverage": true, + "lines": 80, + "statements": 70, + "functions": 70, + "branches": 60, + "reporter": ["lcov", "text"], + "extension": [".ts"], + "include": ["**/*.ts"], + "exclude": ["**/*.d.ts", "test/**"] +} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..631433570 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +"@oclif/prettier-config" diff --git a/guides/PRE_CORE_MIGRATION.md b/guides/PRE_CORE_MIGRATION.md index 16a6fdc20..649af2cda 100644 --- a/guides/PRE_CORE_MIGRATION.md +++ b/guides/PRE_CORE_MIGRATION.md @@ -5,6 +5,7 @@ Migrating to `@oclif/core` from the deprecated oclif libraries (`@oclif/config`, - [Migrating to @oclif/core from deprecated libraries](#migrating-to-oclifcore-from-deprecated-libraries) - [Update Imports](#update-imports) + - [Update Command Args](#update-command-args) - [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) @@ -30,6 +31,50 @@ With this import: import {Command, Flags, Topic, Help} from '@oclif/core'; ``` +## Update Command Args + +We updated the `Command.args` to more closely resemble flags + +**Before** + +```typescript +import { Command } from '@oclif/core' + +export default MyCommand extends Command { + static args = [{name: arg1, description: 'an argument', required: true}] + + public async run(): Promise { + const {args} = await this.parse(MyCommand) // args is useless {[name: string]: any} + } +} +``` + +**After** + +```typescript +import { Command, Args } from '@oclif/core' + +export default MyCommand extends Command { + static args = { + arg1: Args.string({description: 'an argument', required: true}) + } + + public async run(): Promise { + const {args} = await this.parse(MyCommand) // args is { arg1: string } + } +} +``` + +These are the available Args: +- string +- integer +- boolean +- url +- file +- directory +- custom + + ## Update your bin scripts `@oclif/core` now supports separate bin scripts for production and development. diff --git a/guides/V3_MIGRATION.md b/guides/V3_MIGRATION.md index 9b83abc6f..82eaa7ba1 100644 --- a/guides/V3_MIGRATION.md +++ b/guides/V3_MIGRATION.md @@ -5,16 +5,19 @@ Migrating to @oclif/core@V3 - [BREAKING CHANGES ❗](#breaking-changes-) - [Dropping node 14 and node 16 support](#dropping-node-14-and-node-16-support) - [Bin Scripts for ESM/CJS Interoperability](#bin-scripts-for-esmcjs-interoperability) + - [Dropped `ts-node` as a dependency](#dropped-ts-node-as-a-dependency) - [`Config.plugins`](#configplugins) - [Readonly properties on `Config`](#readonly-properties-on-config) - [Private methods on `Plugin`](#private-methods-on-plugin) - [`global['cli-ux']` -\> `global.ux`](#globalcli-ux---globalux) - [`handle`](#handle) - [`noCacheDefault` flag property replaces `isWritingManifest`](#nocachedefault-flag-property-replaces-iswritingmanifest) + - [Removed `toCached` export](#removed-tocached-export) - [Features 🎉](#features-) - - [Cache Flexible taxonomy Command Permutations](#cache-flexible-taxonomy-command-permutations) + - [Performance Improvements](#performance-improvements) - [charAliases Flag Property](#charaliases-flag-property) - [Flags.option](#flagsoption) + - [Set spinner styles](#set-spinner-styles) ## BREAKING CHANGES ❗ @@ -36,6 +39,10 @@ In order to support ESM and CommonJS plugin interoperability you will need to up If you'd like to migrate your plugin to ESM, please read our guide [here](https://oclif.io/docs/esm) +### Dropped `ts-node` as a dependency + +We removed `ts-node` as a dependency to reduce the package size. By doing this, it means that linked plugin **must** have `ts-node` as a `devDependency` in order for auto-transpilation to work. + ### `Config.plugins` `Config.plugins` is now a `Map` where the keys are the plugin names and the values are the loaded `Plugin` instances. Previously it was an array of loaded `Plugin` instances. @@ -110,12 +117,18 @@ export const mySensitiveFlag = Flags.string({ }); ``` +### Removed `toCached` export + +We removed the `toCached` export since there's no need for consumers of `@oclif/core` to use this function. ## Features 🎉 -### Cache Flexible taxonomy Command Permutations +### Performance Improvements -The command permutations for flexible taxonomy are now cached in the oclif.manifest.json allowing for quicker startup times. +- Cache command permutations for flexible taxonomy in the `oclif.manifest.json` +- Cache additional command properties (`isESM`, `relativePath`) in the `oclif.manifest.json` +- Improved accuracy in the `DEBUG=perf` output. +- Remove `ts-node` from `dependencies` to reduce the package size. ### charAliases Flag Property @@ -149,3 +162,15 @@ export default class MyCommand extends Command { } } ``` + +### Set spinner styles + +You can now configure the style of the spinner when using `ux.action.start`. See [spinners](https://github.com/oclif/core/blob/main/src/cli-ux/action/spinners.ts) for all the different options. + +```typescript +ux.action.start('starting spinner', 'spinning', {style: 'arc'}) +await ux.wait(2500) +ux.action.status = 'still going' +await ux.wait(2500) +ux.action.stop() +``` diff --git a/package.json b/package.json index f4f508356..d9cb3af3a 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "@commitlint/config-conventional": "^12.1.4", "@oclif/plugin-help": "^5.2.8", "@oclif/plugin-plugins": "^3.3.0", - "@oclif/test": "^2.4.7", + "@oclif/prettier-config": "^0.1.1", + "@oclif/test": "^3.0.0-beta.1", "@types/ansi-styles": "^3.2.1", "@types/benchmark": "^2.1.2", "@types/chai": "^4.3.4", @@ -65,8 +66,10 @@ "fancy-test": "^3.0.0-beta.2", "globby": "^11.1.0", "husky": "6", + "madge": "^6.1.0", "mocha": "^10.2.0", "nock": "^13.3.0", + "nyc": "^15.1.0", "shx": "^0.3.4", "sinon": "^11.1.2", "tsd": "^0.29.0", @@ -105,13 +108,14 @@ "commitlint": "commitlint", "compile": "tsc", "lint": "eslint . --ext .ts", - "posttest": "yarn lint", + "posttest": "yarn lint && yarn test:circular-deps", "prepack": "yarn run build", - "pretest": "yarn build --noEmit && tsc -p test --noEmit --skipLibCheck", + "pretest": "yarn build && tsc -p test --noEmit --skipLibCheck", + "test:circular-deps": "madge lib/ -c", "test:e2e": "mocha --forbid-only \"test/**/*.e2e.ts\" --parallel --timeout 1200000", "test:esm-cjs": "cross-env DEBUG=e2e:* ts-node test/integration/esm-cjs.ts", "test:perf": "ts-node test/perf/parser.perf.ts", - "test": "mocha --forbid-only \"test/**/*.test.ts\"" + "test": "nyc mocha --forbid-only \"test/**/*.test.ts\"" }, "types": "lib/index.d.ts" } diff --git a/src/cli-ux/action/spinner.ts b/src/cli-ux/action/spinner.ts index d462514b3..7ac05c9ef 100644 --- a/src/cli-ux/action/spinner.ts +++ b/src/cli-ux/action/spinner.ts @@ -64,7 +64,7 @@ export default class SpinnerAction extends ActionBase { } private getFrames(opts?: Options) { - if (opts?.style) return spinners[process.platform === 'win32' ? 'line' : opts.style].frames + if (opts?.style) return spinners[opts.style].frames return spinners[process.platform === 'win32' ? 'line' : 'dots2'].frames } diff --git a/src/cli-ux/flush.ts b/src/cli-ux/flush.ts index c6a0c152d..33abd784f 100644 --- a/src/cli-ux/flush.ts +++ b/src/cli-ux/flush.ts @@ -1,4 +1,5 @@ -import {Errors, stdout} from '..' +import {error} from '../errors' +import {stdout} from './stream' function timeout(p: Promise, ms: number) { function wait(ms: number, unref = false) { @@ -8,7 +9,7 @@ function timeout(p: Promise, ms: number) { }) } - return Promise.race([p, wait(ms, true).then(() => Errors.error('timed out'))]) + return Promise.race([p, wait(ms, true).then(() => error('timed out'))]) } async function _flush() { diff --git a/src/cli-ux/styled/json.ts b/src/cli-ux/styled/json.ts index c7db532d0..cbdc4892a 100644 --- a/src/cli-ux/styled/json.ts +++ b/src/cli-ux/styled/json.ts @@ -1,15 +1,17 @@ import chalk from 'chalk' +import {format} from 'node:util' +import {stdout} from '../stream' -import {ux} from '../../index' +const info = (output: string) => stdout.write(format(output) + '\n') export default function styledJSON(obj: unknown): void { const json = JSON.stringify(obj, null, 2) if (!chalk.level) { - ux.info(json) + info(json) return } const cardinal = require('cardinal') const theme = require('cardinal/themes/jq') - ux.info(cardinal.highlight(json, {json: true, theme})) + info(cardinal.highlight(json, {json: true, theme})) } diff --git a/src/command.ts b/src/command.ts index 7a54e2136..9b365af69 100644 --- a/src/command.ts +++ b/src/command.ts @@ -25,7 +25,7 @@ import {LoadOptions} from './interfaces/config' import {PJSON} from './interfaces' import {Plugin} from './interfaces/plugin' import {PrettyPrintableError} from './errors' -import {boolean} from './flags' +import {aggregateFlags} from './util/aggregate-flags' import chalk from 'chalk' import {fileURLToPath} from 'node:url' import {ux} from './cli-ux' @@ -42,13 +42,6 @@ stdout.on('error', (err: any) => { throw err }) -const jsonFlag = { - json: boolean({ - description: 'Format output as json.', - helpGroup: 'GLOBAL', - }), -} - /** * An abstract class which acts as the base for each command * in your project. @@ -126,37 +119,7 @@ export abstract class Command { public static hasDynamicHelp = false - protected static '_--' = false - - protected static _enableJsonFlag = false - - public static get enableJsonFlag(): boolean { - return this._enableJsonFlag - } - - public static set enableJsonFlag(value: boolean) { - this._enableJsonFlag = value - if (value === true) { - this.baseFlags = jsonFlag - } else { - delete this.baseFlags?.json - this.flags = {} // force the flags setter to run - delete this.flags?.json - } - } - - public static get '--'(): boolean { - return Command['_--'] - } - - public static set '--'(value: boolean) { - Command['_--'] = value - } - - public get passThroughEnabled(): boolean { - return Command['_--'] - } - + public static enableJsonFlag = false /** * instantiate and run the command * @@ -184,29 +147,10 @@ export abstract class Command { return cmd._run>() } - protected static _baseFlags: FlagInput - - static get baseFlags(): FlagInput { - return this._baseFlags - } - - 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 - } + public static baseFlags: FlagInput /** A hash of flags for the command */ - protected static _flags: FlagInput - - public static get flags(): FlagInput { - return this._flags - } - - public static set flags(flags: FlagInput) { - // eslint-disable-next-line prefer-object-spread - this._flags = Object.assign({}, this._flags ?? {}, this.baseFlags, flags) - } + public static flags: FlagInput public id: string | undefined @@ -284,16 +228,19 @@ export abstract class Command { * @returns {boolean} true if the command supports json and the --json flag is present */ public jsonEnabled(): boolean { - // if the command doesn't support json, return false + // If the command doesn't support json, return false if (!this.ctor.enableJsonFlag) return false - // if the command parameter pass through is enabled, return true if the --json flag is before the '--' separator - if (this.passThroughEnabled) { - const ptIndex = this.argv.indexOf('--') - const jsonIndex = this.argv.indexOf('--json') - return jsonIndex > -1 && (ptIndex === -1 || jsonIndex < ptIndex) - } - return this.argv.includes('--json') || this.config.scopedEnvVar?.('CONTENT_TYPE')?.toLowerCase() === 'json' + // If the CONTENT_TYPE env var is set to json, return true + if (this.config.scopedEnvVar?.('CONTENT_TYPE')?.toLowerCase() === 'json') return true + + const passThroughIndex = this.argv.indexOf('--') + const jsonIndex = this.argv.indexOf('--json') + return passThroughIndex === -1 + // If '--' is not present, then check for `--json` in this.argv + ? jsonIndex > -1 + // If '--' is present, return true only the --json flag exists and is before the '--' + : jsonIndex > -1 && jsonIndex < passThroughIndex } /** @@ -312,8 +259,13 @@ export abstract class Command { } protected warnIfFlagDeprecated(flags: Record): void { + const allFlags = aggregateFlags( + this.ctor.flags, + this.ctor.baseFlags, + this.ctor.enableJsonFlag, + ) for (const flag of Object.keys(flags)) { - const flagDef = this.ctor.flags[flag] + const flagDef = allFlags[flag] const deprecated = flagDef?.deprecated if (deprecated) { this.warn(formatFlagDeprecationWarning(flag, deprecated)) @@ -352,12 +304,22 @@ export abstract class Command { } } - protected async parse(options?: Input, argv = this.argv): Promise> { + protected async parse( + options?: Input, + argv = this.argv, + ): Promise> { if (!options) options = this.ctor as Input - const opts = {context: this, ...options} - // the spread operator doesn't work with getters so we have to manually add it here - opts.flags = options?.flags - opts.args = options?.args + + const opts = { + context: this, + ...options, + flags: aggregateFlags( + options.flags, + options.baseFlags, + options.enableJsonFlag, + ), + } + const results = await Parser.parse(argv, opts) this.warnIfFlagDeprecated(results.flags ?? {}) @@ -449,7 +411,7 @@ export namespace Command { export type Flag = IFlag export namespace Flag { - export type Cached = Omit & (BooleanFlagProps | OptionFlagProps) + export type Cached = Omit & (BooleanFlagProps | OptionFlagProps) & {hasDynamicHelp?: boolean} export type Any = Flag | Cached } diff --git a/src/config/config.ts b/src/config/config.ts index 83428679a..41795375d 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,20 +1,19 @@ 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 {Debug, collectUsableIds, getCommandIdPermutations} from './util' import {Hook, Hooks, PJSON, Topic} from '../interfaces' 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 {compact, getHomeDir, getPlatform, isProd, requireJson} from '../util' import {join, sep} from 'node:path' - import {Command} from '../command' import {Performance} from '../performance' import PluginLoader from './plugin-loader' +import WSL from 'is-wsl' import {format} from 'node:util' -import {getHelpFlagAdditions} from '../help' +import {getHelpFlagAdditions} from '../help/util' import {loadWithData} from '../module-loader' import {settings} from '../settings' import {stdout} from '../cli-ux/stream' @@ -30,8 +29,6 @@ function channelFromVersion(version: string) { return (m && m[1]) || 'stable' } -const WSL = require('is-wsl') - function isConfig(o: any): o is Config { return o && Boolean(o._base) } @@ -812,147 +809,3 @@ 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: OptionFlag, respectNoCacheDefault: boolean) => { - if (respectNoCacheDefault && flag.noCacheDefault) return - // Prefer the defaultHelp function (returns a friendly string for complex types) - if (typeof flag.defaultHelp === 'function') { - try { - return await flag.defaultHelp({options: flag, flags: {}}) - } catch { - return - } - } - - // if not specified, try the default function - if (typeof flag.default === 'function') { - try { - return await flag.default({options: flag, flags: {}}) - } catch {} - } else { - return flag.default - } -} - -const defaultArgToCached = async (arg: Arg, respectNoCacheDefault: boolean): Promise => { - if (respectNoCacheDefault && arg.noCacheDefault) return - // Prefer the defaultHelp function (returns a friendly string for complex types) - if (typeof arg.defaultHelp === 'function') { - try { - return await arg.defaultHelp({options: arg, flags: {}}) - } catch { - return - } - } - - // if not specified, try the default function - if (typeof arg.default === 'function') { - try { - return await arg.default({options: arg, flags: {}}) - } catch {} - } else { - return arg.default - } -} - -export async function toCached(c: Command.Class, plugin?: IPlugin, respectNoCacheDefault = false): Promise { - const flags = {} as {[k: string]: Command.Flag.Cached} - - for (const [name, flag] of Object.entries(c.flags || {})) { - if (flag.type === 'boolean') { - flags[name] = { - name, - type: flag.type, - char: flag.char, - summary: flag.summary, - description: flag.description, - hidden: flag.hidden, - required: flag.required, - helpLabel: flag.helpLabel, - helpGroup: flag.helpGroup, - allowNo: flag.allowNo, - dependsOn: flag.dependsOn, - relationships: flag.relationships, - exclusive: flag.exclusive, - deprecated: flag.deprecated, - deprecateAliases: c.deprecateAliases, - aliases: flag.aliases, - charAliases: flag.charAliases, - delimiter: flag.delimiter, - noCacheDefault: flag.noCacheDefault, - } - } else { - flags[name] = { - name, - type: flag.type, - char: flag.char, - summary: flag.summary, - description: flag.description, - hidden: flag.hidden, - required: flag.required, - helpLabel: flag.helpLabel, - helpValue: flag.helpValue, - helpGroup: flag.helpGroup, - multiple: flag.multiple, - options: flag.options, - dependsOn: flag.dependsOn, - relationships: flag.relationships, - exclusive: flag.exclusive, - default: await defaultFlagToCached(flag, respectNoCacheDefault), - deprecated: flag.deprecated, - deprecateAliases: c.deprecateAliases, - aliases: flag.aliases, - charAliases: flag.charAliases, - delimiter: flag.delimiter, - noCacheDefault: flag.noCacheDefault, - } - // a command-level placeholder in the manifest so that oclif knows it should regenerate the command during help-time - if (typeof flag.defaultHelp === 'function') { - c.hasDynamicHelp = true - } - } - } - - const args = {} as {[k: string]: Command.Arg.Cached} - for (const [name, arg] of Object.entries(ensureArgObject(c.args))) { - args[name] = { - name, - description: arg.description, - required: arg.required, - options: arg.options, - default: await defaultArgToCached(arg, respectNoCacheDefault), - hidden: arg.hidden, - noCacheDefault: arg.noCacheDefault, - } - } - - const stdProperties = { - id: c.id, - summary: c.summary, - description: c.description, - strict: c.strict, - usage: c.usage, - pluginName: plugin && plugin.name, - pluginAlias: plugin && plugin.alias, - pluginType: plugin && plugin.type, - hidden: c.hidden, - state: c.state, - aliases: c.aliases || [], - examples: c.examples || (c as any).example, - deprecationOptions: c.deprecationOptions, - deprecateAliases: c.deprecateAliases, - flags, - args, - } - - // do not include these properties in manifest - const ignoreCommandProperties = ['plugin', '_flags', '_enableJsonFlag', '_globalFlags', '_baseFlags'] - const stdKeys = Object.keys(stdProperties) - const keysToAdd = Object.keys(c).filter(property => ![...stdKeys, ...ignoreCommandProperties].includes(property)) - const additionalProperties: Record = {} - for (const key of keysToAdd) { - additionalProperties[key] = (c as any)[key] - } - - return {...stdProperties, ...additionalProperties} -} diff --git a/src/config/index.ts b/src/config/index.ts index a79d4c3bf..748162de7 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,5 +1,4 @@ export {Config} from './config' -export {toCached} from './config' export {Plugin} from './plugin' export {tsPath} from './ts-node' diff --git a/src/config/plugin-loader.ts b/src/config/plugin-loader.ts index 7965a08bb..ed008a708 100644 --- a/src/config/plugin-loader.ts +++ b/src/config/plugin-loader.ts @@ -43,7 +43,7 @@ export default class PluginLoader { 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}) + rootPlugin = new Plugin.Plugin({root: this.options.root, isRoot: true}) await rootPlugin.load() marker?.addDetails({ hasManifest: rootPlugin.hasManifest ?? false, diff --git a/src/config/plugin.ts b/src/config/plugin.ts index 8f623db75..cd229e86a 100644 --- a/src/config/plugin.ts +++ b/src/config/plugin.ts @@ -1,13 +1,11 @@ import {CLIError, error} from '../errors' import { Debug, - flatMap, getCommandIdPermutations, - mapValues, resolvePackage, } from './util' import {Plugin as IPlugin, PluginOptions} from '../interfaces/plugin' -import {compact, exists, isProd, readJson, requireJson} from '../util' +import {compact, exists, isProd, mapValues, readJson, requireJson} from '../util' import {dirname, join, parse, relative, sep} from 'node:path' import {loadWithData, loadWithDataFromManifest} from '../module-loader' import {Command} from '../command' @@ -15,9 +13,9 @@ import {Manifest} from '../interfaces/manifest' import {PJSON} from '../interfaces/pjson' import {Performance} from '../performance' import {Topic} from '../interfaces/topic' +import {cacheCommand} from '../util/cache-command' import {inspect} from 'node:util' import {sync} from 'globby' -import {toCached} from './config' import {tsPath} from './ts-node' const _pjson = requireJson(__dirname, '..', '..', 'package.json') @@ -26,10 +24,10 @@ function topicsToArray(input: any, base?: string): Topic[] { if (!input) return [] base = base ? `${base}:` : '' if (Array.isArray(input)) { - return [...input, ...flatMap(input, t => topicsToArray(t.subtopics, `${base}${t.name}`))] + return [...input, input.flatMap(t => topicsToArray(t.subtopics, `${base}${t.name}`))] } - return flatMap(Object.keys(input), k => { + return Object.keys(input).flatMap(k => { input[k].name = k return [{...input[k], name: `${base}${k}`}, ...topicsToArray(input[k].subtopics, `${base}${input[k].name}`)] }) @@ -141,6 +139,8 @@ export class Plugin implements IPlugin { hasManifest = false + isRoot = false + private _commandsDir!: string | undefined private flexibleTaxonomy!: boolean @@ -155,6 +155,7 @@ export class Plugin implements IPlugin { public async load(): Promise { this.type = this.options.type || 'core' this.tag = this.options.tag + this.isRoot = this.options.isRoot ?? false 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 @@ -304,7 +305,7 @@ export class Plugin implements IPlugin { version: this.version, commands: (await Promise.all(this.commandIDs.map(async id => { try { - const cached = await toCached(await this.findCommand(id, {must: true}), this, respectNoCacheDefault) + const cached = await cacheCommand(await this.findCommand(id, {must: true}), this, respectNoCacheDefault) if (this.flexibleTaxonomy) { const permutations = getCommandIdPermutations(id) const aliasPermutations = cached.aliases.flatMap(a => getCommandIdPermutations(a)) @@ -313,7 +314,7 @@ export class Plugin implements IPlugin { return [id, cached] } catch (error: any) { - const scope = 'toCached' + const scope = 'cacheCommand' if (Boolean(errorOnManifestCreate) === false) this.warn(error, scope) else throw this.addErrorScope(error, scope) } diff --git a/src/config/ts-node.ts b/src/config/ts-node.ts index 2addcf5a9..80b82d47e 100644 --- a/src/config/ts-node.ts +++ b/src/config/ts-node.ts @@ -2,7 +2,6 @@ import * as TSNode from 'ts-node' 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' @@ -13,6 +12,11 @@ const debug = Debug('ts-node') export const TS_CONFIGS: Record = {} const REGISTERED = new Set() +/** + * Cache the root plugin so that we can reference it later when determining if + * we should skip ts-node registration for an ESM plugin. + */ +let ROOT_PLUGIN: Plugin | undefined function loadTSConfig(root: string): TSConfig | undefined { if (TS_CONFIGS[root]) return TS_CONFIGS[root] @@ -123,6 +127,8 @@ function registerTSNode(root: string): TSConfig | undefined { 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 (plugin?.isRoot) ROOT_PLUGIN = plugin + if (!orig) return orig orig = orig.startsWith(root) ? orig : join(root, orig) @@ -146,8 +152,8 @@ export function tsPath(root: string, orig: string | undefined, plugin?: Plugin): * 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 ((isProduction || ROOT_PLUGIN?.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: ${ROOT_PLUGIN?.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.`) diff --git a/src/config/util.ts b/src/config/util.ts index 182b336a5..482ce4554 100644 --- a/src/config/util.ts +++ b/src/config/util.ts @@ -1,17 +1,5 @@ const debug = require('debug') -export function flatMap(arr: T[], fn: (i: T) => U[]): U[] { - return arr.reduce((arr, i) => [...arr, ...fn(i)], [] as U[]) -} - -export function mapValues, TResult>(obj: {[P in keyof T]: T[P]}, fn: (i: T[keyof T], k: keyof T) => TResult): {[P in keyof T]: TResult} { - return Object.entries(obj) - .reduce((o, [k, v]) => { - o[k] = fn(v as any, k as any) - return o - }, {} as any) -} - export function resolvePackage(id: string, paths: { paths: string[] }): string { return require.resolve(id, paths) } diff --git a/src/errors/handle.ts b/src/errors/handle.ts index 138e73d8e..ab1ff2c2c 100644 --- a/src/errors/handle.ts +++ b/src/errors/handle.ts @@ -2,7 +2,7 @@ /* eslint-disable unicorn/no-process-exit */ import {OclifError, PrettyPrintableError} from '../interfaces' import {CLIError} from './errors/cli' -import {ExitError} from '.' +import {ExitError} from './errors/exit' import clean from 'clean-stack' import {config} from './config' import prettyPrint from './errors/pretty-print' diff --git a/src/flags.ts b/src/flags.ts index 61df7db68..699dde32e 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -9,14 +9,14 @@ type NotArray = T extends Array ? never: T; export function custom( defaults: Partial> & { - multiple: true + multiple: true; } & ( {required: true} | {default: OptionFlag['default']} ), ): FlagDefinition export function custom( - defaults: Partial> & { + defaults: Partial, P>> & { multiple?: false | undefined; } & ( {required: true} | {default: OptionFlag, P>['default']} @@ -24,7 +24,7 @@ export function custom( ): FlagDefinition export function custom( - defaults: Partial> & { + defaults: Partial, P>> & { default?: OptionFlag, P>['default'] | undefined; multiple?: false | undefined; required?: false | undefined; diff --git a/src/help/index.ts b/src/help/index.ts index 871d6c2f5..1a8d1f076 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -5,15 +5,15 @@ import {Command} from '../command' import {CommandHelp} from './command' import {HelpFormatter} from './formatter' import RootHelp from './root' +import {cacheDefaultValue} from '../util/cache-default-value' import {error} from '../errors' import {format} from 'node:util' +import {load} from '../module-loader' import {stdout} from '../cli-ux/stream' -import {toCached} from '../config/config' - -import stripAnsi = require('strip-ansi') +import stripAnsi from 'strip-ansi' export {CommandHelp} from './command' -export {standardizeIDFromArgv, loadHelpClass, getHelpFlagAdditions, normalizeArgv} from './util' +export {standardizeIDFromArgv, getHelpFlagAdditions, normalizeArgv} from './util' function getHelpSubject(args: string[], config: Interfaces.Config): string | undefined { // for each help flag that starts with '--' create a new flag with same name sans '--' @@ -108,8 +108,14 @@ export class Help extends HelpBase { const command = this.config.findCommand(subject) if (command) { if (command.hasDynamicHelp && command.pluginType !== 'jit') { - const dynamicCommand = await toCached(await command.load()) - await this.showCommandHelp(dynamicCommand) + const loaded = await command.load() + for (const [name, flag] of Object.entries(loaded.flags)) { + if (flag.type === 'boolean' || !command.flags[name].hasDynamicHelp) continue + // eslint-disable-next-line no-await-in-loop + command.flags[name].default = await cacheDefaultValue(flag, false) + } + + await this.showCommandHelp(command) } else { await this.showCommandHelp(command) } @@ -327,3 +333,27 @@ export class Help extends HelpBase { stdout.write(format.apply(this, args) + '\n') } } + +interface HelpBaseDerived { + new(config: Interfaces.Config, opts?: Partial): HelpBase; +} + +function extractClass(exported: any): HelpBaseDerived { + return exported && exported.default ? exported.default : exported +} + +export async function loadHelpClass(config: Interfaces.Config): Promise { + const {pjson} = config + const configuredClass = pjson.oclif?.helpClass + + if (configuredClass) { + try { + 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}`) + } + } + + return Help +} diff --git a/src/help/root.ts b/src/help/root.ts index 6aaa587f2..1d1ed0d69 100644 --- a/src/help/root.ts +++ b/src/help/root.ts @@ -1,7 +1,7 @@ import * as Interfaces from '../interfaces' import {HelpFormatter} from './formatter' import {compact} from '../util' -import stripAnsi = require('strip-ansi') +import stripAnsi from '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 278c26050..71c07573a 100644 --- a/src/help/util.ts +++ b/src/help/util.ts @@ -1,32 +1,6 @@ import * as ejs from 'ejs' -import {Deprecation, HelpOptions, Config as IConfig} from '../interfaces' -import {Help, HelpBase} from '.' +import {Deprecation, Config as IConfig} from '../interfaces' import {collectUsableIds} from '../config/util' -import {load} from '../module-loader' - -interface HelpBaseDerived { - new(config: IConfig, opts?: Partial): HelpBase; -} - -function extractClass(exported: any): HelpBaseDerived { - return exported && exported.default ? exported.default : exported -} - -export async function loadHelpClass(config: IConfig): Promise { - const {pjson} = config - const configuredClass = pjson && pjson.oclif && pjson.oclif.helpClass - - if (configuredClass) { - try { - 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}`) - } - } - - return Help -} export function template(context: any): (t: string) => string { function render(t: string): string { diff --git a/src/index.ts b/src/index.ts index 4eea78bfa..6093911a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,12 +14,12 @@ checkCWD() export * as Args from './args' export * as Errors from './errors' -export * as Flags from './flags' export * as Interfaces from './interfaces' +export * as Flags from './flags' 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 {Config, Plugin, tsPath} from './config' export {HelpSection, HelpSectionRenderer, HelpSectionKeyValueTable} from './help/formatter' export {Settings, settings} from './settings' export {stdout, stderr} from './cli-ux/stream' diff --git a/src/interfaces/parser.ts b/src/interfaces/parser.ts index c95bfec7a..a201323d4 100644 --- a/src/interfaces/parser.ts +++ b/src/interfaces/parser.ts @@ -161,12 +161,6 @@ export type FlagProps = { * Emit deprecation warning when a flag alias is provided */ deprecateAliases?: boolean - /** - * Delimiter to separate the values for a multiple value flag. - * Only respected if multiple is set to true. Default behavior is to - * separate on spaces. - */ - delimiter?: ',', /** * If true, the value returned by defaultHelp will not be cached in the oclif.manifest.json. * This is helpful if the default value contains sensitive data that shouldn't be published to npm. @@ -210,6 +204,12 @@ export type OptionFlagProps = FlagProps & { helpValue?: string; options?: readonly string[]; multiple?: boolean; + /** + * Delimiter to separate the values for a multiple value flag. + * Only respected if multiple is set to true. Default behavior is to + * separate on spaces. + */ + delimiter?: ',', } export type FlagParserContext = Command & {token: FlagToken} @@ -243,7 +243,7 @@ export type BooleanFlag = FlagProps & BooleanFlagProps & { } export type OptionFlag = FlagProps & OptionFlagProps & { - parse: FlagParser + parse: FlagParser defaultHelp?: FlagDefaultHelp; input: string[]; default?: FlagDefault; @@ -361,6 +361,7 @@ export type Flag = BooleanFlag | OptionFlag export type Input = { flags?: FlagInput; baseFlags?: FlagInput; + enableJsonFlag?: true | false; args?: ArgInput; strict?: boolean; context?: ParserContext; diff --git a/src/interfaces/plugin.ts b/src/interfaces/plugin.ts index 50207bcba..772fbc10c 100644 --- a/src/interfaces/plugin.ts +++ b/src/interfaces/plugin.ts @@ -13,6 +13,7 @@ export interface PluginOptions { parent?: Plugin; children?: Plugin[]; flexibleTaxonomy?: boolean; + isRoot?: boolean; } export interface Options extends PluginOptions { @@ -73,6 +74,11 @@ export interface Plugin { */ valid: boolean; + /** + * True if the plugin is the root plugin. + */ + isRoot: boolean; + commands: Command.Loadable[]; hooks: { [k: string]: string[] }; readonly commandIDs: string[]; diff --git a/src/module-loader.ts b/src/module-loader.ts index c3105d027..ce715ed8a 100644 --- a/src/module-loader.ts +++ b/src/module-loader.ts @@ -4,7 +4,7 @@ import {extname, join, sep} from 'node:path' import {Command} from './command' import {ModuleLoadError} from './errors' import {pathToFileURL} from 'node:url' -import {tsPath} from './config' +import {tsPath} from './config/ts-node' const getPackageType = require('get-package-type') diff --git a/src/parser/parse.ts b/src/parser/parse.ts index 6376ec80c..fffcd68a7 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -250,7 +250,7 @@ export class Parser parseFlagOrThrowError( + valueFunction: async i => parseFlagOrThrowError( last(i.tokens)?.input !== `--no-${i.inputFlag.name}`, i.inputFlag.flag, this.context, @@ -262,8 +262,9 @@ export class Parser (await Promise.all( - ((i.tokens ?? []).flatMap(token => (token.input as string).split(i.inputFlag.flag.delimiter as string))) + ...fws, + valueFunction: async i => (await Promise.all( + ((i.tokens ?? []).flatMap(token => token.input.split((i.inputFlag.flag as OptionFlag).delimiter ?? ','))) // trim, and remove surrounding doubleQuotes (which would hav been needed if the elements contain spaces) .map(v => v.trim().replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1')) .map(async v => parseFlagOrThrowError(v, i.inputFlag.flag, this.context, {...last(i.tokens) as FlagToken, input: v})), diff --git a/src/util/aggregate-flags.ts b/src/util/aggregate-flags.ts new file mode 100644 index 000000000..bd13b9fd7 --- /dev/null +++ b/src/util/aggregate-flags.ts @@ -0,0 +1,18 @@ +import {FlagInput, FlagOutput} from '../interfaces/parser' +import {boolean} from '../flags' + +const json = boolean({ + description: 'Format output as json.', + helpGroup: 'GLOBAL', +}) + +export function aggregateFlags( + flags: FlagInput | undefined, + baseFlags: FlagInput | undefined, + enableJsonFlag: boolean | undefined, +): FlagInput { + const combinedFlags = {...baseFlags, ...flags} + return (enableJsonFlag + ? {json, ...combinedFlags} + : combinedFlags) as FlagInput +} diff --git a/src/util/cache-command.ts b/src/util/cache-command.ts new file mode 100644 index 000000000..176f98516 --- /dev/null +++ b/src/util/cache-command.ts @@ -0,0 +1,111 @@ +import {ArgInput, FlagInput} from '../interfaces/parser' +import {ensureArgObject, pickBy} from './index' +import {Command} from '../command' +import {Plugin as IPlugin} from '../interfaces/plugin' +import {aggregateFlags} from './aggregate-flags' +import {cacheDefaultValue} from './cache-default-value' + +// In order to collect static properties up the inheritance chain, we need to recursively +// access the prototypes until there's nothing left. This allows us to combine baseFlags +// and flags as well as add in the json flag if enableJsonFlag is enabled. +function mergePrototype(result: Command.Class, cmd: Command.Class): Command.Class { + const proto = Object.getPrototypeOf(cmd) + const filteredProto = pickBy(proto, v => v !== undefined) as Command.Class + return Object.keys(proto).length > 0 ? mergePrototype({...filteredProto, ...result} as Command.Class, proto) : result +} + +async function cacheFlags(cmdFlags: FlagInput, respectNoCacheDefault: boolean): Promise> { + const promises = Object.entries(cmdFlags).map(async ([name, flag]) => ([name, { + name, + char: flag.char, + summary: flag.summary, + hidden: flag.hidden, + required: flag.required, + helpLabel: flag.helpLabel, + helpGroup: flag.helpGroup, + description: flag.description, + dependsOn: flag.dependsOn, + relationships: flag.relationships, + exclusive: flag.exclusive, + deprecated: flag.deprecated, + deprecateAliases: flag.deprecateAliases, + aliases: flag.aliases, + charAliases: flag.charAliases, + noCacheDefault: flag.noCacheDefault, + ...flag.type === 'boolean' ? { + allowNo: flag.allowNo, + type: flag.type, + } : { + type: flag.type, + helpValue: flag.helpValue, + multiple: flag.multiple, + options: flag.options, + delimiter: flag.delimiter, + default: await cacheDefaultValue(flag, respectNoCacheDefault), + hasDynamicHelp: typeof flag.defaultHelp === 'function', + }, + }])) + return Object.fromEntries(await Promise.all(promises)) +} + +async function cacheArgs(cmdArgs: ArgInput, respectNoCacheDefault: boolean): Promise> { + const promises = Object.entries(cmdArgs).map(async ([name, arg]) => ([name, { + name, + description: arg.description, + required: arg.required, + options: arg.options, + default: await cacheDefaultValue(arg, respectNoCacheDefault), + hidden: arg.hidden, + noCacheDefault: arg.noCacheDefault, + }])) + return Object.fromEntries(await Promise.all(promises)) +} + +export async function cacheCommand(uncachedCmd: Command.Class, plugin?: IPlugin, respectNoCacheDefault = false): Promise { + const cmd = mergePrototype(uncachedCmd, uncachedCmd) + + const flags = await cacheFlags( + aggregateFlags(cmd.flags, cmd.baseFlags, cmd.enableJsonFlag), + respectNoCacheDefault, + ) + const args = await cacheArgs(ensureArgObject(cmd.args), respectNoCacheDefault) + + const stdProperties = { + id: cmd.id, + summary: cmd.summary, + description: cmd.description, + strict: cmd.strict, + usage: cmd.usage, + pluginName: plugin && plugin.name, + pluginAlias: plugin && plugin.alias, + pluginType: plugin && plugin.type, + hidden: cmd.hidden, + state: cmd.state, + aliases: cmd.aliases || [], + examples: cmd.examples || (cmd as any).example, + deprecationOptions: cmd.deprecationOptions, + deprecateAliases: cmd.deprecateAliases, + flags, + args, + hasDynamicHelp: Object.values(flags).some(f => f.hasDynamicHelp), + } + + // do not include these properties in manifest + const ignoreCommandProperties = [ + 'plugin', + '_flags', + '_enableJsonFlag', + '_globalFlags', + '_baseFlags', + 'baseFlags', + '_--', + '_base', + ] + + // Add in any additional properties that are not standard command properties. + const stdKeysAndIgnored = new Set([...Object.keys(stdProperties), ...ignoreCommandProperties]) + const keysToAdd = Object.keys(cmd).filter(property => !stdKeysAndIgnored.has(property)) + const additionalProperties = Object.fromEntries(keysToAdd.map(key => [key, (cmd as any)[key]])) + + return {...stdProperties, ...additionalProperties} +} diff --git a/src/util/cache-default-value.ts b/src/util/cache-default-value.ts new file mode 100644 index 000000000..990eb5179 --- /dev/null +++ b/src/util/cache-default-value.ts @@ -0,0 +1,23 @@ +import {Arg, OptionFlag} from '../interfaces/parser' + +// when no manifest exists, the default is calculated. This may throw, so we need to catch it +export const cacheDefaultValue = async (flagOrArg: OptionFlag | Arg, respectNoCacheDefault: boolean) => { + if (respectNoCacheDefault && flagOrArg.noCacheDefault) return + // Prefer the defaultHelp function (returns a friendly string for complex types) + if (typeof flagOrArg.defaultHelp === 'function') { + try { + return await flagOrArg.defaultHelp({options: flagOrArg, flags: {}}) + } catch { + return + } + } + + // if not specified, try the default function + if (typeof flagOrArg.default === 'function') { + try { + return await flagOrArg.default({options: flagOrArg, flags: {}}) + } catch {} + } else { + return flagOrArg.default + } +} diff --git a/src/util.ts b/src/util/index.ts similarity index 88% rename from src/util.ts rename to src/util/index.ts index a5c0eab5b..df4a90ec4 100644 --- a/src/util.ts +++ b/src/util/index.ts @@ -1,9 +1,9 @@ -import {access, stat} from 'node:fs/promises' +import {access, readFile, 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 {ArgInput} from '../interfaces/parser' +import {Command} from '../command' import {join} from 'node:path' +import {readFileSync} from 'node:fs' const debug = require('debug') @@ -169,18 +169,10 @@ export function getPlatform(): NodeJS.Platform { return platform() } -export function readJson(path: string): Promise { +export async 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) - } - }) - }) + const contents = await readFile(path, 'utf8') + return JSON.parse(contents) as T } export function readJsonSync(path: string, parse: false): string @@ -189,3 +181,11 @@ export function readJsonSync(path: string, parse = true): T | strin const contents = readFileSync(path, 'utf8') return parse ? JSON.parse(contents) as T : contents } + +export function mapValues, TResult>(obj: {[P in keyof T]: T[P]}, fn: (i: T[keyof T], k: keyof T) => TResult): {[P in keyof T]: TResult} { + return Object.entries(obj) + .reduce((o, [k, v]) => { + o[k] = fn(v as any, k as any) + return o + }, {} as any) +} diff --git a/test/command/command.test.ts b/test/command/command.test.ts index ddbad21bb..ee8991892 100644 --- a/test/command/command.test.ts +++ b/test/command/command.test.ts @@ -1,6 +1,6 @@ import {expect, fancy} from 'fancy-test' // import path = require('path') -import {Args, Command as Base, Flags, toCached} from '../../src' +import {Command as Base, Flags} from '../../src' // import {TestHelpClassConfig} from './helpers/test-help-in-src/src/test-help-plugin' // const pjson = require('../package.json') @@ -69,208 +69,6 @@ describe('command', () => { .catch(/EEXIT: 0/) .it('exits with 0') - describe('toCached', () => { - 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', - }), - } - } - - const c = await toCached(C, undefined, false) - - expect(c).to.deep.equal({ - id: 'foo:bar', - type: 'mytype', - hidden: true, - pluginName: undefined, - pluginAlias: undefined, - pluginType: undefined, - state: undefined, - description: 'test command', - aliases: ['alias1', 'alias2'], - title: 'cmd title', - usage: ['$ usage'], - examples: undefined, - deprecationOptions: undefined, - deprecateAliases: undefined, - summary: undefined, - strict: true, - flags: { - flaga: { - aliases: undefined, - char: undefined, - charAliases: undefined, - description: undefined, - dependsOn: undefined, - deprecateAliases: undefined, - deprecated: undefined, - exclusive: undefined, - helpGroup: undefined, - helpLabel: undefined, - summary: undefined, - name: 'flaga', - hidden: undefined, - required: undefined, - relationships: undefined, - allowNo: false, - type: 'boolean', - delimiter: undefined, - noCacheDefault: undefined, - }, - flagb: { - aliases: undefined, - char: 'b', - charAliases: undefined, - description: 'flagb desc', - dependsOn: undefined, - deprecateAliases: undefined, - deprecated: undefined, - exclusive: undefined, - helpGroup: undefined, - helpLabel: undefined, - summary: undefined, - name: 'flagb', - hidden: true, - required: false, - multiple: false, - relationships: undefined, - type: 'option', - helpValue: undefined, - default: 'a', - options: ['a', 'b'], - delimiter: undefined, - noCacheDefault: undefined, - }, - flagc: { - aliases: undefined, - char: 'c', - charAliases: undefined, - default: 2, - delimiter: undefined, - dependsOn: undefined, - deprecateAliases: undefined, - deprecated: undefined, - description: 'flagc desc', - exclusive: undefined, - helpGroup: undefined, - helpLabel: undefined, - helpValue: undefined, - hidden: undefined, - multiple: false, - name: 'flagc', - options: [ - 'a', - 'b', - ], - relationships: undefined, - required: false, - summary: undefined, - type: 'option', - noCacheDefault: undefined, - }, - - }, - args: { - arg1: { - description: 'arg1 desc', - name: 'arg1', - hidden: false, - required: true, - options: ['af', 'b'], - default: 'a', - noCacheDefault: undefined, - }, - }, - }) - }) - .it('converts to cached with everything set') - - fancy - // .skip() - .do(async () => { - // const c = class extends Command { - // }.convertToCached() - // expect(await c.load()).to.have.property('run') - // delete c.load - // expect(c).to.deep.equal({ - // _base: `@oclif/command@${pjson.version}`, - // id: undefined, - // type: undefined, - // hidden: undefined, - // pluginName: undefined, - // description: 'test command', - // aliases: [], - // title: undefined, - // usage: undefined, - // flags: {}, - // args: [], - // }) - }) - - .it('adds plugin name') - - fancy - // .skip() - // .do(async () => { - // const c = class extends Command { - // }.convertToCached({pluginName: 'myplugin'}) - // expect(await c.load()).to.have.property('run') - // delete c.load - // expect(c).to.deep.equal({ - // _base: `@oclif/command@${pjson.version}`, - // type: undefined, - // id: undefined, - // hidden: undefined, - // pluginName: 'myplugin', - // description: 'test command', - // aliases: [], - // title: undefined, - // usage: undefined, - // flags: {}, - // args: [], - // }) - // }) - .it('converts to cached with nothing set') - }) - describe('parse', () => { fancy .stdout() @@ -553,7 +351,6 @@ describe('command', () => { .do(async () => { class CMD extends Command { static enableJsonFlag = true - static '--' = true async run() { const {flags} = await cmd.parse(CMD, ['--json']) @@ -570,16 +367,12 @@ describe('command', () => { .stdout() .do(async () => { class CMD extends Command { - // static initialization block is required whenever using ES2022 - static { - this.enableJsonFlag = true - this['--'] = true - } + static enableJsonFlag = 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') + // expect(this.passThroughEnabled).to.equal(true, 'pass through should be true') } } @@ -593,7 +386,6 @@ describe('command', () => { .do(async () => { class CMD extends Command { static enableJsonFlag = true - static '--' = true async run() { const {flags} = await cmd.parse(CMD, ['--foo', '--json']) @@ -611,12 +403,11 @@ describe('command', () => { .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') + // expect(this.passThroughEnabled).to.equal(true, 'pass through should be true') } } @@ -630,7 +421,6 @@ describe('command', () => { .do(async () => { class CMD extends Command { static enableJsonFlag = true - static '--' = true async run() {} } diff --git a/test/config/config.flexible.test.ts b/test/config/config.flexible.test.ts index 829b00463..6b451da95 100644 --- a/test/config/config.flexible.test.ts +++ b/test/config/config.flexible.test.ts @@ -101,6 +101,7 @@ describe('Config with flexible taxonomy', () => { tag: 'tag', moduleType: 'commonjs', hasManifest: false, + isRoot: false, } const pluginB: IPlugin = { @@ -121,6 +122,7 @@ describe('Config with flexible taxonomy', () => { tag: 'tag', moduleType: 'commonjs', hasManifest: false, + isRoot: false, } const plugins = new Map().set(pluginA.name, pluginA).set(pluginB.name, pluginB) diff --git a/test/config/config.test.ts b/test/config/config.test.ts index cd5aa9971..1b821d793 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -282,6 +282,7 @@ describe('Config', () => { tag: 'tag', moduleType: 'commonjs', hasManifest: false, + isRoot: false, } const pluginB: IPlugin = { @@ -302,6 +303,7 @@ describe('Config', () => { tag: 'tag', moduleType: 'commonjs', hasManifest: false, + isRoot: false, } const plugins = new Map().set(pluginA.name, pluginA).set(pluginB.name, pluginB) let test = fancy diff --git a/test/help/help-test-utils.ts b/test/help/help-test-utils.ts index cf94558b2..76db5c203 100644 --- a/test/help/help-test-utils.ts +++ b/test/help/help-test-utils.ts @@ -2,8 +2,9 @@ import {Command} from '../../src/command' import stripAnsi = require('strip-ansi') -import {Interfaces, toCached} from '../../src' +import {Interfaces} from '../../src' import {CommandHelp, Help} from '../../src/help' +import {cacheCommand} from '../../src/util/cache-command' export class TestCommandHelp extends CommandHelp { protected sections() { @@ -47,7 +48,7 @@ export class TestHelp extends Help { export const commandHelp = (command?: any) => ({ async run(ctx: {help: TestHelp; commandHelp: string; expectation: string}) { - const cached = await toCached(command!, {} as any, false) + const cached = await cacheCommand(command!, {} as any, false) const help = ctx.help.formatCommand(cached) if (process.env.TEST_OUTPUT === '1') { console.log(help) diff --git a/test/help/util.test.ts b/test/help/util.test.ts index c25aa0b3f..cd02b70ce 100644 --- a/test/help/util.test.ts +++ b/test/help/util.test.ts @@ -4,6 +4,7 @@ import {test} from '@oclif/test' import {loadHelpClass, standardizeIDFromArgv} from '../../src/help' import configuredHelpClass from './_test-help-class' import {expect} from 'chai' +import * as util from '../../src/config/util' describe('util', () => { let config: Interfaces.Config @@ -52,7 +53,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return standardized id when topic separator is a space', () => { config.topicSeparator = ' ' const actual = standardizeIDFromArgv(['foo', 'bar', '--baz'], config) @@ -60,7 +61,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return standardized id when topic separator is a space and command is misspelled', () => { config.topicSeparator = ' ' const actual = standardizeIDFromArgv(['foo', 'ba', '--baz'], config) @@ -68,7 +69,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return standardized id when topic separator is a space and has args and command is misspelled', () => { config.topicSeparator = ' ' // @ts-expect-error private member @@ -81,7 +82,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return standardized id when topic separator is a space and has args', () => { config.topicSeparator = ' ' // @ts-expect-error private member @@ -94,7 +95,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return standardized id when topic separator is a space and has variable arguments', () => { config.topicSeparator = ' ' // @ts-expect-error private member @@ -107,7 +108,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return standardized id when topic separator is a space and has variable arguments and flags', () => { config.topicSeparator = ' ' // @ts-expect-error private member @@ -120,7 +121,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return full id when topic separator is a space and does not have arguments', () => { config.topicSeparator = ' ' // @ts-expect-error private member @@ -134,7 +135,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return standardized id when topic separator is a space and has arg with value', () => { config.topicSeparator = ' ' // @ts-expect-error private member @@ -144,7 +145,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return standardized id when topic separator is a space and has variable args with value', () => { config.topicSeparator = ' ' // @ts-expect-error private member @@ -154,7 +155,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return standardized id when topic separator is a space and has flags', () => { config.topicSeparator = ' ' // @ts-expect-error private member @@ -164,7 +165,7 @@ describe('util', () => { }) test - .stub(Config.prototype, 'collectUsableIds', () => ['foo', 'foo:bar']) + .stub(util, 'collectUsableIds', stub => stub.returns(new Set(['foo', 'foo:bar']))) .it('should return standardized id when topic separator is a space and has flags, arg, and arg with value', () => { config.topicSeparator = ' ' // @ts-expect-error private member diff --git a/test/util.test.ts b/test/util.test.ts deleted file mode 100644 index 4820f23a6..000000000 --- a/test/util.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {expect} from 'chai' -import {capitalize, ensureArgObject, last, maxBy, sumBy} from '../src/util' - -describe('capitalize', () => { - it('capitalizes the string', () => { - expect(capitalize('dominik')).to.equal('Dominik') - }) - it('works with an empty string', () => { - expect(capitalize('')).to.equal('') - }) -}) - -type Item = { x: number } - -describe('sumBy', () => { - it('returns zero for empty array', () => { - const arr: Item[] = [] - expect(sumBy(arr, i => i.x)).to.equal(0) - }) - it('returns sum for non-empty array', () => { - const arr: Item[] = [{x: 1}, {x: 2}, {x: 3}] - expect(sumBy(arr, i => i.x)).to.equal(6) - }) -}) - -describe('maxBy', () => { - it('returns undefined for empty array', () => { - const arr: Item[] = [] - expect(maxBy(arr, i => i.x)).to.be.undefined - }) - it('returns max value in the array', () => { - const arr: Item[] = [{x: 1}, {x: 3}, {x: 2}] - expect(maxBy(arr, i => i.x)).to.equal(arr[1]) - }) -}) - -describe('last', () => { - it('returns undefined for empty array', () => { - expect(last([])).to.be.undefined - }) - it('returns undefined for undefined', () => { - expect(last()).to.be.undefined - }) - it('returns last value in the array', () => { - const arr: Item[] = [{x: 1}, {x: 3}, {x: 2}] - expect(last(arr)).to.equal(arr[2]) - }) - it('returns only item in array', () => { - expect(last([6])).to.equal(6) - }) -}) - -describe('ensureArgObject', () => { - it('should convert array of arguments to an object', () => { - const args = [ - {name: 'foo', description: 'foo desc', required: true}, - {name: 'bar', description: 'bar desc'}, - ] - const expected = {foo: args[0], bar: args[1]} - expect(ensureArgObject(args)).to.deep.equal(expected) - }) - - it('should do nothing to an arguments object', () => { - const args = { - foo: {name: 'foo', description: 'foo desc', required: true}, - bar: {name: 'bar', description: 'bar desc'}, - } - expect(ensureArgObject(args)).to.deep.equal(args) - }) -}) diff --git a/test/util/cache-command.test.ts b/test/util/cache-command.test.ts new file mode 100644 index 000000000..b46a878fe --- /dev/null +++ b/test/util/cache-command.test.ts @@ -0,0 +1,298 @@ +import {expect} from 'chai' +import {Args, Command, Flags} from '../../src/index' +import {cacheCommand} from '../../src/util/cache-command' + +describe('cacheCommand', () => { + it('should return a cached command with every thing set', 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 + 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'], + default: async context => (context.options.min ?? 1) + 1, + }), + } + + static args = { + arg1: Args.string({ + description: 'arg1 desc', + required: true, + hidden: false, + options: ['af', 'b'], + default: async () => 'a', + }), + } + + public async run(): Promise {} + } + const c = await cacheCommand(C, undefined, false) + expect(c).to.deep.equal({ + id: 'foo:bar', + type: 'mytype', + hidden: true, + pluginName: undefined, + pluginAlias: undefined, + pluginType: undefined, + state: undefined, + description: 'test command', + aliases: ['alias1', 'alias2'], + title: 'cmd title', + usage: ['$ usage'], + examples: undefined, + deprecationOptions: undefined, + deprecateAliases: undefined, + summary: undefined, + strict: true, + enableJsonFlag: false, + hasDynamicHelp: false, + flags: { + flaga: { + aliases: undefined, + char: undefined, + charAliases: undefined, + description: undefined, + dependsOn: undefined, + deprecateAliases: undefined, + deprecated: undefined, + exclusive: undefined, + helpGroup: undefined, + helpLabel: undefined, + summary: undefined, + name: 'flaga', + hidden: undefined, + required: undefined, + relationships: undefined, + allowNo: false, + type: 'boolean', + noCacheDefault: undefined, + }, + flagb: { + aliases: undefined, + char: 'b', + charAliases: undefined, + description: 'flagb desc', + dependsOn: undefined, + deprecateAliases: undefined, + deprecated: undefined, + exclusive: undefined, + helpGroup: undefined, + helpLabel: undefined, + summary: undefined, + name: 'flagb', + hidden: true, + required: false, + multiple: false, + relationships: undefined, + type: 'option', + helpValue: undefined, + default: 'a', + options: ['a', 'b'], + delimiter: undefined, + noCacheDefault: undefined, + hasDynamicHelp: false, + }, + flagc: { + aliases: undefined, + char: 'c', + charAliases: undefined, + default: 2, + delimiter: undefined, + dependsOn: undefined, + deprecateAliases: undefined, + deprecated: undefined, + description: 'flagc desc', + exclusive: undefined, + helpGroup: undefined, + helpLabel: undefined, + helpValue: undefined, + hidden: undefined, + multiple: false, + name: 'flagc', + options: [ + 'a', + 'b', + ], + relationships: undefined, + required: false, + summary: undefined, + type: 'option', + noCacheDefault: undefined, + hasDynamicHelp: false, + }, + + }, + args: { + arg1: { + description: 'arg1 desc', + name: 'arg1', + hidden: false, + required: true, + options: ['af', 'b'], + default: 'a', + noCacheDefault: undefined, + }, + }, + }) + }) + + it('should return a cached command with multiple Command classes in inheritance chain', async () => { + class Base extends Command { + public static enableJsonFlag = true + public static aliases = ['base'] + public static state = 'beta' + public static summary = 'base summary' + public static baseFlags = { + parentFlag: Flags.boolean(), + } + + public async run(): Promise {} + } + + class Child extends Base { + static id = 'command' + public static summary = 'child summary' + static flags = { + childFlag: Flags.boolean(), + } + + public async run(): Promise {} + } + + const cached = await cacheCommand(Child, undefined, false) + + expect(cached).to.deep.equal({ + id: 'command', + summary: 'child summary', + description: undefined, + strict: true, + usage: undefined, + pluginName: undefined, + pluginAlias: undefined, + pluginType: undefined, + hidden: undefined, + state: 'beta', + aliases: ['base'], + examples: undefined, + deprecationOptions: undefined, + deprecateAliases: undefined, + flags: { + json: { + name: 'json', + type: 'boolean', + char: undefined, + summary: undefined, + description: 'Format output as json.', + hidden: undefined, + required: undefined, + helpLabel: undefined, + helpGroup: 'GLOBAL', + allowNo: false, + dependsOn: undefined, + relationships: undefined, + exclusive: undefined, + deprecated: undefined, + deprecateAliases: undefined, + aliases: undefined, + charAliases: undefined, + noCacheDefault: undefined, + }, + childFlag: { + name: 'childFlag', + type: 'boolean', + char: undefined, + summary: undefined, + description: undefined, + hidden: undefined, + required: undefined, + helpLabel: undefined, + helpGroup: undefined, + allowNo: false, + dependsOn: undefined, + relationships: undefined, + exclusive: undefined, + deprecated: undefined, + deprecateAliases: undefined, + aliases: undefined, + charAliases: undefined, + noCacheDefault: undefined, + }, + parentFlag: { + name: 'parentFlag', + type: 'boolean', + char: undefined, + summary: undefined, + description: undefined, + hidden: undefined, + required: undefined, + helpLabel: undefined, + helpGroup: undefined, + allowNo: false, + dependsOn: undefined, + relationships: undefined, + exclusive: undefined, + deprecated: undefined, + deprecateAliases: undefined, + aliases: undefined, + charAliases: undefined, + noCacheDefault: undefined, + }, + }, + args: {}, + hasDynamicHelp: false, + enableJsonFlag: true, + }) + }) + + it('should set dynamicHelp to true if defaultHelp is a function', async () => { + class C extends Command { + static id = 'foo:bar' + static flags = { + flaga: Flags.boolean(), + flagb: Flags.string({ + defaultHelp: async () => 'foo', + }), + } + + public async run(): Promise {} + } + const c = await cacheCommand(C, undefined, false) + expect(c.hasDynamicHelp).to.be.true + expect(c.flags.flagb.hasDynamicHelp).to.be.true + }) + + it('should add additional command properties', async () => { + class C extends Command { + static id = 'foo:bar' + static flags = { + flaga: Flags.boolean(), + } + + static envVars = ['FOO_BAR'] + + public async run(): Promise {} + } + + const c = await cacheCommand(C, undefined, false) + expect(c.envVars).to.deep.equal(['FOO_BAR']) + }) +}) diff --git a/test/util/cache-default-value.test.ts b/test/util/cache-default-value.test.ts new file mode 100644 index 000000000..9f7a2b6f9 --- /dev/null +++ b/test/util/cache-default-value.test.ts @@ -0,0 +1,42 @@ +import {expect} from 'chai' +import {cacheDefaultValue} from '../../src/util/cache-default-value' + +describe('cacheDefaultValue', () => { + it('should do nothing if noCacheDefault is true', async () => { + const flag = {noCacheDefault: true} + const result = await cacheDefaultValue(flag as any, true) + expect(result).to.be.undefined + }) + + it('should do nothing if respectNoCacheDefault is true', async () => { + const result = await cacheDefaultValue({} as any, true) + expect(result).to.be.undefined + }) + + it('should return the result of defaultHelp if it exists', async () => { + const flag = {defaultHelp: async () => 'foo'} + const result = await cacheDefaultValue(flag as any, false) + expect(result).to.equal('foo') + }) + + it('should return undefined if defaultHelp throws', async () => { + const flag = {async defaultHelp() { + throw new Error('foo') + }} + const result = await cacheDefaultValue(flag as any, false) + expect(result).to.be.undefined + }) + + it('should return the result of the default if it\'s a function', async () => { + const flag = {default: async () => 'foo'} + const result = await cacheDefaultValue(flag as any, false) + expect(result).to.equal('foo') + }) + + it('should return the result of the default if it\'s a simple value', async () => { + const flag = {default: 'foo'} + const result = await cacheDefaultValue(flag as any, false) + expect(result).to.equal('foo') + }) +}) + diff --git a/test/util/index.test.ts b/test/util/index.test.ts new file mode 100644 index 000000000..815e45f39 --- /dev/null +++ b/test/util/index.test.ts @@ -0,0 +1,141 @@ +import {expect} from 'chai' +import {homedir} from 'node:os' +import {capitalize, castArray, ensureArgObject, getHomeDir, isNotFalsy, isTruthy, last, maxBy, readJson, sumBy} from '../../src/util/index' + +describe('capitalize', () => { + it('capitalizes the string', () => { + expect(capitalize('dominik')).to.equal('Dominik') + }) + it('works with an empty string', () => { + expect(capitalize('')).to.equal('') + }) +}) + +type Item = { x: number } + +describe('sumBy', () => { + it('returns zero for empty array', () => { + const arr: Item[] = [] + expect(sumBy(arr, i => i.x)).to.equal(0) + }) + it('returns sum for non-empty array', () => { + const arr: Item[] = [{x: 1}, {x: 2}, {x: 3}] + expect(sumBy(arr, i => i.x)).to.equal(6) + }) +}) + +describe('maxBy', () => { + it('returns undefined for empty array', () => { + const arr: Item[] = [] + expect(maxBy(arr, i => i.x)).to.be.undefined + }) + it('returns max value in the array', () => { + const arr: Item[] = [{x: 1}, {x: 3}, {x: 2}] + expect(maxBy(arr, i => i.x)).to.equal(arr[1]) + }) +}) + +describe('last', () => { + it('returns undefined for empty array', () => { + expect(last([])).to.be.undefined + }) + it('returns undefined for undefined', () => { + expect(last()).to.be.undefined + }) + it('returns last value in the array', () => { + const arr: Item[] = [{x: 1}, {x: 3}, {x: 2}] + expect(last(arr)).to.equal(arr[2]) + }) + it('returns only item in array', () => { + expect(last([6])).to.equal(6) + }) +}) + +describe('ensureArgObject', () => { + it('should convert array of arguments to an object', () => { + const args = [ + {name: 'foo', description: 'foo desc', required: true}, + {name: 'bar', description: 'bar desc'}, + ] + const expected = {foo: args[0], bar: args[1]} + expect(ensureArgObject(args)).to.deep.equal(expected) + }) + + it('should do nothing to an arguments object', () => { + const args = { + foo: {name: 'foo', description: 'foo desc', required: true}, + bar: {name: 'bar', description: 'bar desc'}, + } + expect(ensureArgObject(args)).to.deep.equal(args) + }) +}) + +describe('isNotFalsy', () => { + it('should return true for truthy values', () => { + expect(isNotFalsy('true')).to.be.true + expect(isNotFalsy('1')).to.be.true + expect(isNotFalsy('yes')).to.be.true + expect(isNotFalsy('y')).to.be.true + }) + + it('should return false for falsy values', () => { + expect(isNotFalsy('false')).to.be.false + expect(isNotFalsy('0')).to.be.false + expect(isNotFalsy('no')).to.be.false + expect(isNotFalsy('n')).to.be.false + }) +}) + +describe('isTruthy', () => { + it('should return true for truthy values', () => { + expect(isTruthy('true')).to.be.true + expect(isTruthy('1')).to.be.true + expect(isTruthy('yes')).to.be.true + expect(isTruthy('y')).to.be.true + }) + + it('should return false for falsy values', () => { + expect(isTruthy('false')).to.be.false + expect(isTruthy('0')).to.be.false + expect(isTruthy('no')).to.be.false + expect(isTruthy('n')).to.be.false + }) +}) + +describe('getHomeDir', () => { + it('should return the home directory', () => { + expect(getHomeDir()).to.equal(homedir()) + }) +}) + +describe('readJson', () => { + it('should return parsed JSON', async () => { + const json = await readJson<{name: string}>('package.json') + expect(json.name).to.equal('@oclif/core') + }) + + it('should throw an error if the file does not exist', async () => { + try { + await readJson('does-not-exist.json') + throw new Error('Expected an error to be thrown') + } catch (error) { + const err = error as Error + expect(err.message).to.include('ENOENT: no such file or directory') + } + }) +}) + +describe('castArray', () => { + it('should cast a value to an array', () => { + expect(castArray('foo')).to.deep.equal(['foo']) + }) + + it('should return an array if the value is an array', () => { + expect(castArray(['foo'])).to.deep.equal(['foo']) + }) + + it('should return an empty array if the value is undefined', () => { + expect(castArray()).to.deep.equal([]) + }) +}) + diff --git a/tsconfig.json b/tsconfig.json index 0aed879ee..6e71ac339 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "./src" ], "strict": true, - "target": "ES2021", + "target": "ES2022", "allowSyntheticDefaultImports": true, "noErrorTruncation": true, "moduleResolution": "Node16" diff --git a/yarn.lock b/yarn.lock index 16a9fccd4..30b5a6612 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,14 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + "@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" @@ -14,16 +22,142 @@ dependencies: "@babel/highlight" "^7.14.5" +"@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + +"@babel/compat-data@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" + integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== + +"@babel/core@^7.7.5": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.0.tgz#f8259ae0e52a123eb40f552551e647b506a94d83" + integrity sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.23.0" + "@babel/helpers" "^7.23.0" + "@babel/parser" "^7.23.0" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.0" + "@babel/types" "^7.23.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== + dependencies: + "@babel/types" "^7.23.0" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.15" + browserslist "^4.21.9" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" + integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + "@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-identifier@^7.22.5": +"@babel/helper-validator-identifier@^7.22.20", "@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/helper-validator-option@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" + integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== + +"@babel/helpers@^7.23.0": + version "7.23.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.1.tgz#44e981e8ce2b9e99f8f0b703f3326a4636c16d15" + integrity sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.0" + "@babel/types" "^7.23.0" + "@babel/highlight@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" @@ -33,6 +167,54 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.0.0", "@babel/parser@^7.21.4", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== + +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53" + integrity sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + 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" @@ -182,6 +364,14 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@dependents/detective-less@^3.0.1": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@dependents/detective-less/-/detective-less-3.0.2.tgz#c6e46997010fe03a5dc98351a7e99a46d34f5832" + integrity sha512-1YUvQ+e0eeTWAHoN8Uz2x2U37jZs6IGutiIE5LXId7cxfUGhtZjzxE06FdUiuiRrW+UE0vNCdSNPH2lY4dQCOQ== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^5.0.1" + "@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" @@ -250,6 +440,22 @@ 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== +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -257,16 +463,40 @@ dependencies: "@sinclair/typebox" "^0.27.8" +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" @@ -275,6 +505,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.19" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" + integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -524,6 +762,38 @@ wordwrap "^1.0.0" wrap-ansi "^7.0.0" +"@oclif/core@^3.0.0-beta.17": + version "3.0.0-beta.17" + resolved "https://registry.yarnpkg.com/@oclif/core/-/core-3.0.0-beta.17.tgz#a6f0d22703647743aba6c9e53f0560724aba3ccb" + integrity sha512-knoDpfAVYgbVeuoUpX/es8os1GeVp//gNczB3mgNPotENXMALOZKgIv7rBbKpHa1xmpGdEf0n31WrBq3HigKwQ== + dependencies: + "@types/cli-progress" "^3.11.0" + ansi-escapes "^4.3.2" + ansi-styles "^4.3.0" + cardinal "^2.1.1" + chalk "^4.1.2" + clean-stack "^3.0.1" + cli-progress "^3.12.0" + debug "^4.3.4" + ejs "^3.1.8" + get-package-type "^0.1.0" + globby "^11.1.0" + hyperlinker "^1.0.0" + indent-string "^4.0.0" + is-wsl "^2.2.0" + js-yaml "^3.14.1" + natural-orderby "^2.0.3" + object-treeify "^1.1.33" + password-prompt "^1.1.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + supports-color "^8.1.1" + supports-hyperlinks "^2.2.0" + widest-line "^3.1.0" + wordwrap "^1.0.0" + wrap-ansi "^7.0.0" + "@oclif/plugin-help@^5.2.8": version "5.2.8" resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-5.2.8.tgz#25821ea8588b59e5554187611ce58b8a95d4e90d" @@ -550,13 +820,18 @@ validate-npm-package-name "^5.0.0" yarn "^1.22.18" -"@oclif/test@^2.4.7": - version "2.4.7" - resolved "https://registry.yarnpkg.com/@oclif/test/-/test-2.4.7.tgz#4372270157aa6598c0d1b61349b2eff4e2003193" - integrity sha512-r18sKGNUm/VGQ8BkSF9Kn7QeMGjGMDUrLxTDPzL5ERaBF5YSi+O9CT3mKhcFdrMwGnCqPVvlAdX4U/6gtYPy1A== +"@oclif/prettier-config@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@oclif/prettier-config/-/prettier-config-0.1.1.tgz#424b7f631b96917efe8b9a925b068cd2218a00f2" + integrity sha512-fxsIwtHCu8V6+WmsN0w/s6QM3z/VJtvTEXTiY8F8l1Oj9S+nOC0eTaQp2YSDFlgpFvDJ5NwC5ssdZAdFE3PyDg== + +"@oclif/test@^3.0.0-beta.1": + version "3.0.0-beta.1" + resolved "https://registry.yarnpkg.com/@oclif/test/-/test-3.0.0-beta.1.tgz#37beca545657eaf04413652f3b3db4f946c9804a" + integrity sha512-f/cuHO1GboGWBvBCxApkm+EtNjTLIT710XANPChdqPrQ5zF2zLicU6Kt7asH4HPsRKYl+LjJUgF1299t6333LA== dependencies: - "@oclif/core" "^2.11.10" - fancy-test "^2.0.34" + "@oclif/core" "^3.0.0-beta.17" + fancy-test "^3.0.1" "@pkgjs/parseargs@^0.11.0": version "0.11.0" @@ -920,6 +1195,16 @@ debug "^4.3.4" ts-api-utils "^1.0.1" +"@typescript-eslint/types@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" + integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + "@typescript-eslint/types@6.7.2": version "6.7.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.2.tgz#75a615a6dbeca09cafd102fe7f465da1d8a3c066" @@ -938,6 +1223,32 @@ semver "^7.5.4" ts-api-utils "^1.0.1" +"@typescript-eslint/typescript-estree@^4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" + integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== + dependencies: + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/visitor-keys" "4.33.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/typescript-estree@^5.55.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + "@typescript-eslint/utils@6.7.2": version "6.7.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.2.tgz#b9ef0da6f04932167a9222cb4ac59cb187165ebf" @@ -951,6 +1262,22 @@ "@typescript-eslint/typescript-estree" "6.7.2" semver "^7.5.4" +"@typescript-eslint/visitor-keys@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" + integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== + dependencies: + "@typescript-eslint/types" "4.33.0" + eslint-visitor-keys "^2.0.0" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + "@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" @@ -1097,6 +1424,11 @@ ansicolors@~0.3.2: resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= +any-promise@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1105,12 +1437,24 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +app-module-path@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" + integrity sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ== + +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== + dependencies: + default-require-extensions "^3.0.0" + "aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -archy@~1.0.0: +archy@^1.0.0, archy@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== @@ -1231,6 +1575,21 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== +ast-module-types@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-2.7.1.tgz#3f7989ef8dfa1fdb82dfe0ab02bdfc7c77a57dd3" + integrity sha512-Rnnx/4Dus6fn7fTqdeLEAn5vUll5w7/vts0RN608yFa6si/rDOUonlIIiwugHBFWjylHjxm9owoSZn71KwG4gw== + +ast-module-types@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-3.0.0.tgz#9a6d8a80f438b6b8fe4995699d700297f398bf81" + integrity sha512-CMxMCOCS+4D+DkOQfuZf+vLrSEmY/7xtORwdxs4wtcC1wVgvk2MqFFTwQCFhvWsI4KPU9lcWXPI8DgRiz+xetQ== + +ast-module-types@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-4.0.0.tgz#17e1cadd5b5b108e7295b0cf0cff21ccc226b639" + integrity sha512-Kd0o8r6CDazJGCRzs8Ivpn0xj19oNKrULhoJFzhGjRsLpekF2zyZs9Ukz+JvZhWD6smszfepakTFhAaYpsI12g== + astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" @@ -1284,6 +1643,15 @@ binary-extensions@^2.0.0, binary-extensions@^2.2.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1311,6 +1679,24 @@ 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.21.9: + version "4.22.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.0.tgz#6adc8116589ccea8a99d0df79c5de2436199abdb" + integrity sha512-v+Jcv64L2LbfTC6OnRcaxtqJNJuQAVhZKSJfR/6hn7lhnChUXl4amwVviqN1k411BB+3rRoKMitELRn1CojeRA== + dependencies: + caniuse-lite "^1.0.30001539" + electron-to-chromium "^1.4.530" + node-releases "^2.0.13" + update-browserslist-db "^1.0.13" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" @@ -1349,6 +1735,16 @@ cacache@^17.0.0, cacache@^17.0.4, cacache@^17.1.3: tar "^6.1.11" unique-filename "^3.0.0" +caching-transform@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== + dependencies: + hasha "^5.0.0" + make-dir "^3.0.0" + package-hash "^4.0.0" + write-file-atomic "^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" @@ -1371,7 +1767,7 @@ camelcase-keys@^6.2.2: map-obj "^4.0.0" quick-lru "^4.0.1" -camelcase@^5.3.1: +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -1381,6 +1777,11 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== +caniuse-lite@^1.0.30001539: + version "1.0.30001540" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001540.tgz#a316ca4f2ae673ab02ff0ec533334016d56ff658" + integrity sha512-9JL38jscuTJBTcuETxm8QLsFr/F6v0CYYTEU6r5+qSM98P2Q0Hmu0eG1dTG5GBUmywU3UlcVOUSIJYY47rdFSw== + cardinal@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" @@ -1409,7 +1810,7 @@ chai@^4.3.7: pathval "^1.1.1" type-detect "^4.0.5" -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1418,7 +1819,7 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1502,6 +1903,13 @@ cli-columns@^4.0.0: string-width "^4.2.3" strip-ansi "^6.0.1" +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + cli-progress@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" @@ -1509,6 +1917,11 @@ cli-progress@^3.12.0: dependencies: string-width "^4.2.3" +cli-spinners@^2.5.0: + version "2.9.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.1.tgz#9c0b9dad69a6d47cbb4333c14319b060ed395a35" + integrity sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ== + cli-table3@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" @@ -1518,6 +1931,15 @@ cli-table3@^0.6.3: optionalDependencies: "@colors/colors" "1.5.0" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -1556,7 +1978,7 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@~1.1.4: +color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -1574,6 +1996,21 @@ columnify@^1.6.0: strip-ansi "^6.0.1" wcwidth "^1.0.0" +commander@^2.16.0, commander@^2.20.3, commander@^2.8.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commander@^9.5.0: + version "9.5.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== + commitlint@^12.1.4: version "12.1.4" resolved "https://registry.yarnpkg.com/commitlint/-/commitlint-12.1.4.tgz#afc185957afdd07d2ebb6c78b5956d407fc09ad0" @@ -1587,6 +2024,11 @@ common-ancestor-path@^1.0.1: resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + compare-func@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" @@ -1645,6 +2087,16 @@ conventional-commits-parser@^3.0.0: through2 "^4.0.0" trim-off-newlines "^1.0.0" +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cosmiconfig@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" @@ -1679,7 +2131,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2: +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1698,7 +2150,7 @@ dargs@^7.0.0: resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== -debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.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== @@ -1737,11 +2189,23 @@ deep-eql@^4.1.2: dependencies: type-detect "^4.0.0" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +default-require-extensions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.1.tgz#bfae00feeaeada68c2ae256c62540f60b80625bd" + integrity sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw== + dependencies: + strip-bom "^4.0.0" + defaults@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" @@ -1772,6 +2236,162 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== +dependency-tree@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/dependency-tree/-/dependency-tree-9.0.0.tgz#9288dd6daf35f6510c1ea30d9894b75369aa50a2" + integrity sha512-osYHZJ1fBSon3lNLw70amAXsQ+RGzXsPvk9HbBgTLbp/bQBmpH5mOmsUvqXU+YEWVU0ZLewsmzOET/8jWswjDQ== + dependencies: + commander "^2.20.3" + debug "^4.3.1" + filing-cabinet "^3.0.1" + precinct "^9.0.0" + typescript "^4.0.0" + +detective-amd@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/detective-amd/-/detective-amd-3.1.2.tgz#bf55eb5291c218b76d6224a3d07932ef13a9a357" + integrity sha512-jffU26dyqJ37JHR/o44La6CxtrDf3Rt9tvd2IbImJYxWKTMdBjctp37qoZ6ZcY80RHg+kzWz4bXn39e4P7cctQ== + dependencies: + ast-module-types "^3.0.0" + escodegen "^2.0.0" + get-amd-module-type "^3.0.0" + node-source-walk "^4.2.0" + +detective-amd@^4.0.1, detective-amd@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/detective-amd/-/detective-amd-4.2.0.tgz#21c43465669f06cf894eef047a27e6e72ba6bc13" + integrity sha512-RbuEJHz78A8nW7CklkqTzd8lDCN42En53dgEIsya0DilpkwslamSZDasLg8dJyxbw46OxhSQeY+C2btdSkCvQQ== + dependencies: + ast-module-types "^4.0.0" + escodegen "^2.0.0" + get-amd-module-type "^4.1.0" + node-source-walk "^5.0.1" + +detective-cjs@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/detective-cjs/-/detective-cjs-3.1.3.tgz#50e107d67b37f459b0ec02966ceb7e20a73f268b" + integrity sha512-ljs7P0Yj9MK64B7G0eNl0ThWSYjhAaSYy+fQcpzaKalYl/UoQBOzOeLCSFEY1qEBhziZ3w7l46KG/nH+s+L7BQ== + dependencies: + ast-module-types "^3.0.0" + node-source-walk "^4.0.0" + +detective-cjs@^4.0.0, detective-cjs@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/detective-cjs/-/detective-cjs-4.1.0.tgz#56b1558ca0910933c7fc47c740b957f0280ff302" + integrity sha512-QxzMwt5MfPLwS7mG30zvnmOvHLx5vyVvjsAV6gQOyuMoBR5G1DhS1eJZ4P10AlH+HSnk93mTcrg3l39+24XCtg== + dependencies: + ast-module-types "^4.0.0" + node-source-walk "^5.0.1" + +detective-es6@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/detective-es6/-/detective-es6-2.2.2.tgz#ee5f880981d9fecae9a694007029a2f6f26d8d28" + integrity sha512-eZUKCUsbHm8xoeoCM0z6JFwvDfJ5Ww5HANo+jPR7AzkFpW9Mun3t/TqIF2jjeWa2TFbAiGaWESykf2OQp3oeMw== + dependencies: + node-source-walk "^4.0.0" + +detective-es6@^3.0.0, detective-es6@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/detective-es6/-/detective-es6-3.0.1.tgz#53a15fbb2f298c4a106d9fe7427da8a57162dde6" + integrity sha512-evPeYIEdK1jK3Oji5p0hX4sPV/1vK+o4ihcWZkMQE6voypSW/cIBiynOLxQk5KOOQbdP8oOAsYqouMTYO5l1sw== + dependencies: + node-source-walk "^5.0.0" + +detective-less@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/detective-less/-/detective-less-1.0.2.tgz#a68af9ca5f69d74b7d0aa190218b211d83b4f7e3" + integrity sha512-Rps1xDkEEBSq3kLdsdnHZL1x2S4NGDcbrjmd4q+PykK5aJwDdP5MBgrJw1Xo+kyUHuv3JEzPqxr+Dj9ryeDRTA== + dependencies: + debug "^4.0.0" + gonzales-pe "^4.2.3" + node-source-walk "^4.0.0" + +detective-postcss@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detective-postcss/-/detective-postcss-4.0.0.tgz#24e69b465e5fefe7a6afd05f7e894e34595dbf51" + integrity sha512-Fwc/g9VcrowODIAeKRWZfVA/EufxYL7XfuqJQFroBKGikKX83d2G7NFw6kDlSYGG3LNQIyVa+eWv1mqre+v4+A== + dependencies: + debug "^4.1.1" + is-url "^1.2.4" + postcss "^8.1.7" + postcss-values-parser "^2.0.1" + +detective-postcss@^6.1.0, detective-postcss@^6.1.1: + version "6.1.3" + resolved "https://registry.yarnpkg.com/detective-postcss/-/detective-postcss-6.1.3.tgz#51a2d4419327ad85d0af071c7054c79fafca7e73" + integrity sha512-7BRVvE5pPEvk2ukUWNQ+H2XOq43xENWbH0LcdCE14mwgTBEAMoAx+Fc1rdp76SmyZ4Sp48HlV7VedUnP6GA1Tw== + dependencies: + is-url "^1.2.4" + postcss "^8.4.23" + postcss-values-parser "^6.0.2" + +detective-sass@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/detective-sass/-/detective-sass-3.0.2.tgz#e0f35aac79a4d2f6409c284d95b8f7ecd5973afd" + integrity sha512-DNVYbaSlmti/eztFGSfBw4nZvwsTaVXEQ4NsT/uFckxhJrNRFUh24d76KzoCC3aarvpZP9m8sC2L1XbLej4F7g== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^4.0.0" + +detective-sass@^4.0.1, detective-sass@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/detective-sass/-/detective-sass-4.1.3.tgz#6cdcc27ae8a90d15704e0ba83683048f77f10b75" + integrity sha512-xGRbwGaGte57gvEqM8B9GDiURY3El/H49vA6g9wFkxq9zalmTlTAuqWu+BsH0iwonGPruLt55tZZDEZqPc6lag== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^5.0.1" + +detective-scss@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detective-scss/-/detective-scss-2.0.2.tgz#7d2a642616d44bf677963484fa8754d9558b8235" + integrity sha512-hDWnWh/l0tht/7JQltumpVea/inmkBaanJUcXRB9kEEXVwVUMuZd6z7eusQ6GcBFrfifu3pX/XPyD7StjbAiBg== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^4.0.0" + +detective-scss@^3.0.0, detective-scss@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/detective-scss/-/detective-scss-3.1.1.tgz#b49f05cadfb0837b04e23baba292581b7c7f65e1" + integrity sha512-FWkfru1jZBhUeuBsOeGKXKAVDrzYFSQFK2o2tuG/nCCFQ0U/EcXC157MNAcR5mmj+mCeneZzlkBOFJTesDjrww== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^5.0.1" + +detective-stylus@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detective-stylus/-/detective-stylus-1.0.3.tgz#20a702936c9fd7d4203fd7a903314b5dd43ac713" + integrity sha512-4/bfIU5kqjwugymoxLXXLltzQNeQfxGoLm2eIaqtnkWxqbhap9puDVpJPVDx96hnptdERzS5Cy6p9N8/08A69Q== + +detective-stylus@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detective-stylus/-/detective-stylus-2.0.1.tgz#d528dfa7ef3c4eb2fbc9a7249d54906ec4e05d09" + integrity sha512-/Tvs1pWLg8eYwwV6kZQY5IslGaYqc/GACxjcaGudiNtN5nKCH6o2WnJK3j0gA3huCnoQcbv8X7oz/c1lnvE3zQ== + +detective-stylus@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/detective-stylus/-/detective-stylus-3.0.0.tgz#c869795a7d6df7043ab6aee8b1a6f3dd66764232" + integrity sha512-1xYTzbrduExqMYmte7Qk99IRA3Aa6oV7PYzd+3yDcQXkmENvyGF/arripri6lxRDdNYEb4fZFuHtNRAXbz3iAA== + +detective-typescript@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/detective-typescript/-/detective-typescript-7.0.2.tgz#c6e00b4c28764741ef719662250e6b014a5f3c8e" + integrity sha512-unqovnhxzvkCz3m1/W4QW4qGsvXCU06aU2BAm8tkza+xLnp9SOFnob2QsTxUv5PdnQKfDvWcv9YeOeFckWejwA== + dependencies: + "@typescript-eslint/typescript-estree" "^4.33.0" + ast-module-types "^2.7.1" + node-source-walk "^4.2.0" + typescript "^3.9.10" + +detective-typescript@^9.0.0, detective-typescript@^9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/detective-typescript/-/detective-typescript-9.1.1.tgz#b99c0122cbb35b39de2c5f58447f1e93ac28c6d5" + integrity sha512-Uc1yVutTF0RRm1YJ3g//i1Cn2vx1kwHj15cnzQP6ff5koNzQ0idc1zAC73ryaWEulA0ElRXFTq6wOqe8vUQ3MA== + dependencies: + "@typescript-eslint/typescript-estree" "^5.55.0" + ast-module-types "^4.0.0" + node-source-walk "^5.0.1" + typescript "^4.9.5" + diff-sequences@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" @@ -1832,6 +2452,11 @@ ejs@^3.1.8: dependencies: jake "^10.8.5" +electron-to-chromium@^1.4.530: + version "1.4.531" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.531.tgz#22966d894c4680726c17cf2908ee82ff5d26ac25" + integrity sha512-H6gi5E41Rn3/mhKlPaT1aIMg/71hTAqn0gYEllSuw9igNWtvQwu185jiCZoZD29n7Zukgh7GVZ3zGf0XvkhqjQ== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -1849,7 +2474,7 @@ encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -enhanced-resolve@^5.12.0: +enhanced-resolve@^5.12.0, enhanced-resolve@^5.8.3: version "5.15.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== @@ -1944,6 +2569,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -1964,6 +2594,17 @@ 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= +escodegen@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionalDependencies: + source-map "~0.6.1" + 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" @@ -2210,7 +2851,7 @@ espree@^9.6.0, espree@^9.6.1: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" -esprima@^4.0.0, esprima@~4.0.0: +esprima@^4.0.0, esprima@^4.0.1, 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== @@ -2254,10 +2895,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.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" "*" @@ -2266,12 +2907,13 @@ 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" -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== +fancy-test@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fancy-test/-/fancy-test-3.0.1.tgz#8b19ed4ccc8b0625475eabf36fd743e1d0168abb" + integrity sha512-Ke1IFOGEBxP2dNg0X7ZYPUSwKSRr5GNn3xM/2DpHkP86riF3MFDpesXJuD1TGm7gcfwBtYpuSzuw3m704bThVg== dependencies: "@types/chai" "*" "@types/lodash" "*" @@ -2346,6 +2988,25 @@ filelist@^1.0.1: dependencies: minimatch "^3.0.4" +filing-cabinet@^3.0.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/filing-cabinet/-/filing-cabinet-3.3.1.tgz#45d87bb273a0e0a7dd6ac6bac9111059186e2e9c" + integrity sha512-renEK4Hh6DUl9Vl22Y3cxBq1yh8oNvbAdXnhih0wVpmea+uyKjC9K4QeRjUaybIiIewdzfum+Fg15ZqJ/GyCaA== + dependencies: + app-module-path "^2.2.0" + commander "^2.20.3" + debug "^4.3.3" + enhanced-resolve "^5.8.3" + is-relative-path "^1.0.2" + module-definition "^3.3.1" + module-lookup-amd "^7.0.1" + resolve "^1.21.0" + resolve-dependency-path "^2.0.0" + sass-lookup "^3.0.0" + stylus-lookup "^3.0.1" + tsconfig-paths "^3.10.1" + typescript "^3.9.7" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -2353,6 +3014,15 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +find-cache-dir@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + find-up@5.0.0, find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -2361,7 +3031,7 @@ find-up@5.0.0, find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-up@^4.1.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -2387,6 +3057,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== +flatten@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -2394,6 +3069,14 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" @@ -2402,6 +3085,11 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +fromentries@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + fs-extra@^9.0, fs-extra@^9.0.0, fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" @@ -2484,7 +3172,28 @@ gauge@^5.0.0: strip-ansi "^6.0.1" wide-align "^1.1.5" -get-caller-file@^2.0.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-amd-module-type@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-3.0.2.tgz#46550cee2b8e1fa4c3f2c8a5753c36990aa49ab0" + integrity sha512-PcuKwB8ouJnKuAPn6Hk3UtdfKoUV3zXRqVEvj8XGIXqjWfgd1j7QGdXy5Z9OdQfzVt1Sk29HVe/P+X74ccOuqw== + dependencies: + ast-module-types "^3.0.0" + node-source-walk "^4.2.2" + +get-amd-module-type@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-4.1.0.tgz#af1396d02cd935cb6fafdc4a5282395db3422db6" + integrity sha512-0e/eK6vTGCnSfQ6eYs3wtH05KotJYIP7ZIZEueP/KlA+0dIAEs8bYFvOd/U56w1vfjhJqBagUxVMyy9Tr/cViQ== + dependencies: + ast-module-types "^4.0.0" + node-source-walk "^5.0.1" + +get-caller-file@^2.0.1, 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" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -2504,6 +3213,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-proto "^1.0.1" has-symbols "^1.0.3" +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + 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" @@ -2584,7 +3298,7 @@ glob@^7.0.0, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.4: +glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -2603,6 +3317,11 @@ 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.19.0: version "13.21.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571" @@ -2617,7 +3336,7 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" -globby@^11.0.1, globby@^11.1.0: +globby@^11.0.1, globby@^11.0.3, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -2629,6 +3348,13 @@ globby@^11.0.1, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +gonzales-pe@^4.2.3, gonzales-pe@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" + integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== + dependencies: + minimist "^1.2.5" + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -2707,6 +3433,14 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hasha@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -2731,6 +3465,11 @@ hosted-git-info@^6.0.0, hosted-git-info@^6.1.1: dependencies: lru-cache "^7.5.1" +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + http-cache-semantics@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" @@ -2789,7 +3528,7 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.2.1: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -2834,6 +3573,11 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2842,12 +3586,12 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.4: +ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -3004,6 +3748,11 @@ is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" @@ -3026,6 +3775,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== + is-obj@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" @@ -3054,6 +3808,16 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== + +is-relative-path@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-relative-path/-/is-relative-path-1.0.2.tgz#091b46a0d67c1ed0fe85f1f8cfdde006bb251d46" + integrity sha512-i1h+y50g+0hRbBD+dbnInl3JlJ702aar58snAeX+MxBAPvzXGej7sYoPMhlnykabt0ZzCJNBEyzMlekuQZN7fA== + 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" @@ -3099,11 +3863,26 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: dependencies: which-typed-array "^1.1.11" +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + 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-url-superb@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-url-superb/-/is-url-superb-4.0.0.tgz#b54d1d2499bb16792748ac967aa3ecb41a33a8c2" + integrity sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA== + +is-url@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -3111,6 +3890,11 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -3133,6 +3917,66 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-processinfo@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz#366d454cd0dcb7eb6e0e419378e60072c8626169" + integrity sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg== + dependencies: + archy "^1.0.0" + cross-spawn "^7.0.3" + istanbul-lib-coverage "^3.2.0" + p-map "^3.0.0" + rimraf "^3.0.0" + uuid "^8.3.2" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + jackspeak@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.0.tgz#aa228a94de830f31d4e4f0184427ce91c4ff1493" @@ -3179,7 +4023,7 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js-yaml@^3.14.1: +js-yaml@^3.13.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== @@ -3187,6 +4031,11 @@ 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" @@ -3239,6 +4088,11 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -3424,6 +4278,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -3439,7 +4298,7 @@ lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@4.1.0, log-symbols@^4.0.0: +log-symbols@4.1.0, log-symbols@^4.0.0, log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -3454,6 +4313,13 @@ loupe@^2.3.1: dependencies: get-func-name "^2.0.0" +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -3471,6 +4337,48 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== +madge@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/madge/-/madge-6.1.0.tgz#9835bb53f2e00d184914c2b007b50736a964d297" + integrity sha512-irWhT5RpFOc6lkzGHKLihonCVgM0YtfNUh4IrFeW3EqHpnt/JHUG3z26j8PeJEktCGB4tmGOOOJi1Rl/ACWucQ== + dependencies: + chalk "^4.1.1" + commander "^7.2.0" + commondir "^1.0.1" + debug "^4.3.1" + dependency-tree "^9.0.0" + detective-amd "^4.0.1" + detective-cjs "^4.0.0" + detective-es6 "^3.0.0" + detective-less "^1.0.2" + detective-postcss "^6.1.0" + detective-sass "^4.0.1" + detective-scss "^3.0.0" + detective-stylus "^2.0.1" + detective-typescript "^9.0.0" + ora "^5.4.1" + pluralize "^8.0.0" + precinct "^8.1.0" + pretty-ms "^7.0.1" + rc "^1.2.7" + stream-to-array "^2.3.0" + ts-graphviz "^1.5.0" + walkdir "^0.4.1" + +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -3555,6 +4463,11 @@ micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -3597,7 +4510,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -3716,6 +4629,33 @@ 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-definition@^3.3.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/module-definition/-/module-definition-3.4.0.tgz#953a3861f65df5e43e80487df98bb35b70614c2b" + integrity sha512-XxJ88R1v458pifaSkPNLUTdSPNVGMP2SXVncVmApGO+gAfrLANiYe6JofymCzVceGOMwQE2xogxBSc8uB7XegA== + dependencies: + ast-module-types "^3.0.0" + node-source-walk "^4.0.0" + +module-definition@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/module-definition/-/module-definition-4.1.0.tgz#148ff9348e3401867229dcbe5947f4f6d5ccd3a2" + integrity sha512-rHXi/DpMcD2qcKbPCTklDbX9lBKJrUSl971TW5l6nMpqKCIlzJqmQ8cfEF5M923h2OOLHPDVlh5pJxNyV+AJlw== + dependencies: + ast-module-types "^4.0.0" + node-source-walk "^5.0.1" + +module-lookup-amd@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/module-lookup-amd/-/module-lookup-amd-7.0.1.tgz#d67c1a93f2ff8e38b8774b99a638e9a4395774b2" + integrity sha512-w9mCNlj0S8qviuHzpakaLVc+/7q50jl9a/kmJ/n8bmXQZgDPkQHnPBb8MUOYh3WpAYkXuNc2c+khsozhIp/amQ== + dependencies: + commander "^2.8.1" + debug "^4.1.0" + glob "^7.1.6" + requirejs "^2.3.5" + requirejs-config-file "^4.0.0" + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -3736,6 +4676,11 @@ nanoid@3.3.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -3815,6 +4760,32 @@ node-gyp@^9.0.0, node-gyp@^9.4.0: tar "^6.1.2" which "^2.0.2" +node-preload@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== + dependencies: + process-on-spawn "^1.0.0" + +node-releases@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== + +node-source-walk@^4.0.0, node-source-walk@^4.2.0, node-source-walk@^4.2.2: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-4.3.0.tgz#8336b56cfed23ac5180fe98f1e3bb6b11fd5317c" + integrity sha512-8Q1hXew6ETzqKRAs3jjLioSxNfT1cx74ooiF8RlAONwVMcfq+UdzLC2eB5qcPldUxaE5w3ytLkrmV1TGddhZTA== + dependencies: + "@babel/parser" "^7.0.0" + +node-source-walk@^5.0.0, node-source-walk@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-5.0.2.tgz#0eb439ce378946ce531e07a6a0073d06288396dd" + integrity sha512-Y4jr/8SRS5hzEdZ7SGuvZGwfORvNsSsNRwDXx5WisiqzsVfeftDvRgfeqWNgZvWSJbgubTRVRYBzK6UO+ErqjA== + dependencies: + "@babel/parser" "^7.21.4" + nopt@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" @@ -4042,6 +5013,39 @@ npmlog@^7.0.1: gauge "^5.0.0" set-blocking "^2.0.0" +nyc@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" + integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + caching-transform "^4.0.0" + convert-source-map "^1.7.0" + decamelize "^1.2.0" + find-cache-dir "^3.2.0" + find-up "^4.1.0" + foreground-child "^2.0.0" + get-package-type "^0.1.0" + glob "^7.1.6" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-processinfo "^2.0.2" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + make-dir "^3.0.0" + node-preload "^0.2.1" + p-map "^3.0.0" + process-on-spawn "^1.0.0" + resolve-from "^5.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + spawn-wrap "^2.0.0" + test-exclude "^6.0.0" + yargs "^15.0.2" + 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" @@ -4102,6 +5106,13 @@ once@^1.3.0: dependencies: wrappy "1" +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -4114,6 +5125,21 @@ optionator@^0.9.3: prelude-ls "^1.2.1" type-check "^0.4.0" +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -4142,6 +5168,13 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" @@ -4154,6 +5187,16 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + pacote@^15.0.0, pacote@^15.0.8, pacote@^15.2.0: version "15.2.0" resolved "https://registry.yarnpkg.com/pacote/-/pacote-15.2.0.tgz#0f0dfcc3e60c7b39121b2ac612bf8596e95344d3" @@ -4212,6 +5255,11 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-ms@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" + integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA== + password-prompt@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.2.tgz#85b2f93896c5bd9e9f2d6ff0627fa5af3dc00923" @@ -4270,6 +5318,11 @@ pathval@^1.1.1: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -4280,6 +5333,13 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + platform@^1.3.3: version "1.3.6" resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" @@ -4305,6 +5365,70 @@ postcss-selector-parser@^6.0.10: cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-values-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" + integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-values-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz#636edc5b86c953896f1bb0d7a7a6615df00fb76f" + integrity sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw== + dependencies: + color-name "^1.1.4" + is-url-superb "^4.0.0" + quote-unquote "^1.0.0" + +postcss@^8.1.7, postcss@^8.4.23: + version "8.4.30" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.30.tgz#0e0648d551a606ef2192a26da4cabafcc09c1aa7" + integrity sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +precinct@^8.1.0: + version "8.3.1" + resolved "https://registry.yarnpkg.com/precinct/-/precinct-8.3.1.tgz#94b99b623df144eed1ce40e0801c86078466f0dc" + integrity sha512-pVppfMWLp2wF68rwHqBIpPBYY8Kd12lDhk8LVQzOwqllifVR15qNFyod43YLyFpurKRZQKnE7E4pofAagDOm2Q== + dependencies: + commander "^2.20.3" + debug "^4.3.3" + detective-amd "^3.1.0" + detective-cjs "^3.1.1" + detective-es6 "^2.2.1" + detective-less "^1.0.2" + detective-postcss "^4.0.0" + detective-sass "^3.0.1" + detective-scss "^2.0.1" + detective-stylus "^1.0.0" + detective-typescript "^7.0.0" + module-definition "^3.3.1" + node-source-walk "^4.2.0" + +precinct@^9.0.0: + version "9.2.1" + resolved "https://registry.yarnpkg.com/precinct/-/precinct-9.2.1.tgz#db0a67abff7b0a9a3b2b1ac33d170e8a5fcac7b2" + integrity sha512-uzKHaTyiVejWW7VJtHInb9KBUq9yl9ojxXGujhjhDmPon2wgZPBKQIKR+6csGqSlUeGXAA4MEFnU6DesxZib+A== + dependencies: + "@dependents/detective-less" "^3.0.1" + commander "^9.5.0" + detective-amd "^4.1.0" + detective-cjs "^4.1.0" + detective-es6 "^3.0.1" + detective-postcss "^6.1.1" + detective-sass "^4.1.1" + detective-scss "^3.0.1" + detective-stylus "^3.0.0" + detective-typescript "^9.1.1" + module-definition "^4.1.0" + node-source-walk "^5.0.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -4319,11 +5443,25 @@ pretty-format@^29.7.0: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-ms@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8" + integrity sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q== + dependencies: + parse-ms "^2.1.0" + proc-log@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8" integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A== +process-on-spawn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93" + integrity sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg== + dependencies: + fromentries "^1.2.0" + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -4389,6 +5527,11 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quote-unquote@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/quote-unquote/-/quote-unquote-1.0.0.tgz#67a9a77148effeaf81a4d428404a710baaac8a0b" + integrity sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg== + rambda@^7.1.0: version "7.5.0" resolved "https://registry.yarnpkg.com/rambda/-/rambda-7.5.0.tgz#1865044c59bc0b16f63026c6e5a97e4b1bbe98fe" @@ -4401,6 +5544,16 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-is@^18.0.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" @@ -4464,7 +5617,7 @@ readable-stream@3, readable-stream@^3.0.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^3.6.0: +readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -4539,11 +5692,41 @@ regjsparser@^0.10.0: dependencies: jsesc "~0.5.0" +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA== + dependencies: + es6-error "^4.0.1" + 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-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +requirejs-config-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz#4244da5dd1f59874038cc1091d078d620abb6ebc" + integrity sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw== + dependencies: + esprima "^4.0.0" + stringify-object "^3.2.1" + +requirejs@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" + integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== + +resolve-dependency-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-dependency-path/-/resolve-dependency-path-2.0.0.tgz#11700e340717b865d216c66cabeb4a2a3c696736" + integrity sha512-DIgu+0Dv+6v2XwRaNWnumKu7GPufBBOr5I1gRPJHkvghrfCGOooJODFvgFimX/KRxk9j0whD2MnKHzM1jYvk9w== + 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" @@ -4574,7 +5757,7 @@ resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.20.0: is-core-module "^2.2.0" path-parse "^1.0.6" -resolve@^1.22.4: +resolve@^1.21.0, resolve@^1.22.4: version "1.22.6" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== @@ -4583,6 +5766,14 @@ resolve@^1.22.4: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -4593,7 +5784,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -4636,6 +5827,13 @@ safe-regex-test@^1.0.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sass-lookup@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/sass-lookup/-/sass-lookup-3.0.0.tgz#3b395fa40569738ce857bc258e04df2617c48cac" + integrity sha512-TTsus8CfFRn1N44bvdEai1no6PqdmDiQUiqW5DlpmtT+tYnIt1tXtDIph5KA1efC+LmioJXSnCtUVpcK9gaKIg== + dependencies: + commander "^2.16.0" + "semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -4648,16 +5846,16 @@ semver@7.3.5: dependencies: lru-cache "^6.0.0" +semver@^6.0.0, semver@^6.3.0, 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@^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" @@ -4743,7 +5941,7 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.7: +signal-exit@^3.0.2, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -4824,6 +6022,28 @@ socks@^2.6.2: ip "^2.0.0" smart-buffer "^4.2.0" +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spawn-wrap@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" + integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== + dependencies: + foreground-child "^2.0.0" + is-windows "^1.0.2" + make-dir "^3.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + which "^2.0.1" + spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -4877,6 +6097,13 @@ stdout-stderr@^0.1.9: debug "^4.1.1" strip-ansi "^6.0.0" +stream-to-array@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353" + integrity sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA== + dependencies: + any-promise "^1.1.0" + "string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -4929,6 +6156,15 @@ string_decoder@^1.1.1, string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" +stringify-object@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -4955,6 +6191,11 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -4967,6 +6208,19 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +stylus-lookup@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/stylus-lookup/-/stylus-lookup-3.0.2.tgz#c9eca3ff799691020f30b382260a67355fefdddd" + integrity sha512-oEQGHSjg/AMaWlKe7gqsnYzan8DLcGIHe0dUaFkucZZ14z4zjENRlQMCHT4FNsiWnJf17YN9OvrCfCoi7VvOyg== + dependencies: + commander "^2.8.1" + debug "^4.1.0" + supports-color@8.1.1, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" @@ -5023,6 +6277,15 @@ tar@^6.1.11, tar@^6.1.13, tar@^6.1.15, tar@^6.1.2: mkdirp "^1.0.3" yallist "^4.0.0" +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-extensions@^1.0.0: version "1.9.0" resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" @@ -5050,6 +6313,11 @@ 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 sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + 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" @@ -5077,6 +6345,11 @@ ts-api-utils@^1.0.1: 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-graphviz@^1.5.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/ts-graphviz/-/ts-graphviz-1.8.1.tgz#5d95e58120be8b571847331516327d4840cc44f7" + integrity sha512-54/fe5iu0Jb6X0pmDmzsA2UHLfyHjUEUwfHtZcEOR0fZ6Myf+dFoO6eNsyL8CBDMJ9u7WWEewduVaiaXlvjSVw== + ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -5096,7 +6369,7 @@ ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tsconfig-paths@^3.14.2: +tsconfig-paths@^3.10.1, 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== @@ -5119,6 +6392,11 @@ tsd@^0.29.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" @@ -5129,6 +6407,13 @@ 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" @@ -5182,7 +6467,7 @@ type-fest@^0.6.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== -type-fest@^0.8.1: +type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== @@ -5226,6 +6511,23 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^3.9.10, typescript@^3.9.7: + version "3.9.10" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" + integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== + +typescript@^4.0.0, 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== + typescript@^5: version "5.2.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" @@ -5241,6 +6543,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA== + unique-filename@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea" @@ -5260,6 +6567,14 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -5272,6 +6587,11 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -5297,7 +6617,12 @@ walk-up-path@^3.0.1: resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-3.0.1.tgz#c8d78d5375b4966c717eb17ada73dbd41490e886" integrity sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA== -wcwidth@^1.0.0: +walkdir@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" + integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== + +wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== @@ -5315,6 +6640,11 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + 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" @@ -5380,6 +6710,15 @@ workerpool@6.2.1: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -5394,6 +6733,16 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + write-file-atomic@^5.0.0, write-file-atomic@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" @@ -5402,11 +6751,21 @@ write-file-atomic@^5.0.0, write-file-atomic@^5.0.1: imurmurhash "^0.1.4" signal-exit "^4.0.1" +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" @@ -5422,6 +6781,14 @@ yargs-parser@20.2.4: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" @@ -5450,6 +6817,23 @@ yargs@16.2.0, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^15.0.2: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yarn@^1.22.18: version "1.22.19" resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.19.tgz#4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" From b38f222d9cc581eb75ff012689d4b125fc9f9b6e Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Thu, 28 Sep 2023 18:19:09 +0000 Subject: [PATCH 02/13] chore(release): 3.0.0-beta.18 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d9cb3af3a..43c7814d8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/core", "description": "base library for oclif CLIs", - "version": "3.0.0-beta.17", + "version": "3.0.0-beta.18", "author": "Salesforce", "bugs": "https://github.com/oclif/core/issues", "dependencies": { From 2a8770b18eef488834ba5d56b71b36e721bbffac Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Fri, 29 Sep 2023 13:03:16 -0700 Subject: [PATCH 03/13] chore: use prettier and lint-staged (#798) * chore: update prettier config * chore: prettier and lint-staged * chore: reformat everything * chore: deal with reformat aftermath * chore: update configs and deps * perf: move ansi-escapes require to top level * chore: automerge.yml * chore: unpin eslint-plugin-prettier * chore: remove prettier plugin * chore: update lint-staged config --- .commitlintrc.json | 3 + .editorconfig | 11 - .eslintrc.json | 3 +- .github/workflows/automerge.yml | 7 +- .husky/pre-commit | 4 + .lintstagedrc.json | 5 + commitlint.config.js | 1 - package.json | 22 +- src/args.ts | 8 +- src/cli-ux/action/base.ts | 32 +- src/cli-ux/action/spinner.ts | 15 +- src/cli-ux/action/spinners.ts | 2 +- src/cli-ux/action/types.ts | 4 +- src/cli-ux/config.ts | 18 +- src/cli-ux/exit.ts | 2 +- src/cli-ux/flush.ts | 4 +- src/cli-ux/global.d.ts | 2 +- src/cli-ux/index.ts | 3 +- src/cli-ux/list.ts | 4 +- src/cli-ux/prompt.ts | 91 +- src/cli-ux/styled/index.ts | 1 - src/cli-ux/styled/object.ts | 9 +- src/cli-ux/styled/table.ts | 175 ++-- src/cli-ux/styled/tree.ts | 4 +- src/cli-ux/wait.ts | 7 +- src/command.ts | 107 +-- src/config/config.ts | 152 +-- src/config/index.ts | 1 - src/config/plugin-loader.ts | 123 +-- src/config/plugin.ts | 111 +-- src/config/ts-node.ts | 30 +- src/config/util.ts | 13 +- src/errors/errors/cli.ts | 6 +- src/errors/errors/pretty-print.ts | 10 +- src/errors/handle.ts | 7 +- src/errors/logger.ts | 11 +- src/execute.ts | 24 +- src/flags.ts | 124 ++- src/help/command.ts | 186 ++-- src/help/docopts.ts | 37 +- src/help/formatter.ts | 37 +- src/help/index.ts | 116 +-- src/help/root.ts | 12 +- src/help/util.ts | 2 +- src/interfaces/config.ts | 119 +-- src/interfaces/errors.ts | 14 +- src/interfaces/flags.ts | 2 +- src/interfaces/help.ts | 16 +- src/interfaces/hooks.ts | 96 +- src/interfaces/index.ts | 10 +- src/interfaces/manifest.ts | 4 +- src/interfaces/parser.ts | 359 ++++---- src/interfaces/pjson.ts | 149 +-- src/interfaces/plugin.ts | 72 +- src/interfaces/s3-manifest.ts | 22 +- src/interfaces/topic.ts | 6 +- src/interfaces/ts-config.ts | 30 +- src/module-loader.ts | 67 +- src/parser/errors.ts | 48 +- src/parser/help.ts | 3 +- src/parser/index.ts | 3 +- src/parser/parse.ts | 304 +++--- src/parser/validate.ts | 136 +-- src/performance.ts | 106 ++- src/settings.ts | 12 +- src/util/aggregate-flags.ts | 4 +- src/util/cache-command.ts | 109 ++- src/util/index.ts | 28 +- test/cli-ux/export.test.ts | 1 - test/cli-ux/fancy.ts | 22 +- test/cli-ux/index.test.ts | 15 +- test/cli-ux/prompt.test.ts | 105 ++- test/cli-ux/styled/object.test.ts | 4 +- test/cli-ux/styled/progress.test.ts | 11 +- test/cli-ux/styled/table.e2e.ts | 22 +- test/cli-ux/styled/table.test.ts | 176 ++-- test/cli-ux/styled/tree.test.ts | 4 +- test/command/command.test.ts | 575 ++++++------ .../command/fixtures/typescript/tsconfig.json | 8 +- .../test-help-in-lib/lib/test-help-plugin.js | 30 +- .../test-help-in-src/src/test-help-plugin.ts | 2 +- test/command/main-esm.test.ts | 68 +- test/command/main.test.ts | 7 +- test/config/config.flexible.test.ts | 180 ++-- test/config/config.test.ts | 231 ++--- test/config/esm.test.ts | 22 +- test/config/fixtures/help/package.json | 11 +- .../src/commands/foo/bar/test-result.js | 1 - .../fixtures/typescript/src/hooks/postrun.ts | 1 - test/config/fixtures/typescript/tsconfig.json | 8 +- test/config/help.config.test.ts | 36 +- test/config/mixed-cjs-esm.test.ts | 26 +- test/config/mixed-esm-cjs.test.ts | 26 +- test/config/test.ts | 3 +- test/config/typescript.test.ts | 22 +- test/config/util.test.ts | 41 +- test/errors/error.test.ts | 158 ++-- test/errors/handle.test.ts | 176 ++-- test/errors/pretty-print.test.ts | 34 +- test/errors/warn.test.ts | 26 +- test/help/_test-help-class.ts | 2 +- test/help/docopts.test.ts | 384 ++++---- test/help/fixtures/fixtures.ts | 17 + test/help/format-command-with-options.test.ts | 470 ++++++---- test/help/format-command.test.ts | 743 ++++++++------- test/help/format-commands.test.ts | 50 +- test/help/format-root.test.ts | 113 +-- test/help/format-topic.test.ts | 69 +- test/help/format-topics.test.ts | 52 +- test/help/help-test-utils.ts | 23 +- test/help/show-customized-help.test.ts | 200 ++-- test/help/show-help.test.ts | 460 +++++----- test/help/util.test.ts | 222 ++--- test/integration/esm-cjs.ts | 79 +- test/integration/plugins.e2e.ts | 15 +- test/integration/sf.e2e.ts | 12 +- test/integration/util.ts | 40 +- test/interfaces/args.test-types.ts | 7 +- test/interfaces/flags.test-types.ts | 47 +- test/module-loader/module-loader.test.ts | 36 +- test/parser/help.test.ts | 2 +- test/parser/parse.test.ts | 165 ++-- test/parser/validate.test.ts | 74 +- test/perf/parser.perf.ts | 137 +-- test/tsconfig.json | 5 +- test/util/cache-command.test.ts | 8 +- test/util/cache-default-value.test.ts | 13 +- test/util/index.test.ts | 24 +- yarn.lock | 862 ++++++++++++------ 129 files changed, 5301 insertions(+), 4347 deletions(-) create mode 100644 .commitlintrc.json delete mode 100644 .editorconfig create mode 100755 .husky/pre-commit create mode 100644 .lintstagedrc.json delete mode 100644 commitlint.config.js diff --git a/.commitlintrc.json b/.commitlintrc.json new file mode 100644 index 000000000..c30e5a970 --- /dev/null +++ b/.commitlintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["@commitlint/config-conventional"] +} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index beffa3084..000000000 --- a/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json index 2887d4e87..5971db05c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,8 @@ { "extends": [ "oclif", - "oclif-typescript" + "oclif-typescript", + "prettier" ], "rules": { "sort-imports": "error", diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 796eafae2..b61a8843d 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -2,9 +2,10 @@ name: automerge on: workflow_dispatch: schedule: - - cron: '17 2,5,8,11 * * *' + - cron: "17 2,5,8,11 * * *" jobs: automerge: - uses: oclif/github-workflows/.github/workflows/automerge.yml@main - secrets: inherit + uses: salesforcecli/github-workflows/.github/workflows/automerge.yml@main + secrets: + SVC_CLI_BOT_GITHUB_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..4abc587ad --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn lint-staged --concurrent false diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 000000000..c31470780 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,5 @@ +{ + "*.json": ["prettier --write"], + "*.md": ["prettier --write"], + "+(src|test)/**/*.+(ts|js)": ["eslint --fix", "prettier --write"] +} diff --git a/commitlint.config.js b/commitlint.config.js deleted file mode 100644 index 28fe5c5bf..000000000 --- a/commitlint.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {extends: ['@commitlint/config-conventional']} diff --git a/package.json b/package.json index 43c7814d8..973fb79ba 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,11 @@ "wrap-ansi": "^7.0.0" }, "devDependencies": { - "@commitlint/config-conventional": "^12.1.4", + "@commitlint/config-conventional": "^17.7.0", "@oclif/plugin-help": "^5.2.8", "@oclif/plugin-plugins": "^3.3.0", - "@oclif/prettier-config": "^0.1.1", - "@oclif/test": "^3.0.0-beta.1", + "@oclif/prettier-config": "^0.2.1", + "@oclif/test": "^3.0.1", "@types/ansi-styles": "^3.2.1", "@types/benchmark": "^2.1.2", "@types/chai": "^4.3.4", @@ -46,8 +46,7 @@ "@types/ejs": "^3.1.2", "@types/indent-string": "^4.0.1", "@types/js-yaml": "^3.12.7", - "@types/mocha": "^8.2.3", - "@types/nock": "^11.1.0", + "@types/mocha": "^10.0.2", "@types/node": "^18", "@types/node-notifier": "^8.0.2", "@types/slice-ansi": "^4.0.0", @@ -58,22 +57,24 @@ "benchmark": "^2.1.4", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", - "commitlint": "^12.1.4", + "commitlint": "^17.7.2", "cross-env": "^7.0.3", "eslint": "^8.49.0", "eslint-config-oclif": "^5.0.0", "eslint-config-oclif-typescript": "^2.0.1", - "fancy-test": "^3.0.0-beta.2", + "eslint-config-prettier": "^9.0.0", + "fancy-test": "^3.0.1", "globby": "^11.1.0", - "husky": "6", + "husky": "^8", + "lint-staged": "^14.0.1", "madge": "^6.1.0", "mocha": "^10.2.0", - "nock": "^13.3.0", "nyc": "^15.1.0", + "prettier": "^3.0.3", "shx": "^0.3.4", "sinon": "^11.1.2", - "tsd": "^0.29.0", "ts-node": "^10.9.1", + "tsd": "^0.29.0", "tslib": "^2.5.0", "typescript": "^5" }, @@ -107,6 +108,7 @@ "build": "shx rm -rf lib && tsc", "commitlint": "commitlint", "compile": "tsc", + "format": "prettier --write \"+(src|test)/**/*.+(ts|js|json)\"", "lint": "eslint . --ext .ts", "posttest": "yarn lint && yarn test:circular-deps", "prepack": "yarn run build", diff --git a/src/args.ts b/src/args.ts index 861bf8954..22f811c13 100644 --- a/src/args.ts +++ b/src/args.ts @@ -1,4 +1,3 @@ - import {Arg, ArgDefinition} from './interfaces/parser' import {dirExists, fileExists, isNotFalsy} from './util' import {Command} from './command' @@ -33,13 +32,12 @@ export function custom>(defaults: Partial({ - parse: async b => Boolean(b) && isNotFalsy(b), + parse: async (b) => Boolean(b) && isNotFalsy(b), }) -export const integer = custom({ +export const integer = custom({ async parse(input, _, opts) { - if (!/^-?\d+$/.test(input)) - throw new Error(`Expected an integer but received: ${input}`) + if (!/^-?\d+$/.test(input)) throw new Error(`Expected an integer but received: ${input}`) const num = Number.parseInt(input, 10) if (opts.min !== undefined && num < opts.min) throw new Error(`Expected an integer greater than or equal to ${opts.min} but received: ${input}`) diff --git a/src/cli-ux/action/base.ts b/src/cli-ux/action/base.ts index a29bfc809..38aa2cdb9 100644 --- a/src/cli-ux/action/base.ts +++ b/src/cli-ux/action/base.ts @@ -4,9 +4,9 @@ import {castArray} from '../../util' import {inspect} from 'node:util' export interface ITask { - action: string; - status: string | undefined; - active: boolean; + action: string + status: string | undefined + active: boolean } export type ActionType = 'spinner' | 'simple' | 'debug' @@ -45,8 +45,8 @@ export class ActionBase { this._stdout(false) } - private get globals(): { action: { task?: ITask }; output: string | undefined } { - (global as any).ux = (global as any).ux || {} + private get globals(): {action: {task?: ITask}; output: string | undefined} { + ;(global as any).ux = (global as any).ux || {} const globals = (global as any).ux globals.action = globals.action || {} return globals @@ -201,19 +201,19 @@ export class ActionBase { // write to the real stdout/stderr protected _write(std: 'stdout' | 'stderr', s: string | string[]): void { switch (std) { - case 'stdout': { - this.stdmockOrigs.stdout.apply(stdout, castArray(s) as [string]) - break - } + case 'stdout': { + this.stdmockOrigs.stdout.apply(stdout, castArray(s) as [string]) + break + } - case 'stderr': { - this.stdmockOrigs.stderr.apply(stderr, castArray(s) as [string]) - break - } + case 'stderr': { + this.stdmockOrigs.stderr.apply(stderr, castArray(s) as [string]) + break + } - default: { - throw new Error(`invalid std: ${std}`) - } + default: { + throw new Error(`invalid std: ${std}`) + } } } } diff --git a/src/cli-ux/action/spinner.ts b/src/cli-ux/action/spinner.ts index 7ac05c9ef..b85caf7f3 100644 --- a/src/cli-ux/action/spinner.ts +++ b/src/cli-ux/action/spinner.ts @@ -7,6 +7,8 @@ import {errtermwidth} from '../../screen' import spinners from './spinners' import stripAnsi from 'strip-ansi' +const ansiEscapes = require('ansi-escapes') + function color(s: string): string { if (!supportsColor) return s const has256 = supportsColor.stdout ? supportsColor.stdout.has256 : (process.env.TERM || '').includes('256') @@ -34,10 +36,10 @@ export default class SpinnerAction extends ActionBase { this._reset() if (this.spinner) clearInterval(this.spinner) this._render() - this.spinner = setInterval(icon => - this._render.bind(this)(icon), - process.platform === 'win32' ? 500 : 100, - 'spinner', + this.spinner = setInterval( + (icon) => this._render.bind(this)(icon), + process.platform === 'win32' ? 500 : 100, + 'spinner', ) const interval = this.spinner interval.unref() @@ -82,15 +84,12 @@ export default class SpinnerAction extends ActionBase { private _reset() { if (!this.output) return - const ansiEscapes = require('ansi-escapes') const lines = this._lines(this.output) this._write(this.std, ansiEscapes.cursorLeft + ansiEscapes.cursorUp(lines) + ansiEscapes.eraseDown) this.output = undefined } private _lines(s: string): number { - return (stripAnsi(s).split('\n') as any[]) - .map(l => Math.ceil(l.length / errtermwidth)) - .reduce((c, i) => c + i, 0) + return (stripAnsi(s).split('\n') as any[]).map((l) => Math.ceil(l.length / errtermwidth)).reduce((c, i) => c + i, 0) } } diff --git a/src/cli-ux/action/spinners.ts b/src/cli-ux/action/spinners.ts index d38cc75f7..e7ba12bad 100644 --- a/src/cli-ux/action/spinners.ts +++ b/src/cli-ux/action/spinners.ts @@ -157,7 +157,7 @@ export default { }, flip: { interval: 70, - frames: ['_', '_', '_', '-', '`', '`', '\'', 'ÂŽ', '-', '_', '_', '_'], + frames: ['_', '_', '_', '-', '`', '`', "'", 'ÂŽ', '-', '_', '_', '_'], }, hamburger: { interval: 100, diff --git a/src/cli-ux/action/types.ts b/src/cli-ux/action/types.ts index 0e5eb9179..7fe562ed1 100644 --- a/src/cli-ux/action/types.ts +++ b/src/cli-ux/action/types.ts @@ -1,6 +1,6 @@ import spinners from './spinners' export type Options = { - stdout?: boolean; - style?: keyof typeof spinners; + stdout?: boolean + style?: keyof typeof spinners } diff --git a/src/cli-ux/config.ts b/src/cli-ux/config.ts index 5718f9775..bbf2e5483 100644 --- a/src/cli-ux/config.ts +++ b/src/cli-ux/config.ts @@ -7,20 +7,20 @@ import spinner from './action/spinner' export type Levels = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' export interface ConfigMessage { - type: 'config'; - prop: string; - value: any; + type: 'config' + prop: string + value: any } 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' -) || 'simple' +const actionType = + (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/exit.ts b/src/cli-ux/exit.ts index a7ce32f27..8d154caef 100644 --- a/src/cli-ux/exit.ts +++ b/src/cli-ux/exit.ts @@ -1,6 +1,6 @@ export class ExitError extends Error { public ux: { - exit: number; + exit: number } public code: 'EEXIT' diff --git a/src/cli-ux/flush.ts b/src/cli-ux/flush.ts index 33abd784f..63713eea1 100644 --- a/src/cli-ux/flush.ts +++ b/src/cli-ux/flush.ts @@ -3,7 +3,7 @@ import {stdout} from './stream' function timeout(p: Promise, ms: number) { function wait(ms: number, unref = false) { - return new Promise(resolve => { + return new Promise((resolve) => { const t: any = setTimeout(() => resolve(null), ms) if (unref) t.unref() }) @@ -13,7 +13,7 @@ function timeout(p: Promise, ms: number) { } async function _flush() { - const p = new Promise(resolve => { + const p = new Promise((resolve) => { stdout.once('drain', () => resolve(null)) }) const flushed = stdout.write('') diff --git a/src/cli-ux/global.d.ts b/src/cli-ux/global.d.ts index 08e1e9e13..503b2265d 100644 --- a/src/cli-ux/global.d.ts +++ b/src/cli-ux/global.d.ts @@ -1,5 +1,5 @@ declare namespace NodeJS { interface Global { - ux: any; + ux: any } } diff --git a/src/cli-ux/index.ts b/src/cli-ux/index.ts index 1b7811f7a..96257a1e2 100644 --- a/src/cli-ux/index.ts +++ b/src/cli-ux/index.ts @@ -1,4 +1,3 @@ - import * as Errors from '../errors' import * as styled from './styled' import * as uxPrompt from './prompt' @@ -169,7 +168,7 @@ const uxProcessExitHandler = async () => { // to avoid MaxListenersExceededWarning // only attach named listener once -const uxListener = process.listeners('exit').find(fn => fn.name === uxProcessExitHandler.name) +const uxListener = process.listeners('exit').find((fn) => fn.name === uxProcessExitHandler.name) if (!uxListener) { process.once('exit', uxProcessExitHandler) } diff --git a/src/cli-ux/list.ts b/src/cli-ux/list.ts index 46773cb7d..af08c1cc1 100644 --- a/src/cli-ux/list.ts +++ b/src/cli-ux/list.ts @@ -15,8 +15,8 @@ export function renderList(items: IListItem[]): string { return '' } - const maxLength = maxBy(items, item => item[0].length)?.[0].length ?? 0 - const lines = items.map(i => { + const maxLength = maxBy(items, (item) => item[0].length)?.[0].length ?? 0 + const lines = items.map((i) => { let left = i[0] let right = i[1] if (!right) { diff --git a/src/cli-ux/prompt.ts b/src/cli-ux/prompt.ts index 9af137f84..a218e1231 100644 --- a/src/cli-ux/prompt.ts +++ b/src/cli-ux/prompt.ts @@ -4,24 +4,24 @@ import {config} from './config' import {stderr} from './stream' export interface IPromptOptions { - prompt?: string; - type?: 'normal' | 'mask' | 'hide' | 'single'; - timeout?: number; + prompt?: string + type?: 'normal' | 'mask' | 'hide' | 'single' + timeout?: number /** * Requires user input if true, otherwise allows empty input */ - required?: boolean; - default?: string; + required?: boolean + default?: string } interface IPromptConfig { - name: string; - prompt: string; - type: 'normal' | 'mask' | 'hide' | 'single'; - isTTY: boolean; - required: boolean; - default?: string; - timeout?: number; + name: string + prompt: string + type: 'normal' | 'mask' | 'hide' | 'single' + isTTY: boolean + required: boolean + default?: string + timeout?: number } function normal(options: IPromptConfig, retries = 100): Promise { @@ -39,14 +39,14 @@ function normal(options: IPromptConfig, retries = 100): Promise { process.stdin.setEncoding('utf8') stderr.write(options.prompt) process.stdin.resume() - process.stdin.once('data', b => { + process.stdin.once('data', (b) => { if (timer) clearTimeout(timer) process.stdin.pause() const data: string = (typeof b === 'string' ? b : b.toString()).trim() if (!options.default && options.required && data === '') { resolve(normal(options, retries - 1)) } else { - resolve(data || options.default as string) + resolve(data || (options.default as string)) } }) }) @@ -76,8 +76,15 @@ 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 { @@ -94,36 +101,36 @@ async function _prompt(name: string, inputOptions: Partial = {}) const passwordPrompt = require('password-prompt') switch (options.type) { - case 'normal': { - return normal(options) - } + case 'normal': { + return normal(options) + } - case 'single': { - return single(options) - } + case 'single': { + return single(options) + } - case 'mask': { - return passwordPrompt(options.prompt, { - method: options.type, - required: options.required, - default: options.default, - }).then((value: string) => { - replacePrompt(getPrompt(name, 'hide', inputOptions.default)) - return value - }) - } + case 'mask': { + return passwordPrompt(options.prompt, { + method: options.type, + required: options.required, + default: options.default, + }).then((value: string) => { + replacePrompt(getPrompt(name, 'hide', inputOptions.default)) + return value + }) + } - case 'hide': { - return passwordPrompt(options.prompt, { - method: options.type, - required: options.required, - default: options.default, - }) - } + case 'hide': { + return passwordPrompt(options.prompt, { + method: options.type, + required: options.required, + default: options.default, + }) + } - default: { - throw new Error(`unexpected type ${options.type}`) - } + default: { + throw new Error(`unexpected type ${options.type}`) + } } } diff --git a/src/cli-ux/styled/index.ts b/src/cli-ux/styled/index.ts index 93ce7faa2..1340c6e7f 100644 --- a/src/cli-ux/styled/index.ts +++ b/src/cli-ux/styled/index.ts @@ -1,4 +1,3 @@ - export * as Table from './table' export {default as progress} from './progress' export {default as styledJSON} from './json' diff --git a/src/cli-ux/styled/object.ts b/src/cli-ux/styled/object.ts index 96345c016..272f66bcc 100644 --- a/src/cli-ux/styled/object.ts +++ b/src/cli-ux/styled/object.ts @@ -3,20 +3,21 @@ import {inspect} from 'node:util' export default function styledObject(obj: any, keys?: string[]): string { const output: string[] = [] - const keyLengths = Object.keys(obj).map(key => key.toString().length) + const keyLengths = Object.keys(obj).map((key) => key.toString().length) const maxKeyLength = Math.max(...keyLengths) + 2 function pp(obj: any) { if (typeof obj === 'string' || typeof obj === 'number') return obj if (typeof obj === 'object') { return Object.keys(obj) - .map(k => k + ': ' + inspect(obj[k])) - .join(', ') + .map((k) => k + ': ' + inspect(obj[k])) + .join(', ') } return inspect(obj) } - const logKeyValue = (key: string, value: any): string => `${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 dc4f7f7d6..e22ac2f97 100644 --- a/src/cli-ux/styled/table.ts +++ b/src/cli-ux/styled/table.ts @@ -11,11 +11,15 @@ import {stdtermwidth} from '../../screen' import sw from 'string-width' class Table> { - options: table.Options & { printLine(s: any): any } + options: table.Options & {printLine(s: any): any} - columns: (table.Column & { key: string; width?: number; maxWidth?: number })[] + columns: (table.Column & {key: string; width?: number; maxWidth?: number})[] - constructor(private data: T[], columns: table.Columns, options: table.Options = {}) { + constructor( + private data: T[], + columns: table.Columns, + options: table.Options = {}, + ) { // assign columns this.columns = Object.keys(columns).map((key: string) => { const col = columns[key] @@ -52,7 +56,7 @@ class Table> { display() { // build table rows from input array data - let rows = this.data.map(d => { + let rows = this.data.map((d) => { const row: any = {} for (const col of this.columns) { let val = col.get(d) @@ -81,9 +85,9 @@ class Table> { // sort rows 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 => ((v: any) => v[c.key])) - const sortKeysOrder = sorters.map(k => k[0] === '-' ? 'desc' : 'asc') + const sortHeaders = sorters.map((k) => (k[0] === '-' ? k.slice(1) : k)) + 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) } @@ -93,43 +97,47 @@ class Table> { this.columns = this.filterColumnsFromHeaders(filters) } else if (!this.options.extended) { // show extented columns/properties - this.columns = this.columns.filter(c => !c.extended) + this.columns = this.columns.filter((c) => !c.extended) } this.data = rows switch (this.options.output) { - case 'csv': { - this.outputCSV() - break - } + case 'csv': { + this.outputCSV() + break + } - case 'json': { - this.outputJSON() - break - } + case 'json': { + this.outputJSON() + break + } - case 'yaml': { - this.outputYAML() - break - } + case 'yaml': { + this.outputYAML() + break + } - default: { - this.outputTable() - } + default: { + this.outputTable() + } } } - private findColumnFromHeader(header: string): (table.Column & { key: string; width?: number; maxWidth?: number }) | undefined { - return this.columns.find(c => c.header.toLowerCase() === header.toLowerCase()) + private findColumnFromHeader( + header: string, + ): (table.Column & {key: string; width?: number; maxWidth?: number}) | undefined { + return this.columns.find((c) => c.header.toLowerCase() === header.toLowerCase()) } - private filterColumnsFromHeaders(filters: string[]): (table.Column & { key: string; width?: number; maxWidth?: number })[] { + private filterColumnsFromHeaders( + filters: string[], + ): (table.Column & {key: string; width?: number; maxWidth?: number})[] { // unique - filters = [...(new Set(filters))] + filters = [...new Set(filters)] const cols: (table.Column & {key: string; width?: number; maxWidth?: number})[] = [] for (const f of filters) { - const c = this.columns.find(c => c.header.toLowerCase() === f.toLowerCase()) + const c = this.columns.find((c) => c.header.toLowerCase() === f.toLowerCase()) if (c) cols.push(c) } @@ -137,17 +145,16 @@ class Table> { } private getCSVRow(d: any): string[] { - const values = this.columns.map(col => d[col.key] || '') - const lineToBeEscaped = values.find((e: string) => e.includes('"') || e.includes('\n') || e.includes('\r\n') || e.includes('\r') || e.includes(',')) - return values.map(e => lineToBeEscaped ? `"${e.replace('"', '""')}"` : e) + const values = this.columns.map((col) => d[col.key] || '') + const lineToBeEscaped = values.find( + (e: string) => e.includes('"') || e.includes('\n') || e.includes('\r\n') || e.includes('\r') || e.includes(','), + ) + return values.map((e) => (lineToBeEscaped ? `"${e.replace('"', '""')}"` : e)) } private resolveColumnsToObjectArray() { const {data, columns} = this - return data.map((d: any) => - - Object.fromEntries(columns.map(col => [col.key, d[col.key] ?? ''])), - ) + return data.map((d: any) => Object.fromEntries(columns.map((col) => [col.key, d[col.key] ?? '']))) } private outputJSON() { @@ -162,7 +169,7 @@ class Table> { const {data, columns, options} = this if (!options['no-header']) { - options.printLine(columns.map(c => c.header).join(',')) + options.printLine(columns.map((c) => c.header).join(',')) } for (const d of data) { @@ -176,7 +183,7 @@ class Table> { // column truncation // // find max width for each column - const columns = this.columns.map(c => { + const columns = this.columns.map((c) => { const maxWidth = Math.max(sw('.'.padEnd(c.minWidth! - 1)), sw(c.header), getWidestColumnWith(data, c.key)) + 1 return { ...c, @@ -193,7 +200,7 @@ class Table> { if (options['no-truncate'] || (!stdout.isTTY && !process.env.CLI_UX_SKIP_TTY_CHECK)) return // don't shorten if there is enough screen width - const dataMaxWidth = sumBy(columns, c => c.width!) + const dataMaxWidth = sumBy(columns, (c) => c.width!) const overWidth = dataMaxWidth - maxWidth if (overWidth <= 0) return @@ -205,15 +212,17 @@ class Table> { // if sum(minWidth's) is greater than term width // nothing can be done so // display all as minWidth - const dataMinWidth = sumBy(columns, c => c.minWidth!) + const dataMinWidth = sumBy(columns, (c) => c.minWidth!) if (dataMinWidth >= maxWidth) return // some wiggle room left, add it back to "needy" columns let wiggleRoom = maxWidth - dataMinWidth - const needyCols = columns.map(c => ({key: c.key, needs: c.maxWidth! - c.width!})).sort((a, b) => a.needs - b.needs) + const needyCols = columns + .map((c) => ({key: c.key, needs: c.maxWidth! - c.width!})) + .sort((a, b) => a.needs - b.needs) for (const {key, needs} of needyCols) { if (!needs) continue - const col = columns.find(c => key === c.key) + const col = columns.find((c) => key === c.key) if (!col) continue if (wiggleRoom > needs) { col.width = col.width! + needs @@ -231,7 +240,12 @@ class Table> { if (options.title) { options.printLine(options.title) // print title divider - options.printLine(''.padEnd(columns.reduce((sum, col) => sum + col.width!, 1), '=')) + options.printLine( + ''.padEnd( + columns.reduce((sum, col) => sum + col.width!, 1), + '=', + ), + ) options.rowStart = '| ' } @@ -280,9 +294,9 @@ class Table> { let d = (row as any)[col.key] d = d.split('\n')[i] || '' const visualWidth = sw(d) - const colorWidth = (d.length - visualWidth) + const colorWidth = d.length - visualWidth let cell = d.padEnd(width + colorWidth) - if ((cell.length - colorWidth) > width || visualWidth === width) { + if (cell.length - colorWidth > width || visualWidth === width) { // truncate the cell, preserving ANSI escape sequences, and keeping // into account the width of fullwidth unicode characters cell = sliceAnsi(cell, 0, width - 2) + '
 ' @@ -300,23 +314,27 @@ class Table> { } } -export function table>(data: T[], columns: table.Columns, options: table.Options = {}): void { +export function table>( + data: T[], + columns: table.Columns, + options: table.Options = {}, +): void { new Table(data, columns, options).display() } export namespace table { export const Flags: { - columns: Interfaces.OptionFlag; - sort: Interfaces.OptionFlag; - filter: Interfaces.OptionFlag; - csv: Interfaces.BooleanFlag; - output: Interfaces.OptionFlag; - extended: Interfaces.BooleanFlag; - 'no-truncate': Interfaces.BooleanFlag; - 'no-header': Interfaces.BooleanFlag; + columns: Interfaces.OptionFlag + sort: Interfaces.OptionFlag + filter: Interfaces.OptionFlag + csv: Interfaces.BooleanFlag + output: Interfaces.OptionFlag + extended: Interfaces.BooleanFlag + 'no-truncate': Interfaces.BooleanFlag + 'no-header': Interfaces.BooleanFlag } = { columns: F.string({exclusive: ['extended'], description: 'only show provided columns (comma-separated)'}), - sort: F.string({description: 'property to sort by (prepend \'-\' for descending)'}), + sort: F.string({description: "property to sort by (prepend '-' for descending)"}), filter: F.string({description: 'filter property by partial string matching, ex: name=foo'}), csv: F.boolean({exclusive: ['no-truncate'], description: 'output is csv format [alias: --output=csv]'}), output: F.string({ @@ -334,8 +352,8 @@ export namespace table { type IncludeFlags = Pick export function flags(): IFlags - export function flags(opts: { except: Z | Z[] }): ExcludeFlags - export function flags(opts: { only: K | K[] }): IncludeFlags + export function flags(opts: {except: Z | Z[]}): ExcludeFlags + export function flags(opts: {only: K | K[]}): IncludeFlags export function flags(opts?: any): any { if (opts) { @@ -344,7 +362,7 @@ export namespace table { const e = (opts.except && typeof opts.except === 'string' ? [opts.except] : opts.except) || [] for (const key of o) { if (!(e as any[]).includes(key)) { - (f as any)[key] = (Flags as any)[key] + ;(f as any)[key] = (Flags as any)[key] } } @@ -355,33 +373,34 @@ export namespace table { } export interface Column> { - header: string; - extended: boolean; - minWidth: number; - get(row: T): any; + header: string + extended: boolean + minWidth: number + get(row: T): any } - export type Columns> = { [key: string]: Partial> } + export type Columns> = {[key: string]: Partial>} // export type OutputType = 'csv' | 'json' | 'yaml' export interface Options { - [key: string]: any; - sort?: string; - filter?: string; - columns?: string; - extended?: boolean; - 'no-truncate'?: boolean; - output?: string; - 'no-header'?: boolean; - printLine?(s: any): any; + [key: string]: any + sort?: string + filter?: string + columns?: string + extended?: boolean + 'no-truncate'?: boolean + output?: string + 'no-header'?: boolean + printLine?(s: any): any } } -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) +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/styled/tree.ts b/src/cli-ux/styled/tree.ts index 75b2dbc4e..916261506 100644 --- a/src/cli-ux/styled/tree.ts +++ b/src/cli-ux/styled/tree.ts @@ -1,7 +1,7 @@ const treeify = require('object-treeify') export class Tree { - nodes: { [key: string]: Tree } = {} + nodes: {[key: string]: Tree} = {} insert(child: string, value: Tree = new Tree()): Tree { this.nodes[child] = value @@ -21,7 +21,7 @@ export class Tree { display(logger: any = console.log): void { const addNodes = function (nodes: any) { - const tree: { [key: string]: any } = {} + const tree: {[key: string]: any} = {} for (const p of Object.keys(nodes)) { tree[p] = addNodes(nodes[p].nodes) } diff --git a/src/cli-ux/wait.ts b/src/cli-ux/wait.ts index 7514385ff..ecf28356f 100644 --- a/src/cli-ux/wait.ts +++ b/src/cli-ux/wait.ts @@ -1,3 +1,4 @@ -export default (ms = 1000): Promise => 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 9b365af69..a0480bc59 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1,4 +1,3 @@ - import * as Errors from './errors' import * as Parser from './parser' import { @@ -37,8 +36,7 @@ const pjson = requireJson(__dirname, '..', 'package.json') * this occurs when stdout closes such as when piping to head */ stdout.on('error', (err: any) => { - if (err && err.code === 'EPIPE') - return + if (err && err.code === 'EPIPE') return throw err }) @@ -128,7 +126,11 @@ export abstract class Command { * @param {LoadOptions} opts options * @returns {Promise} result */ - public static async run(this: new(argv: string[], config: Config) => T, argv?: string[], opts?: LoadOptions): Promise> { + public static async run( + this: new (argv: string[], config: Config) => T, + argv?: string[], + opts?: LoadOptions, + ): Promise> { if (!argv) argv = process.argv.slice(2) // Handle the case when a file URL string is passed in such as 'import.meta.url'; covert to file path. @@ -156,7 +158,10 @@ export abstract class Command { protected debug: (...args: any[]) => void - public constructor(public argv: string[], public config: Config) { + public constructor( + public argv: string[], + public config: Config, + ) { this.id = this.ctor.id try { this.debug = require('debug')(this.id ? `${this.config.bin}:${this.id}` : this.config.bin) @@ -204,7 +209,10 @@ export abstract class Command { public error(input: string | Error, options?: {code?: string; exit?: number} & PrettyPrintableError): never - public error(input: string | Error, options: {code?: string; exit?: number | false} & PrettyPrintableError = {}): void { + public error( + input: string | Error, + options: {code?: string; exit?: number | false} & PrettyPrintableError = {}, + ): void { return Errors.error(input, options as any) } @@ -237,10 +245,10 @@ export abstract class Command { const passThroughIndex = this.argv.indexOf('--') const jsonIndex = this.argv.indexOf('--json') return passThroughIndex === -1 - // If '--' is not present, then check for `--json` in this.argv - ? jsonIndex > -1 - // If '--' is present, return true only the --json flag exists and is before the '--' - : jsonIndex > -1 && jsonIndex < passThroughIndex + ? // If '--' is not present, then check for `--json` in this.argv + jsonIndex > -1 + : // If '--' is present, return true only the --json flag exists and is before the '--' + jsonIndex > -1 && jsonIndex < passThroughIndex } /** @@ -259,11 +267,7 @@ export abstract class Command { } protected warnIfFlagDeprecated(flags: Record): void { - const allFlags = aggregateFlags( - this.ctor.flags, - this.ctor.baseFlags, - this.ctor.enableJsonFlag, - ) + const allFlags = aggregateFlags(this.ctor.flags, this.ctor.baseFlags, this.ctor.enableJsonFlag) for (const flag of Object.keys(flags)) { const flagDef = allFlags[flag] const deprecated = flagDef?.deprecated @@ -273,10 +277,12 @@ export abstract class Command { const deprecateAliases = flagDef?.deprecateAliases if (deprecateAliases) { - const aliases = uniq([...flagDef?.aliases ?? [], ...flagDef?.charAliases ?? []]).map(a => a.length === 1 ? `-${a}` : `--${a}`) + 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))) + const foundAliases = aliases.filter((alias) => this.argv.some((a) => a.startsWith(alias))) for (const alias of foundAliases) { let preferredUsage = `--${flagDef?.name}` if (flagDef?.char) { @@ -313,11 +319,7 @@ export abstract class Command { const opts = { context: this, ...options, - flags: aggregateFlags( - options.flags, - options.baseFlags, - options.enableJsonFlag, - ), + flags: aggregateFlags(options.flags, options.baseFlags, options.enableJsonFlag), } const results = await Parser.parse(argv, opts) @@ -369,14 +371,14 @@ export abstract class Command { keys.push(this.config.scopedEnvVarKey(envVar)) } - keys.map(key => delete process.env[key]) + keys.map((key) => delete process.env[key]) } } export namespace Command { export type Class = typeof Command & { - id: string; - run(argv?: string[], config?: LoadOptions): Promise; + id: string + run(argv?: string[], config?: LoadOptions): Promise } export interface Loadable extends Cached { @@ -384,34 +386,35 @@ export namespace Command { } export type Cached = { - [key: string]: unknown; - id: string; - hidden: boolean; - state?: 'beta' | 'deprecated' | string; - deprecationOptions?: Deprecation; - aliases: string[]; - summary?: string; - description?: string; - usage?: string | string[]; - examples?: Example[]; - strict?: boolean; - type?: string; - pluginName?: string; - pluginType?: string; - pluginAlias?: string; - flags: {[name: string]: Flag.Cached}; - args: {[name: string]: Arg.Cached}; - hasDynamicHelp?: boolean; + [key: string]: unknown + id: string + hidden: boolean + state?: 'beta' | 'deprecated' | string + deprecationOptions?: Deprecation + aliases: string[] + summary?: string + description?: string + usage?: string | string[] + examples?: Example[] + strict?: boolean + type?: string + pluginName?: string + pluginType?: string + pluginAlias?: string + flags: {[name: string]: Flag.Cached} + args: {[name: string]: Arg.Cached} + hasDynamicHelp?: boolean permutations?: string[] - aliasPermutations?: string[]; - isESM?: boolean; - relativePath?: string[]; + aliasPermutations?: string[] + isESM?: boolean + relativePath?: string[] } export type Flag = IFlag export namespace Flag { - export type Cached = Omit & (BooleanFlagProps | OptionFlagProps) & {hasDynamicHelp?: boolean} + export type Cached = Omit & + (BooleanFlagProps | OptionFlagProps) & {hasDynamicHelp?: boolean} export type Any = Flag | Cached } @@ -422,8 +425,10 @@ export namespace Command { export type Any = Arg | Cached } - export type Example = string | { - description: string; - command: string; - } + export type Example = + | string + | { + description: string + command: string + } } diff --git a/src/config/config.ts b/src/config/config.ts index 41795375d..66da7a0db 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -93,7 +93,7 @@ export class Config implements IConfig { public version!: string public windows!: boolean public binAliases?: string[] - public nsisCustomization?:string + public nsisCustomization?: string protected warned = false @@ -151,7 +151,8 @@ 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 + 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() @@ -167,7 +168,7 @@ export class Config implements IConfig { this.channel = this.options.channel || channelFromVersion(this.version) this.valid = Config._rootPlugin.valid - this.arch = (arch() === 'ia32' ? 'x86' : arch() as any) + this.arch = arch() === 'ia32' ? 'x86' : (arch() as any) this.platform = WSL ? 'wsl' : getPlatform() this.windows = this.platform === 'win32' this.bin = this.pjson.oclif.bin || this.name @@ -176,7 +177,8 @@ export class Config implements IConfig { this.dirname = this.pjson.oclif.dirname || this.name this.flexibleTaxonomy = this.pjson.oclif.flexibleTaxonomy || false // currently, only colons or spaces are valid separators - if (this.pjson.oclif.topicSeparator && [':', ' '].includes(this.pjson.oclif.topicSeparator)) this.topicSeparator = this.pjson.oclif.topicSeparator! + if (this.pjson.oclif.topicSeparator && [':', ' '].includes(this.pjson.oclif.topicSeparator)) + this.topicSeparator = this.pjson.oclif.topicSeparator! if (this.platform === 'win32') this.dirname = this.dirname.replace('/', '\\') this.userAgent = `${this.name}/${this.version} ${this.platform}-${this.arch} node-${process.version}` this.shell = this._shell() @@ -201,17 +203,20 @@ export class Config implements IConfig { ...s3.templates, target: { baseDir: '<%- bin %>', - unversioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-<%- platform %>-<%- arch %><%- ext %>", - versioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-v<%- version %>/<%- bin %>-v<%- version %>-<%- platform %>-<%- arch %><%- ext %>", + unversioned: + "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-<%- platform %>-<%- arch %><%- ext %>", + versioned: + "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-v<%- version %>/<%- bin %>-v<%- version %>-<%- platform %>-<%- arch %><%- ext %>", manifest: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- platform %>-<%- arch %>", - ...s3.templates && s3.templates.target, + ...(s3.templates && s3.templates.target), }, vanilla: { unversioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %><%- ext %>", - versioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-v<%- version %>/<%- bin %>-v<%- version %><%- ext %>", + versioned: + "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-v<%- version %>/<%- bin %>-v<%- version %><%- ext %>", baseDir: '<%- bin %>', manifest: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %>version", - ...s3.templates && s3.templates.vanilla, + ...(s3.templates && s3.templates.vanilla), }, } @@ -275,7 +280,7 @@ export class Config implements IConfig { }, ms).unref() }) - return Promise.race([promise, timeout]).then(result => { + return Promise.race([promise, timeout]).then((result) => { clearTimeout(id) return result }) @@ -285,7 +290,7 @@ export class Config implements IConfig { successes: [], failures: [], } as Hook.Result - const promises = [...this.plugins.values()].map(async p => { + const promises = [...this.plugins.values()].map(async (p) => { const debug = require('debug')([this.bin, p.name, 'hooks', event].join(':')) const context: Hook.Context = { config: this, @@ -296,7 +301,7 @@ export class Config implements IConfig { log(message?: any, ...args: any[]) { stdout.write(format(message, ...args) + '\n') }, - error(message, options: { code?: string; exit?: number } = {}) { + error(message, options: {code?: string; exit?: number} = {}) { error(message, options) }, warn(message: string) { @@ -314,8 +319,8 @@ export class Config implements IConfig { 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}) + ? 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') { @@ -346,15 +351,20 @@ export class Config implements IConfig { return final } - public async runCommand(id: string, argv: string[] = [], cachedCommand: Command.Loadable | null = null): Promise { + public async runCommand( + id: string, + argv: string[] = [], + cachedCommand: Command.Loadable | null = null, + ): Promise { const marker = Performance.mark(`config.runCommand#${id}`) debug('runCommand %s %o', id, argv) 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 @@ -396,11 +406,11 @@ export class Config implements IConfig { } public scopedEnvVar(k: string): string | undefined { - return process.env[this.scopedEnvVarKeys(k).find(k => process.env[k]) as string] + return process.env[this.scopedEnvVarKeys(k).find((k) => process.env[k]) as string] } public scopedEnvVarTrue(k: string): boolean { - const v = process.env[this.scopedEnvVarKeys(k).find(k => process.env[k]) as string] + const v = process.env[this.scopedEnvVarKeys(k).find((k) => process.env[k]) as string] return v === '1' || v === 'true' } @@ -411,9 +421,9 @@ export class Config implements IConfig { */ public scopedEnvVarKey(k: string): string { return [this.bin, k] - .map(p => p.replaceAll('@', '').replaceAll(/[/-]/g, '_')) - .join('_') - .toUpperCase() + .map((p) => p.replaceAll('@', '').replaceAll(/[/-]/g, '_')) + .join('_') + .toUpperCase() } /** @@ -422,26 +432,27 @@ export class Config implements IConfig { * @returns {string[]} e.g. ['SF_DEBUG', 'SFDX_DEBUG'] */ public scopedEnvVarKeys(k: string): string[] { - return [this.bin, ...this.binAliases ?? []].filter(Boolean).map(alias => - [alias.replaceAll('@', '').replaceAll(/[/-]/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 + public findCommand(id: string, opts: {must: true}): Command.Loadable - public findCommand(id: string, opts?: { must: boolean }): Command.Loadable | undefined + public findCommand(id: string, opts?: {must: boolean}): Command.Loadable | undefined - public findCommand(id: string, opts: { must?: boolean } = {}): Command.Loadable | undefined { + public findCommand(id: string, opts: {must?: boolean} = {}): Command.Loadable | undefined { const lookupId = this.getCmdLookupId(id) const command = this._commands.get(lookupId) if (opts.must && !command) error(`command ${lookupId} not found`) return command } - public findTopic(id: string, opts: { must: true }): Topic + public findTopic(id: string, opts: {must: true}): Topic - public findTopic(id: string, opts?: { must: boolean }): Topic | undefined + public findTopic(id: string, opts?: {must: boolean}): Topic | undefined - public findTopic(name: string, opts: { must?: boolean } = {}): Topic | undefined { + public findTopic(name: string, opts: {must?: boolean} = {}): Topic | undefined { const lookupId = this.getTopicLookupId(name) const topic = this._topics.get(lookupId) if (topic) return topic @@ -462,14 +473,18 @@ 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.replaceAll('-', '')) - const possibleMatches = [...this.commandPermutations.get(partialCmdId)].map(k => this._commands.get(k)!) + 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]) => def.char ? [def.char, flag] : [flag]) as string[] + const matches = possibleMatches.filter((command) => { + 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)) + return flags.every((f) => cmdFlags.includes(f)) }) return matches @@ -497,7 +512,7 @@ export class Config implements IConfig { * @returns string[] */ public getAllCommandIDs(): string[] { - return this.getAllCommands().map(c => c.id) + return this.getAllCommands().map((c) => c.id) } public get commands(): Command.Loadable[] { @@ -506,7 +521,7 @@ export class Config implements IConfig { public get commandIDs(): string[] { if (this._commandIDs) return this._commandIDs - this._commandIDs = this.commands.map(c => c.id) + this._commandIDs = this.commands.map((c) => c.id) return this._commandIDs } @@ -520,18 +535,24 @@ export class Config implements IConfig { cliVersion, architecture, nodeVersion, - pluginVersions: Object.fromEntries([...this.plugins.values()].map(p => [p.name, {version: p.version, type: p.type, root: p.root}])), + pluginVersions: Object.fromEntries( + [...this.plugins.values()].map((p) => [p.name, {version: p.version, type: p.type, root: p.root}]), + ), osVersion: `${type()} ${release()}`, shell: this.shell, rootPath: this.root, } } - public s3Key(type: keyof PJSON.S3.Templates, ext?: '.tar.gz' | '.tar.xz' | IConfig.s3Key.Options, options: IConfig.s3Key.Options = {}): string { + public s3Key( + type: keyof PJSON.S3.Templates, + ext?: '.tar.gz' | '.tar.xz' | IConfig.s3Key.Options, + options: IConfig.s3Key.Options = {}, + ): string { if (typeof ext === 'object') options = ext else if (ext) options.ext = ext const template = this.pjson.oclif.update.s3.templates[options.platform ? 'target' : 'vanilla'][type] ?? '' - return ejs.render(template, {...this as any, ...options}) + return ejs.render(template, {...(this as any), ...options}) } public s3Url(key: string): string { @@ -547,9 +568,10 @@ 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) } @@ -558,7 +580,7 @@ export class Config implements IConfig { } protected windowsHomedriveHome(): string | undefined { - return (process.env.HOMEDRIVE && process.env.HOMEPATH && join(process.env.HOMEDRIVE!, process.env.HOMEPATH!)) + return process.env.HOMEDRIVE && process.env.HOMEPATH && join(process.env.HOMEDRIVE!, process.env.HOMEPATH!) } protected windowsUserprofileHome(): string | undefined { @@ -594,7 +616,7 @@ export class Config implements IConfig { return 0 } - protected warn(err: string | Error | { name: string; detail: string }, scope?: string): void { + protected warn(err: string | Error | {name: string; detail: string}, scope?: string): void { if (this.warned) return if (typeof err === 'string') { @@ -638,7 +660,10 @@ export class Config implements IConfig { private isJitPluginCommand(c: Command.Loadable): boolean { // Return true if the command's plugin is listed under oclif.jitPlugins AND if the plugin hasn't been loaded to this.plugins - return Object.keys(this.pjson.oclif.jitPlugins ?? {}).includes(c.pluginName ?? '') && Boolean(c?.pluginName && !this.plugins.has(c.pluginName)) + return ( + Object.keys(this.pjson.oclif.jitPlugins ?? {}).includes(c.pluginName ?? '') && + Boolean(c?.pluginName && !this.plugins.has(c.pluginName)) + ) } private getCmdLookupId(id: string): string { @@ -666,9 +691,10 @@ export class Config implements IConfig { // 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] + const permutations = + this.flexibleTaxonomy && command.permutations === undefined + ? getCommandIdPermutations(command.id) + : command.permutations ?? [command.id] // set every permutation for (const permutation of permutations) { this.commandPermutations.add(permutation, command.id) @@ -687,9 +713,10 @@ export class Config implements IConfig { // 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] + 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) @@ -719,7 +746,7 @@ export class Config implements IConfig { } // Add missing topics for displaying help when partial commands are entered. - for (const c of plugin.commands.filter(c => !c.hidden)) { + for (const c of plugin.commands.filter((c) => !c.hidden)) { const parts = c.id.split(':') while (parts.length > 0) { const name = parts.join(':') @@ -793,14 +820,14 @@ export class Config implements IConfig { } /** - * Insert legacy plugins - * - * Replace invalid CLI plugins (cli-engine plugins, mostly Heroku) loaded via `this.loadPlugins` - * with oclif-compatible ones returned by @oclif/plugin-legacy init hook. - * - * @param plugins array of oclif-compatible plugins - * @returns void - */ + * Insert legacy plugins + * + * Replace invalid CLI plugins (cli-engine plugins, mostly Heroku) loaded via `this.loadPlugins` + * with oclif-compatible ones returned by @oclif/plugin-legacy init hook. + * + * @param plugins array of oclif-compatible plugins + * @returns void + */ private insertLegacyPlugins(plugins: IPlugin[]) { for (const plugin of plugins) { this.plugins.set(plugin.name, plugin) @@ -808,4 +835,3 @@ export class Config implements IConfig { } } } - diff --git a/src/config/index.ts b/src/config/index.ts index 748162de7..e3e23deca 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,4 +1,3 @@ export {Config} from './config' export {Plugin} from './plugin' export {tsPath} from './ts-node' - diff --git a/src/config/plugin-loader.ts b/src/config/plugin-loader.ts index ed008a708..a72fa8f27 100644 --- a/src/config/plugin-loader.ts +++ b/src/config/plugin-loader.ts @@ -10,16 +10,16 @@ import {join} from 'node:path' const debug = Debug() type PluginLoaderOptions = { - root: string; - plugins?: IPlugin[] | PluginsMap; + root: string + plugins?: IPlugin[] | PluginsMap } type LoadOpts = { - devPlugins?: boolean; - userPlugins?: boolean; - dataDir: string; - rootPlugin: IPlugin; - force?: boolean; + devPlugins?: boolean + userPlugins?: boolean + dataDir: string + rootPlugin: IPlugin + force?: boolean } type PluginsMap = Map @@ -32,7 +32,7 @@ export default class PluginLoader { constructor(public options: PluginLoaderOptions) { if (options.plugins) { this.pluginsProvided = true - this.plugins = Array.isArray(options.plugins) ? new Map(options.plugins.map(p => [p.name, p])) : options.plugins + this.plugins = Array.isArray(options.plugins) ? new Map(options.plugins.map((p) => [p.name, p])) : options.plugins } } @@ -40,7 +40,7 @@ export default class PluginLoader { let rootPlugin: IPlugin if (this.pluginsProvided) { const plugins = [...this.plugins.values()] - rootPlugin = plugins.find(p => p.root === this.options.root) ?? plugins[0] + 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, isRoot: true}) @@ -97,61 +97,76 @@ export default class PluginLoader { 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')) - await this.loadPlugins(userPJSONPath, 'link', pjson.oclif.plugins.filter((p: any) => p.type === 'link')) + await this.loadPlugins( + userPJSONPath, + 'user', + pjson.oclif.plugins.filter((p: any) => p.type === 'user'), + ) + await this.loadPlugins( + userPJSONPath, + 'link', + pjson.oclif.plugins.filter((p: any) => p.type === 'link'), + ) } catch (error: any) { if (error.code !== 'ENOENT') process.emitWarning(error) } } } - private async loadPlugins(root: string, type: string, plugins: (string | { root?: string; name?: string; tag?: string })[], parent?: Plugin.Plugin): Promise { + private async loadPlugins( + root: string, + type: string, + plugins: (string | {root?: string; name?: string; tag?: string})[], + parent?: Plugin.Plugin, + ): Promise { if (!plugins || plugins.length === 0) return const mark = Performance.mark(`config.loadPlugins#${type}`) debug('loading plugins', plugins) - await Promise.all((plugins || []).map(async plugin => { - try { - const name = typeof plugin === 'string' ? plugin : plugin.name! - const opts: Options = { - name, - type, - root, - } - if (typeof plugin !== 'string') { - opts.tag = plugin.tag || opts.tag - opts.root = plugin.root || opts.root + await Promise.all( + (plugins || []).map(async (plugin) => { + try { + const name = typeof plugin === 'string' ? plugin : plugin.name! + const opts: Options = { + name, + type, + root, + } + if (typeof plugin !== 'string') { + opts.tag = plugin.tag || opts.tag + 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) + await instance.load() + pluginMarker?.addDetails({ + hasManifest: instance.hasManifest, + commandCount: instance.commands.length, + topicCount: instance.topics.length, + type: instance.type, + usesMain: Boolean(instance.pjson.main), + name: instance.name, + }) + pluginMarker?.stop() + + this.plugins.set(instance.name, instance) + if (parent) { + instance.parent = parent + if (!parent.children) parent.children = [] + parent.children.push(instance) + } + + await this.loadPlugins(instance.root, type, instance.pjson.oclif.plugins || [], instance) + } catch (error: any) { + this.errors.push(error) } - - if (parent) { - opts.parent = parent - } - - if (this.plugins.has(name)) return - const pluginMarker = Performance.mark(`plugin.load#${name}`) - const instance = new Plugin.Plugin(opts) - await instance.load() - pluginMarker?.addDetails({ - hasManifest: instance.hasManifest, - commandCount: instance.commands.length, - topicCount: instance.topics.length, - type: instance.type, - usesMain: Boolean(instance.pjson.main), - name: instance.name, - }) - pluginMarker?.stop() - - this.plugins.set(instance.name, instance) - if (parent) { - instance.parent = parent - if (!parent.children) parent.children = [] - parent.children.push(instance) - } - - await this.loadPlugins(instance.root, type, instance.pjson.oclif.plugins || [], instance) - } catch (error: any) { - this.errors.push(error) - } - })) + }), + ) mark?.addDetails({pluginCount: plugins.length}) mark?.stop() diff --git a/src/config/plugin.ts b/src/config/plugin.ts index cd229e86a..da3c73ce5 100644 --- a/src/config/plugin.ts +++ b/src/config/plugin.ts @@ -1,9 +1,5 @@ import {CLIError, error} from '../errors' -import { - Debug, - getCommandIdPermutations, - resolvePackage, -} from './util' +import {Debug, getCommandIdPermutations, resolvePackage} from './util' import {Plugin as IPlugin, PluginOptions} from '../interfaces/plugin' import {compact, exists, isProd, mapValues, readJson, requireJson} from '../util' import {dirname, join, parse, relative, sep} from 'node:path' @@ -24,17 +20,17 @@ function topicsToArray(input: any, base?: string): Topic[] { if (!input) return [] base = base ? `${base}:` : '' if (Array.isArray(input)) { - return [...input, input.flatMap(t => topicsToArray(t.subtopics, `${base}${t.name}`))] + return [...input, input.flatMap((t) => topicsToArray(t.subtopics, `${base}${t.name}`))] } - return Object.keys(input).flatMap(k => { + return Object.keys(input).flatMap((k) => { input[k].name = k return [{...input[k], name: `${base}${k}`}, ...topicsToArray(input[k].subtopics, `${base}${input[k].name}`)] }) } // essentially just "cd .." -function * up(from: string) { +function* up(from: string) { while (dirname(from) !== from) { yield from from = dirname(from) @@ -96,7 +92,7 @@ async function findRoot(name: string | undefined, root: string) { } const cachedCommandCanBeUsed = (manifest: Manifest | undefined, id: string): boolean => - Boolean(manifest?.commands[id] && ('isESM' in manifest.commands[id] && 'relativePath' in manifest.commands[id])) + 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 @@ -160,7 +156,8 @@ export class Plugin implements IPlugin { // 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) + 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) @@ -181,18 +178,17 @@ export class Plugin implements IPlugin { this.pjson.oclif = this.pjson['cli-engine'] || {} } - this.hooks = mapValues(this.pjson.oclif.hooks || {}, i => Array.isArray(i) ? i : [i]) + this.hooks = mapValues(this.pjson.oclif.hooks || {}, (i) => (Array.isArray(i) ? i : [i])) this.manifest = await this._manifest() - this.commands = Object - .entries(this.manifest.commands) - .map(([id, c]) => ({ - ...c, - pluginAlias: this.alias, - pluginType: c.pluginType === 'jit' ? 'jit' : this.type, - load: async () => this.findCommand(id, {must: true}), - })) - .sort((a, b) => a.id.localeCompare(b.id)) + this.commands = Object.entries(this.manifest.commands) + .map(([id, c]) => ({ + ...c, + pluginAlias: this.alias, + pluginType: c.pluginType === 'jit' ? 'jit' : this.type, + load: async () => this.findCommand(id, {must: true}), + })) + .sort((a, b) => a.id.localeCompare(b.id)) } public get topics(): Topic[] { @@ -211,12 +207,8 @@ export class Plugin implements IPlugin { const marker = Performance.mark(`plugin.commandIDs#${this.name}`, {plugin: this.name}) this._debug(`loading IDs from ${this.commandsDir}`) - const patterns = [ - '**/*.+(js|cjs|mjs|ts|tsx)', - '!**/*.+(d.ts|test.ts|test.js|spec.ts|spec.js)?(x)', - ] - const ids = sync(patterns, {cwd: this.commandsDir}) - .map(file => { + const patterns = ['**/*.+(js|cjs|mjs|ts|tsx)', '!**/*.+(d.ts|test.ts|test.js|spec.ts|spec.js)?(x)'] + const ids = sync(patterns, {cwd: this.commandsDir}).map((file) => { const p = parse(file) const topics = p.dir.split('/') const command = p.name !== 'index' && p.name @@ -242,7 +234,7 @@ export class Plugin implements IPlugin { let isESM: boolean | undefined let filePath: string | undefined try { - ({isESM, module, filePath} = cachedCommandCanBeUsed(this.manifest, id) + ;({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) @@ -276,7 +268,9 @@ export class Plugin implements IPlugin { 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.`) + 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 { this._debug('using manifest from', p) this.hasManifest = true @@ -303,28 +297,35 @@ export class Plugin implements IPlugin { const manifest = { version: this.version, - commands: (await Promise.all(this.commandIDs.map(async id => { - try { - const cached = await cacheCommand(await this.findCommand(id, {must: true}), this, respectNoCacheDefault) - if (this.flexibleTaxonomy) { - const permutations = getCommandIdPermutations(id) - const aliasPermutations = cached.aliases.flatMap(a => getCommandIdPermutations(a)) - return [id, {...cached, permutations, aliasPermutations} as Command.Cached] - } - - return [id, cached] - } catch (error: any) { - const scope = 'cacheCommand' - if (Boolean(errorOnManifestCreate) === false) this.warn(error, scope) - 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 - return commands - }, {} as {[k: string]: Command.Cached}), + commands: ( + await Promise.all( + this.commandIDs.map(async (id) => { + try { + const cached = await cacheCommand(await this.findCommand(id, {must: true}), this, respectNoCacheDefault) + if (this.flexibleTaxonomy) { + const permutations = getCommandIdPermutations(id) + const aliasPermutations = cached.aliases.flatMap((a) => getCommandIdPermutations(a)) + return [id, {...cached, permutations, aliasPermutations} as Command.Cached] + } + + return [id, cached] + } catch (error: any) { + const scope = 'cacheCommand' + if (Boolean(errorOnManifestCreate) === false) this.warn(error, scope) + 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 + return commands + }, + {} as {[k: string]: Command.Cached}, + ), } marker?.addDetails({fromCache: false, commandCount: Object.keys(manifest.commands).length}) marker?.stop() @@ -339,8 +340,14 @@ export class Plugin implements IPlugin { private addErrorScope(err: any, scope?: string) { err.name = `${err.name} Plugin: ${this.name}` - err.detail = compact([err.detail, `module: ${this._base}`, scope && `task: ${scope}`, `plugin: ${this.name}`, `root: ${this.root}`, 'See more details with DEBUG=*']).join('\n') + err.detail = compact([ + err.detail, + `module: ${this._base}`, + scope && `task: ${scope}`, + `plugin: ${this.name}`, + `root: ${this.root}`, + 'See more details with DEBUG=*', + ]).join('\n') return err } } - diff --git a/src/config/ts-node.ts b/src/config/ts-node.ts index 80b82d47e..f94c61566 100644 --- a/src/config/ts-node.ts +++ b/src/config/ts-node.ts @@ -29,20 +29,20 @@ function loadTSConfig(root: string): TSConfig | undefined { typescript = require(join(root, 'node_modules', 'typescript')) } catch { debug(`Could not find typescript dependency. Skipping ts-node registration for ${root}.`) - memoizedWarn('Could not find typescript. Please ensure that typescript is a devDependency. Falling back to compiled source.') + memoizedWarn( + 'Could not find typescript. Please ensure that typescript is a devDependency. Falling back to compiled source.', + ) return } } if (existsSync(tsconfigPath) && typescript) { - const tsconfig = typescript.parseConfigFileTextToJson( - tsconfigPath, - readJsonSync(tsconfigPath, false), - ).config + const tsconfig = typescript.parseConfigFileTextToJson(tsconfigPath, 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 @@ -63,13 +63,13 @@ function registerTSNode(root: string): TSConfig | undefined { tsNode = require(tsNodePath) } catch { debug(`Could not find ts-node at ${tsNodePath}. Skipping ts-node registration for ${root}.`) - memoizedWarn(`Could not find ts-node at ${tsNodePath}. Please ensure that ts-node is a devDependency. Falling back to compiled source.`) + memoizedWarn( + `Could not find ts-node at ${tsNodePath}. Please ensure that ts-node is a devDependency. Falling back to compiled source.`, + ) return } - const typeRoots = [ - join(root, 'node_modules', '@types'), - ] + const typeRoots = [join(root, 'node_modules', '@types')] const rootDirs: string[] = [] @@ -153,9 +153,13 @@ export function tsPath(root: string, orig: string | undefined, plugin?: Plugin): * In other words, this allows plugins to be auto-transpiled when developing locally using `bin/dev.js`. */ if ((isProduction || ROOT_PLUGIN?.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: ${ROOT_PLUGIN?.moduleType})))`) + debug( + `Skipping ts-node registration for ${root} because it's an ESM module (NODE_ENV: ${process.env.NODE_ENV}, root plugin module type: ${ROOT_PLUGIN?.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.`) + memoizedWarn( + `${plugin.name} is a linked ESM module and cannot be auto-transpiled. Existing compiled source will be used instead.`, + ) return orig } diff --git a/src/config/util.ts b/src/config/util.ts index 482ce4554..bbcb2bed6 100644 --- a/src/config/util.ts +++ b/src/config/util.ts @@ -1,6 +1,6 @@ const debug = require('debug') -export function resolvePackage(id: string, paths: { paths: string[] }): string { +export function resolvePackage(id: string, paths: {paths: string[]}): string { return require.resolve(id, paths) } @@ -13,9 +13,10 @@ function displayWarnings() { } export function Debug(...scope: string[]): (..._: any) => void { - if (!debug) return (..._: any[]) => { - // noop - } + if (!debug) + return (..._: any[]) => { + // noop + } const d = debug(['config', ...scope].join(':')) if (d.enabled) displayWarnings() @@ -47,7 +48,7 @@ export function getPermutations(arr: string[]): Array { } export function getCommandIdPermutations(commandId: string): string[] { - return getPermutations(commandId.split(':')).flatMap(c => c.join(':')) + return getPermutations(commandId.split(':')).flatMap((c) => c.join(':')) } /** @@ -70,4 +71,4 @@ export function getCommandIdPermutations(commandId: string): string[] { * @returns string[] */ export const collectUsableIds = (commandIds: string[]): Set => - new Set(commandIds.flatMap(id => id.split(':').map((_, i, a) => a.slice(0, i + 1).join(':')))) + new Set(commandIds.flatMap((id) => id.split(':').map((_, i, a) => a.slice(0, i + 1).join(':')))) diff --git a/src/errors/errors/cli.ts b/src/errors/errors/cli.ts index b4aa7e869..cb1bca95f 100644 --- a/src/errors/errors/cli.ts +++ b/src/errors/errors/cli.ts @@ -10,9 +10,9 @@ import wrap from 'wrap-ansi' * properties specific to internal oclif error handling */ -export function addOclifExitCode(error: Record, options?: { exit?: number | false }): OclifError { +export function addOclifExitCode(error: Record, options?: {exit?: number | false}): OclifError { if (!('oclif' in error)) { - (error as unknown as OclifError).oclif = {} + ;(error as unknown as OclifError).oclif = {} } error.oclif.exit = options?.exit === undefined ? 2 : options.exit @@ -25,7 +25,7 @@ export class CLIError extends Error implements OclifError { code?: string suggestions?: string[] - constructor(error: string | Error, options: { exit?: number | false } & PrettyPrintableError = {}) { + constructor(error: string | Error, options: {exit?: number | false} & PrettyPrintableError = {}) { super(error instanceof Error ? error.message : error) addOclifExitCode(this, options) this.code = options.code diff --git a/src/errors/errors/pretty-print.ts b/src/errors/errors/pretty-print.ts index 3067501db..46c6efb4e 100644 --- a/src/errors/errors/pretty-print.ts +++ b/src/errors/errors/pretty-print.ts @@ -5,7 +5,7 @@ import indent from 'indent-string' import wrap from 'wrap-ansi' // These exist for backwards compatibility with CLIError -type CLIErrorDisplayOptions = { name?: string; bang?: string } +type CLIErrorDisplayOptions = {name?: string; bang?: string} export function applyPrettyPrintOptions(error: Error, options: PrettyPrintableError): PrettyPrintableError { const prettyErrorKeys: (keyof PrettyPrintableError)[] = ['message', 'code', 'ref', 'suggestions'] @@ -13,7 +13,7 @@ export function applyPrettyPrintOptions(error: Error, options: PrettyPrintableEr for (const key of prettyErrorKeys) { const applyOptionsKey = !(key in error) && options[key] if (applyOptionsKey) { - (error as any)[key] = options[key] + ;(error as any)[key] = options[key] } } @@ -25,7 +25,7 @@ const formatSuggestions = (suggestions?: string[]): string | undefined => { if (!suggestions || suggestions.length === 0) return undefined if (suggestions.length === 1) return `${label} ${suggestions[0]}` - const multiple = suggestions.map(suggestion => `* ${suggestion}`).join('\n') + const multiple = suggestions.map((suggestion) => `* ${suggestion}`).join('\n') return `${label}\n${indent(multiple, 2)}` } @@ -44,8 +44,8 @@ export default function prettyPrint(error: Error & PrettyPrintableError & CLIErr const formattedReference = ref ? `Reference: ${ref}` : undefined const formatted = [formattedHeader, formattedCode, formattedSuggestions, formattedReference] - .filter(Boolean) - .join('\n') + .filter(Boolean) + .join('\n') let output = wrap(formatted, errtermwidth - 6, {trim: false, hard: true} as any) output = indent(output, 3) diff --git a/src/errors/handle.ts b/src/errors/handle.ts index ab1ff2c2c..c359c0e41 100644 --- a/src/errors/handle.ts +++ b/src/errors/handle.ts @@ -40,9 +40,10 @@ export async function handle(err: ErrorToHandle): Promise { config.errorLogger.log(stack) } - await config.errorLogger.flush() - .then(() => Exit.exit(exitCode)) - .catch(console.error) + await config.errorLogger + .flush() + .then(() => Exit.exit(exitCode)) + .catch(console.error) } else Exit.exit(exitCode) } catch (error: any) { console.error(err.stack) diff --git a/src/errors/logger.ts b/src/errors/logger.ts index f2a79c329..83549ed32 100644 --- a/src/errors/logger.ts +++ b/src/errors/logger.ts @@ -4,10 +4,11 @@ import stripAnsi = require('strip-ansi') const timestamp = () => new Date().toISOString() let timer: any -const wait = (ms: number) => new Promise(resolve => { - if (timer) timer.unref() - timer = setTimeout(() => resolve(null), ms) -}) +const wait = (ms: number) => + new Promise((resolve) => { + if (timer) timer.unref() + timer = setTimeout(() => resolve(null), ms) + }) function chomp(s: string): string { if (s.endsWith('\n')) return s.replace(/\n$/, '') @@ -23,7 +24,7 @@ export class Logger { log(msg: string): void { msg = stripAnsi(chomp(msg)) - const lines = msg.split('\n').map(l => `${timestamp()} ${l}`.trimEnd()) + const lines = msg.split('\n').map((l) => `${timestamp()} ${l}`.trimEnd()) this.buffer.push(...lines) this.flush(50).catch(console.error) } diff --git a/src/execute.ts b/src/execute.ts index d5e95aa25..29180f46c 100644 --- a/src/execute.ts +++ b/src/execute.ts @@ -46,14 +46,12 @@ import {settings} from './settings' * })() * ``` */ -export async function execute( - options: { - dir: string; - args?: string[]; - loadOptions?: LoadOptions; - development?: boolean; - }, -): Promise { +export async function execute(options: { + dir: string + args?: string[] + loadOptions?: LoadOptions + development?: boolean +}): Promise { if (options.development) { // In dev mode -> use ts-node and dev plugins process.env.NODE_ENV = 'development' @@ -61,9 +59,9 @@ export async function execute( } return run(options.args ?? process.argv.slice(2), options.loadOptions ?? options.dir) - .then(async result => { - flush() - return result - }) - .catch(async error => handle(error)) + .then(async (result) => { + flush() + return result + }) + .catch(async (error) => handle(error)) } diff --git a/src/flags.ts b/src/flags.ts index 699dde32e..90ff5406d 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -5,41 +5,41 @@ import {CLIError} from './errors' import {URL} from 'node:url' import {loadHelpClass} from './help' -type NotArray = T extends Array ? never: T; +type NotArray = T extends Array ? never : T export function custom( defaults: Partial> & { - multiple: true; - } & ( - {required: true} | {default: OptionFlag['default']} - ), + multiple: true + } & ({required: true} | {default: OptionFlag['default']}), ): FlagDefinition export function custom( defaults: Partial, P>> & { - multiple?: false | undefined; - } & ( - {required: true} | {default: OptionFlag, P>['default']} - ), + multiple?: false | undefined + } & ({required: true} | {default: OptionFlag, P>['default']}), ): FlagDefinition export function custom( defaults: Partial, P>> & { - default?: OptionFlag, P>['default'] | undefined; - multiple?: false | undefined; - required?: false | undefined; + 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; + multiple: true + default?: OptionFlag['default'] | undefined + required?: false | undefined }, ): FlagDefinition -export function custom(): FlagDefinition +export function custom(): FlagDefinition< + T, + P, + {multiple: false; requiredOrDefaulted: false} +> /** * Create a custom flag. * @@ -70,9 +70,7 @@ export function custom( }) } -export function boolean( - options: Partial> = {}, -): BooleanFlag { +export function boolean(options: Partial> = {}): BooleanFlag { return { parse: async (b, _) => b, ...options, @@ -81,10 +79,9 @@ export function boolean( } as BooleanFlag } -export const integer = custom({ +export const integer = custom({ async parse(input, _, opts) { - if (!/^-?\d+$/.test(input)) - throw new CLIError(`Expected an integer but received: ${input}`) + if (!/^-?\d+$/.test(input)) throw new CLIError(`Expected an integer but received: ${input}`) const num = Number.parseInt(input, 10) if (opts.min !== undefined && num < opts.min) throw new CLIError(`Expected an integer greater than or equal to ${opts.min} but received: ${input}`) @@ -126,64 +123,65 @@ export const url = custom({ 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 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 => 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) - }, -}) +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]; +type ElementType> = T[number] export function option( defaults: Partial[], P>> & { - options: T; + options: T multiple: true } & ( - {required: true} | { - default: OptionFlag[], P>['default'] | undefined; - } - ), -): FlagDefinition + | {required: true} + | { + default: OptionFlag[], P>['default'] | undefined + } + ), +): FlagDefinition<(typeof defaults.options)[number], P, {multiple: true; requiredOrDefaulted: true}> export function option( defaults: Partial, P>> & { - options: T; - multiple?: false | undefined; - } & ( - {required: true} | {default: OptionFlag, P>['default']} - ), -): FlagDefinition + options: T + multiple?: false | undefined + } & ({required: true} | {default: OptionFlag, P>['default']}), +): FlagDefinition<(typeof defaults.options)[number], P, {multiple: false; requiredOrDefaulted: true}> export function option( defaults: Partial, P>> & { - options: T; - default?: OptionFlag, P>['default'] | undefined; - multiple?: false | undefined; - required?: false | undefined; + options: T + default?: OptionFlag, P>['default'] | undefined + multiple?: false | undefined + required?: false | undefined }, -): FlagDefinition +): FlagDefinition<(typeof defaults.options)[number], P, {multiple: false; requiredOrDefaulted: false}> export function option( defaults: Partial[], P>> & { - options: T; - multiple: true; - default?: OptionFlag[], P>['default'] | undefined; - required?: false | undefined; + options: T + multiple: true + default?: OptionFlag[], P>['default'] | undefined + required?: false | undefined }, -): FlagDefinition +): FlagDefinition<(typeof defaults.options)[number], P, {multiple: true; requiredOrDefaulted: false}> /** * Create a custom flag that infers the flag type from the provided options. @@ -199,7 +197,7 @@ export function option( */ export function option( defaults: Partial, P>> & {options: T}, -): FlagDefinition { +): FlagDefinition<(typeof defaults.options)[number], P, {multiple: boolean; requiredOrDefaulted: boolean}> { return (options: any = {}) => ({ parse: async (input, _ctx, _opts) => input, ...defaults, diff --git a/src/help/command.ts b/src/help/command.ts index 7faae8825..6eda6034b 100644 --- a/src/help/command.ts +++ b/src/help/command.ts @@ -11,9 +11,7 @@ import stripAnsi from 'strip-ansi' // split on any platform, not just the os specific EOL at runtime. const POSSIBLE_LINE_FEED = /\r\n|\n/ -let { - dim, -} = chalk +let {dim} = chalk if (process.env.ConEmuANSI === 'ON') { // eslint-disable-next-line unicorn/consistent-destructuring @@ -24,35 +22,46 @@ export class CommandHelp extends HelpFormatter { constructor( public command: Command.Class | Command.Loadable | Command.Cached, public config: Interfaces.Config, - public opts: Interfaces.HelpOptions) { + public opts: Interfaces.HelpOptions, + ) { super(config, opts) } generate(): string { const cmd = this.command - const flags = sortBy(Object.entries(cmd.flags || {}) - .filter(([, v]) => !v.hidden) - .map(([k, v]) => { - v.name = k - return v - }), f => [!f.char, f.char, f.name]) - - const args = Object.values(ensureArgObject(cmd.args)).filter(a => !a.hidden) - const output = compact(this.sections().map(({header, generate}) => { - const body = generate({cmd, flags, args}, header) - // Generate can return a list of sections - if (Array.isArray(body)) { - return body.map(helpSection => helpSection && helpSection.body && this.section(helpSection.header, helpSection.body)).join('\n\n') - } + const flags = sortBy( + Object.entries(cmd.flags || {}) + .filter(([, v]) => !v.hidden) + .map(([k, v]) => { + v.name = k + return v + }), + (f) => [!f.char, f.char, f.name], + ) + + const args = Object.values(ensureArgObject(cmd.args)).filter((a) => !a.hidden) + const output = compact( + this.sections().map(({header, generate}) => { + const body = generate({cmd, flags, args}, header) + // Generate can return a list of sections + if (Array.isArray(body)) { + return body + .map((helpSection) => helpSection && helpSection.body && this.section(helpSection.header, helpSection.body)) + .join('\n\n') + } - return body && this.section(header, body) - })).join('\n\n') + return body && this.section(header, body) + }), + ).join('\n\n') return output } - protected groupFlags(flags: Array): {mainFlags: Array; flagGroups: {[name: string]: Array}} { + protected groupFlags(flags: Array): { + mainFlags: Array + flagGroups: {[name: string]: Array} + } { const mainFlags: Array = [] - const flagGroups: { [index: string]: Array } = {} + const flagGroups: {[index: string]: Array} = {} for (const flag of flags) { const group = flag.helpGroup @@ -68,7 +77,7 @@ export class CommandHelp extends HelpFormatter { return {mainFlags, flagGroups} } - protected sections(): Array<{ header: string; generate: HelpSectionRenderer }> { + protected sections(): Array<{header: string; generate: HelpSectionRenderer}> { return [ { header: this.opts.usageHeader || 'USAGE', @@ -121,18 +130,21 @@ export class CommandHelp extends HelpFormatter { protected usage(): string { 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)) - } + .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 this.wrap(line) - }) - .join('\n') + return this.wrap(line) + }) + .join('\n') return body } @@ -144,7 +156,10 @@ export class CommandHelp extends HelpFormatter { return compact([ this.command.id, - Object.values(this.command.args ?? {})?.filter(a => !a.hidden).map(a => this.arg(a)).join(' '), + Object.values(this.command.args ?? {}) + ?.filter((a) => !a.hidden) + .map((a) => this.arg(a)) + .join(' '), ]).join(' ') } @@ -156,10 +171,9 @@ export class CommandHelp extends HelpFormatter { description = (cmd.description || '').split(POSSIBLE_LINE_FEED).slice(1) } else if (cmd.description) { const summary = cmd.summary ? `${cmd.summary}\n` : null - description = summary ? [ - ...summary.split(POSSIBLE_LINE_FEED), - ...(cmd.description || '').split(POSSIBLE_LINE_FEED), - ] : (cmd.description || '').split(POSSIBLE_LINE_FEED) + description = summary + ? [...summary.split(POSSIBLE_LINE_FEED), ...(cmd.description || '').split(POSSIBLE_LINE_FEED)] + : (cmd.description || '').split(POSSIBLE_LINE_FEED) } if (description) { @@ -169,55 +183,56 @@ export class CommandHelp extends HelpFormatter { protected aliases(aliases: string[] | undefined): string | undefined { if (!aliases || aliases.length === 0) return - const body = aliases.map(a => ['$', this.config.bin, a].join(' ')).join('\n') + const body = aliases.map((a) => ['$', this.config.bin, a].join(' ')).join('\n') return body } protected examples(examples: Command.Example[] | undefined | string): string | undefined { if (!examples || examples.length === 0) return - const body = castArray(examples).map(a => { - let description - let commands - if (typeof a === 'string') { - const lines = a - .split(POSSIBLE_LINE_FEED) - .filter(Boolean) - // If the example is \n then format correctly - if (lines.length >= 2 && !this.isCommand(lines[0]) && lines.slice(1).every(i => this.isCommand(i))) { - description = lines[0] - commands = lines.slice(1) + const body = castArray(examples) + .map((a) => { + let description + let commands + if (typeof a === 'string') { + const lines = a.split(POSSIBLE_LINE_FEED).filter(Boolean) + // If the example is \n then format correctly + 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) => this.formatIfCommand(line)).join('\n') + } } else { - return lines.map(line => this.formatIfCommand(line)).join('\n') + description = a.description + commands = [a.command] } - } else { - description = a.description - commands = [a.command] - } - 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 => - // First indent keeping room for escaped newlines - this.indent(this.wrap(this.formatIfCommand(c), finalIndentedSpacing + 4)) - // Then add the escaped newline - .split(POSSIBLE_LINE_FEED).join(` ${multilineSeparator}\n `), - ).join('\n') - - return `${this.wrap(description, finalIndentedSpacing)}\n\n${multilineCommands}` - }).join('\n\n') + 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) => + // First indent keeping room for escaped newlines + this.indent(this.wrap(this.formatIfCommand(c), finalIndentedSpacing + 4)) + // Then add the escaped newline + .split(POSSIBLE_LINE_FEED) + .join(` ${multilineSeparator}\n `), + ) + .join('\n') + + return `${this.wrap(description, finalIndentedSpacing)}\n\n${multilineCommands}` + }) + .join('\n\n') return body } protected args(args: Command.Arg.Any[]): [string, string | undefined][] | undefined { - if (args.filter(a => a.description).length === 0) return + if (args.filter((a) => a.description).length === 0) return - return args.map(a => { + return args.map((a) => { const name = a.name.toUpperCase() let description = a.description || '' if (a.default) description = `[default: ${a.default}] ${description}` @@ -266,7 +281,7 @@ export class CommandHelp extends HelpFormatter { protected flags(flags: Array): [string, string | undefined][] | undefined { if (flags.length === 0) return - return flags.map(flag => { + return flags.map((flag) => { const left = this.flagHelpLabel(flag) let right = flag.summary || flag.description || '' @@ -285,16 +300,21 @@ export class CommandHelp extends HelpFormatter { } protected flagsDescriptions(flags: Array): string | undefined { - const flagsWithExtendedDescriptions = flags.filter(flag => flag.summary && flag.description) + const flagsWithExtendedDescriptions = flags.filter((flag) => flag.summary && flag.description) if (flagsWithExtendedDescriptions.length === 0) return - const body = flagsWithExtendedDescriptions.map(flag => { - // Guaranteed to be set because of the filter above, but make ts happy - const summary = flag.summary || '' - let flagHelp = this.flagHelpLabel(flag, true) - flagHelp += flagHelp.length + summary.length + 2 < this.opts.maxWidth ? ' ' + summary : '\n\n' + this.indent(this.wrap(summary, this.indentSpacing * 2)) - return `${flagHelp}\n\n${this.indent(this.wrap(flag.description || '', this.indentSpacing * 2))}` - }).join('\n\n') + const body = flagsWithExtendedDescriptions + .map((flag) => { + // Guaranteed to be set because of the filter above, but make ts happy + const summary = flag.summary || '' + let flagHelp = this.flagHelpLabel(flag, true) + flagHelp += + flagHelp.length + summary.length + 2 < this.opts.maxWidth + ? ' ' + summary + : '\n\n' + this.indent(this.wrap(summary, this.indentSpacing * 2)) + return `${flagHelp}\n\n${this.indent(this.wrap(flag.description || '', this.indentSpacing * 2))}` + }) + .join('\n\n') return body } diff --git a/src/help/docopts.ts b/src/help/docopts.ts index 3ee8f8299..247854fa9 100644 --- a/src/help/docopts.ts +++ b/src/help/docopts.ts @@ -65,11 +65,11 @@ export class DocOpts { // Create a new map with references to the flags that we can manipulate. this.flagMap = {} this.flagList = Object.entries(cmd.flags || {}) - .filter(([_, flag]) => !flag.hidden) - .map(([name, flag]) => { - this.flagMap[name] = flag - return flag - }) + .filter(([_, flag]) => !flag.hidden) + .map(([name, flag]) => { + this.flagMap[name] = flag + return flag + }) } public static generate(cmd: Command.Class | Command.Loadable | Command.Cached): string { @@ -79,7 +79,10 @@ 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 => 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) } @@ -87,11 +90,13 @@ export class DocOpts { opts.push(...Object.values(this.groupFlagElements())) } catch { // If there is an error, just return no usage so we don't fail command help. - opts.push(...this.flagList.map(flag => { - const name = flag.char ? `-${flag.char}` : `--${flag.name}` - if (flag.type === 'boolean') return name - return `${name}=` - })) + opts.push( + ...this.flagList.map((flag) => { + const name = flag.char ? `-${flag.char}` : `--${flag.name}` + if (flag.type === 'boolean') return name + return `${name}=` + }), + ) } return opts.join(' ') @@ -102,9 +107,15 @@ export class DocOpts { // Generate all doc opt elements for combining // Show required flags first - this.generateElements(elementMap, this.flagList.filter(flag => flag.required)) + this.generateElements( + elementMap, + this.flagList.filter((flag) => flag.required), + ) // Then show optional flags - this.generateElements(elementMap, this.flagList.filter(flag => !flag.required)) + this.generateElements( + elementMap, + this.flagList.filter((flag) => !flag.required), + ) for (const flag of this.flagList) { if (Array.isArray(flag.dependsOn)) { diff --git a/src/help/formatter.ts b/src/help/formatter.ts index ad039f851..a2d09239f 100644 --- a/src/help/formatter.ts +++ b/src/help/formatter.ts @@ -10,8 +10,13 @@ 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; -export type HelpSectionRenderer = (data: {cmd: Command.Class | Command.Loadable | Command.Cached; flags: Command.Flag.Any[]; args: Command.Arg.Any[]}, header: string) => HelpSection | HelpSection[] | string | undefined; +export type HelpSection = + | {header: string; body: string | HelpSectionKeyValueTable | [string, string | undefined][] | undefined} + | undefined +export type HelpSectionRenderer = ( + data: {cmd: Command.Class | Command.Loadable | Command.Cached; flags: Command.Flag.Any[]; args: Command.Arg.Any[]}, + header: string, +) => HelpSection | HelpSection[] | string | undefined export class HelpFormatter { indentSpacing = 2 @@ -101,7 +106,10 @@ export class HelpFormatter { return indent(body, spacing) } - public renderList(input: (string | undefined)[][], opts: {indentation: number; multiline?: boolean; stripAnsi?: boolean; spacer?: string}): string { + public renderList( + input: (string | undefined)[][], + opts: {indentation: number; multiline?: boolean; stripAnsi?: boolean; spacer?: string}, + ): string { if (input.length === 0) { return '' } @@ -128,7 +136,7 @@ export class HelpFormatter { } if (opts.multiline) return renderMultiline() - const maxLength = widestLine(input.map(i => i[0]).join('\n')) + const maxLength = widestLine(input.map((i) => i[0]).join('\n')) let output = '' let spacer = opts.spacer || '\n' let cur = '' @@ -149,7 +157,7 @@ export class HelpFormatter { if (opts.stripAnsi) right = stripAnsi(right) right = this.wrap(right.trim(), opts.indentation + maxLength + 2) - const [first, ...lines] = right!.split('\n').map(s => s.trim()) + const [first, ...lines] = right!.split('\n').map((s) => s.trim()) cur += ' '.repeat(maxLength - width(cur) + 2) cur += first if (lines.length === 0) { @@ -172,32 +180,37 @@ export class HelpFormatter { return output.trim() } - public section(header: string, body: string | HelpSection | HelpSectionKeyValueTable | [string, string | undefined][]): string { + public section( + header: string, + body: string | HelpSection | HelpSectionKeyValueTable | [string, string | undefined][], + ): string { // Always render template strings with the provided render function before wrapping and indenting let newBody: any if (typeof body! === 'string') { newBody = this.render(body!) } else if (Array.isArray(body)) { - newBody = (body! as [string, string | undefined | HelpSectionKeyValueTable][]).map(entry => { + newBody = (body! as [string, string | undefined | HelpSectionKeyValueTable][]).map((entry) => { if ('name' in entry) { const tableEntry = entry as unknown as {name: string; description: string} - return ([this.render(tableEntry.name), this.render(tableEntry.description)]) + return [this.render(tableEntry.name), this.render(tableEntry.description)] } const [left, right] = entry - return ([this.render(left), right && this.render(right as string)]) + return [this.render(left), right && this.render(right as string)] }) } else if ('header' in body!) { return this.section(body!.header, body!.body) } else { newBody = (body! as unknown as HelpSectionKeyValueTable) - .map((entry: { name: string; description: string }) => ([entry.name, entry.description])) - .map(([left, right]) => ([this.render(left), right && this.render(right)])) + .map((entry: {name: string; description: string}) => [entry.name, entry.description]) + .map(([left, right]) => [this.render(left), right && this.render(right)]) } const output = [ chalk.bold(header), - this.indent(Array.isArray(newBody) ? this.renderList(newBody, {stripAnsi: this.opts.stripAnsi, indentation: 2}) : newBody), + 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 1a8d1f076..24bb92f06 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -36,14 +36,14 @@ export abstract class HelpBase extends HelpFormatter { * Show help, used in multi-command CLIs * @param args passed into your command, useful for determining which type of help to display */ - public abstract showHelp(argv: string[]): Promise; + public abstract showHelp(argv: string[]): Promise /** * Show help for an individual command * @param command * @param topics */ - public abstract showCommandHelp(command: Command.Class, topics: Interfaces.Topic[]): Promise; + public abstract showCommandHelp(command: Command.Class, topics: Interfaces.Topic[]): Promise } export class Help extends HelpBase { @@ -58,7 +58,7 @@ export class Help extends HelpBase { private get _topics(): Interfaces.Topic[] { return this.config.topics.filter((topic: Interfaces.Topic) => { // it is assumed a topic has a child if it has children - const hasChild = this.config.topics.some(subTopic => subTopic.name.includes(`${topic.name}:`)) + const hasChild = this.config.topics.some((subTopic) => subTopic.name.includes(`${topic.name}:`)) return hasChild }) } @@ -66,18 +66,18 @@ export class Help extends HelpBase { protected get sortedCommands(): Command.Loadable[] { let {commands} = this.config - commands = commands.filter(c => this.opts.all || !c.hidden) - commands = sortBy(commands, c => c.id) - commands = uniqBy(commands, c => c.id) + commands = commands.filter((c) => this.opts.all || !c.hidden) + commands = sortBy(commands, (c) => c.id) + commands = uniqBy(commands, (c) => c.id) return commands } protected get sortedTopics(): Interfaces.Topic[] { let topics = this._topics - topics = topics.filter(t => this.opts.all || !t.hidden) - topics = sortBy(topics, t => t.name) - topics = uniqBy(topics, t => t.name) + topics = topics.filter((t) => this.opts.all || !t.hidden) + topics = sortBy(topics, (t) => t.name) + topics = uniqBy(topics, (t) => t.name) return topics } @@ -88,7 +88,7 @@ export class Help extends HelpBase { public async showHelp(argv: string[]): Promise { const originalArgv = argv.slice(1) - argv = argv.filter(arg => !getHelpFlagAdditions(this.config).includes(arg)) + argv = argv.filter((arg) => !getHelpFlagAdditions(this.config).includes(arg)) if (this.config.topicSeparator !== ':') argv = standardizeIDFromArgv(argv, this.config) const subject = getHelpSubject(argv, this.config) @@ -134,7 +134,7 @@ export class Help extends HelpBase { if (matches.length > 0) { const result = await this.config.runHook('command_incomplete', { id: subject, - argv: originalArgv.filter(o => !subject.split(':').includes(o)), + argv: originalArgv.filter((o) => !subject.split(':').includes(o)), matches, }) if (result.successes.length > 0) return @@ -148,8 +148,12 @@ export class Help extends HelpBase { const name = command.id const depth = name.split(':').length - const subTopics = this.sortedTopics.filter(t => t.name.startsWith(name + ':') && t.name.split(':').length === depth + 1) - const subCommands = this.sortedCommands.filter(c => c.id.startsWith(name + ':') && c.id.split(':').length === depth + 1) + const subTopics = this.sortedTopics.filter( + (t) => t.name.startsWith(name + ':') && t.name.split(':').length === depth + 1, + ) + const subCommands = this.sortedCommands.filter( + (c) => c.id.startsWith(name + ':') && c.id.split(':').length === depth + 1, + ) const plugin = this.config.plugins.get(command.pluginName!) const state = this.config.pjson?.oclif?.state || plugin?.pjson?.oclif?.state || command.state @@ -157,11 +161,17 @@ export class Help extends HelpBase { if (state) { this.log( state === 'deprecated' - ? `${formatCommandDeprecationWarning(toConfiguredId(name, this.config), command.deprecationOptions)}` + ? `${formatCommandDeprecationWarning(toConfiguredId(name, this.config), command.deprecationOptions)}\n` : `This command is in ${state}.\n`, ) } + if (command.deprecateAliases && command.aliases.includes(name)) { + const actualCmd = this.config.commands.find((c) => c.aliases.includes(name)) + const opts = {...command.deprecationOptions, ...(actualCmd ? {to: actualCmd.id} : {})} + this.log(`${formatCommandDeprecationWarning(toConfiguredId(name, this.config), opts)}\n`) + } + const summary = this.summary(command) if (summary) { this.log(summary + '\n') @@ -176,8 +186,8 @@ export class Help extends HelpBase { } if (subCommands.length > 0) { - const aliases:string[] = [] - const uniqueSubCommands: Command.Loadable[] = subCommands.filter(p => { + const aliases: string[] = [] + const uniqueSubCommands: Command.Loadable[] = subCommands.filter((p) => { aliases.push(...p.aliases) return !aliases.includes(p.id) }) @@ -192,19 +202,15 @@ 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`, - ) + this.log(state === 'deprecated' ? `${this.config.bin} is deprecated` : `${this.config.bin} is in ${state}.\n`) } this.log(this.formatRoot()) this.log('') if (!this.opts.all) { - rootTopics = rootTopics.filter(t => !t.name.includes(':')) - rootCommands = rootCommands.filter(c => !c.id.includes(':')) + rootTopics = rootTopics.filter((t) => !t.name.includes(':')) + rootCommands = rootCommands.filter((c) => !c.id.includes(':')) } if (rootTopics.length > 0) { @@ -213,7 +219,7 @@ export class Help extends HelpBase { } if (rootCommands.length > 0) { - rootCommands = rootCommands.filter(c => c.id) + rootCommands = rootCommands.filter((c) => c.id) this.log(this.formatCommands(rootCommands)) this.log('') } @@ -223,8 +229,12 @@ export class Help extends HelpBase { const {name} = topic const depth = name.split(':').length - const subTopics = this.sortedTopics.filter(t => t.name.startsWith(name + ':') && t.name.split(':').length === depth + 1) - const commands = this.sortedCommands.filter(c => c.id.startsWith(name + ':') && c.id.split(':').length === depth + 1) + const subTopics = this.sortedTopics.filter( + (t) => t.name.startsWith(name + ':') && t.name.split(':').length === depth + 1, + ) + const commands = this.sortedCommands.filter( + (c) => c.id.startsWith(name + ':') && c.id.split(':').length === depth + 1, + ) const state = this.config.pjson?.oclif?.state if (state) this.log(`This topic is in ${state}.\n`) @@ -250,7 +260,7 @@ export class Help extends HelpBase { protected formatCommand(command: Command.Class | Command.Loadable | Command.Cached): string { if (this.config.topicSeparator !== ':') { command.id = command.id.replaceAll(':', this.config.topicSeparator) - command.aliases = command.aliases && command.aliases.map(a => a.replaceAll(':', this.config.topicSeparator)) + command.aliases = command.aliases && command.aliases.map((a) => a.replaceAll(':', this.config.topicSeparator)) } const help = this.getCommandHelpClass(command) @@ -264,17 +274,17 @@ export class Help extends HelpBase { protected formatCommands(commands: Array): string { if (commands.length === 0) return '' - const body = this.renderList(commands.map(c => { - if (this.config.topicSeparator !== ':') c.id = c.id.replaceAll(':', this.config.topicSeparator) - return [ - c.id, - this.summary(c), - ] - }), { - spacer: '\n', - stripAnsi: this.opts.stripAnsi, - indentation: 2, - }) + const body = this.renderList( + commands.map((c) => { + if (this.config.topicSeparator !== ':') c.id = c.id.replaceAll(':', this.config.topicSeparator) + return [c.id, this.summary(c)] + }), + { + spacer: '\n', + stripAnsi: this.opts.stripAnsi, + indentation: 2, + }, + ) return this.section('COMMANDS', body) } @@ -311,17 +321,17 @@ 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.replaceAll(':', this.config.topicSeparator) - return [ - c.name, - c.description && this.render(c.description.split('\n')[0]), - ] - }), { - spacer: '\n', - stripAnsi: this.opts.stripAnsi, - indentation: 2, - }) + const body = this.renderList( + topics.map((c) => { + if (this.config.topicSeparator !== ':') c.name = c.name.replaceAll(':', this.config.topicSeparator) + return [c.name, c.description && this.render(c.description.split('\n')[0])] + }), + { + spacer: '\n', + stripAnsi: this.opts.stripAnsi, + indentation: 2, + }, + ) return this.section('TOPICS', body) } @@ -335,7 +345,7 @@ export class Help extends HelpBase { } interface HelpBaseDerived { - new(config: Interfaces.Config, opts?: Partial): HelpBase; + new (config: Interfaces.Config, opts?: Partial): HelpBase } function extractClass(exported: any): HelpBaseDerived { @@ -348,10 +358,12 @@ export async function loadHelpClass(config: Interfaces.Config): Promise ids.has(id) - const finalizeId = (s?: string) => s ? [...final, s].join(':') : final.join(':') + const finalizeId = (s?: string) => (s ? [...final, s].join(':') : final.join(':')) const hasArgs = () => { const id = finalizeId() diff --git a/src/interfaces/config.ts b/src/interfaces/config.ts index df7eebb5e..c476c9525 100644 --- a/src/interfaces/config.ts +++ b/src/interfaces/config.ts @@ -9,137 +9,142 @@ export type PlatformTypes = NodeJS.Platform | 'wsl' export type ArchTypes = 'arm' | 'arm64' | 'mips' | 'mipsel' | 'ppc' | 'ppc64' | 's390' | 's390x' | 'x32' | 'x64' | 'x86' export type PluginVersionDetail = { - version: string; - type: string; + version: string + type: string root: string -}; +} export type VersionDetails = { - cliVersion: string; - architecture: string; - nodeVersion: string; - pluginVersions?: Record; - osVersion?: string; - shell?: string; - rootPath?: string; + cliVersion: string + architecture: string + nodeVersion: string + pluginVersions?: Record + osVersion?: string + shell?: string + rootPath?: string } export interface Config { - readonly name: string; - readonly version: string; - readonly channel: string; - readonly pjson: PJSON.CLI; - readonly root: string; + readonly name: string + readonly version: string + readonly channel: string + readonly pjson: PJSON.CLI + readonly root: string /** * process.arch */ - readonly arch: ArchTypes; + readonly arch: ArchTypes /** * bin name of CLI command */ - readonly bin: string; + readonly bin: string /** * cache directory to use for CLI * * example ~/Library/Caches/mycli or ~/.cache/mycli */ - readonly cacheDir: string; + readonly cacheDir: string /** * config directory to use for CLI * * example: ~/.config/mycli */ - readonly configDir: string; + readonly configDir: string /** * data directory to use for CLI * * example: ~/.local/share/mycli */ - readonly dataDir: string; + readonly dataDir: string /** * base dirname to use in cacheDir/configDir/dataDir */ - readonly dirname: string; + readonly dirname: string /** * points to a file that should be appended to for error logs * * example: ~/Library/Caches/mycli/error.log */ - readonly errlog: string; + readonly errlog: string /** * path to home directory * * example: /home/myuser */ - readonly home: string; + readonly home: string /** * process.platform */ - readonly platform: PlatformTypes; + readonly platform: PlatformTypes /** * active shell */ - readonly shell: string; + readonly shell: string /** * user agent to use for http calls * * example: mycli/1.2.3 (darwin-x64) node-9.0.0 */ - readonly userAgent: string; + readonly userAgent: string /** * if windows */ - readonly windows: boolean; + readonly windows: boolean /** * debugging level * * set by ${BIN}_DEBUG or DEBUG=$BIN */ - readonly debug: number; + readonly debug: number /** * npm registry to use for installing plugins */ - readonly npmRegistry?: string; - readonly plugins: Map; - readonly binPath?: string; + readonly npmRegistry?: string + readonly plugins: Map + readonly binPath?: string /** * name of any bin aliases that will execute the cli */ - readonly binAliases?: string[]; - readonly nsisCustomization?: string; - readonly valid: boolean; - readonly flexibleTaxonomy?: boolean; - topicSeparator: ':' | ' '; - readonly commands: Command.Loadable[]; - readonly topics: Topic[]; - readonly commandIDs: string[]; + readonly binAliases?: string[] + readonly nsisCustomization?: string + readonly valid: boolean + readonly flexibleTaxonomy?: boolean + topicSeparator: ':' | ' ' + readonly commands: Command.Loadable[] + readonly topics: Topic[] + readonly commandIDs: string[] readonly versionDetails: VersionDetails - runCommand(id: string, argv?: string[], cachedCommand?: Command.Loadable): Promise; - runHook(event: T, opts: Hooks[T]['options'], timeout?: number, captureErrors?: boolean): Promise>; + runCommand(id: string, argv?: string[], cachedCommand?: Command.Loadable): Promise + runHook( + event: T, + opts: Hooks[T]['options'], + timeout?: number, + captureErrors?: boolean, + ): Promise> getAllCommandIDs(): string[] getAllCommands(): Command.Loadable[] - findCommand(id: string, opts: { must: true }): Command.Loadable; - findCommand(id: string, opts?: { must: boolean }): Command.Loadable | undefined; - findTopic(id: string, opts: { must: true }): Topic; - findTopic(id: string, opts?: { must: boolean }): Topic | undefined; - findMatches(id: string, argv: string[]): Command.Loadable[]; - scopedEnvVar(key: string): string | undefined; - scopedEnvVarKey(key: string): string; - scopedEnvVarKeys(key: string): string[]; - scopedEnvVarTrue(key: string): boolean; - s3Url(key: string): string; - s3Key(type: 'versioned' | 'unversioned', ext: '.tar.gz' | '.tar.xz', options?: Config.s3Key.Options): string; - s3Key(type: keyof PJSON.S3.Templates, options?: Config.s3Key.Options): string; - getPluginsList(): Plugin[]; + findCommand(id: string, opts: {must: true}): Command.Loadable + findCommand(id: string, opts?: {must: boolean}): Command.Loadable | undefined + findTopic(id: string, opts: {must: true}): Topic + findTopic(id: string, opts?: {must: boolean}): Topic | undefined + findMatches(id: string, argv: string[]): Command.Loadable[] + scopedEnvVar(key: string): string | undefined + scopedEnvVarKey(key: string): string + scopedEnvVarKeys(key: string): string[] + scopedEnvVarTrue(key: string): boolean + s3Url(key: string): string + s3Key(type: 'versioned' | 'unversioned', ext: '.tar.gz' | '.tar.xz', options?: Config.s3Key.Options): string + s3Key(type: keyof PJSON.S3.Templates, options?: Config.s3Key.Options): string + getPluginsList(): Plugin[] } export namespace Config { export namespace s3Key { export interface Options { - platform?: PlatformTypes; - arch?: ArchTypes; - [key: string]: any; + platform?: PlatformTypes + arch?: ArchTypes + [key: string]: any } } } diff --git a/src/interfaces/errors.ts b/src/interfaces/errors.ts index e0c083e3a..4486b7561 100644 --- a/src/interfaces/errors.ts +++ b/src/interfaces/errors.ts @@ -1,30 +1,30 @@ -export type CommandError = Error & {exitCode?: number}; +export type CommandError = Error & {exitCode?: number} export interface OclifError { oclif: { - exit?: number; - }; + exit?: number + } } export interface PrettyPrintableError { /** * message to display related to the error */ - message?: string; + message?: string /** * a unique error code for this error class */ - code?: string; + code?: string /** * a url to find out more information related to this error * or fixing the error */ - ref?: string; + ref?: string /** * a suggestion that may be useful or provide additional context */ - suggestions?: string[]; + suggestions?: string[] } diff --git a/src/interfaces/flags.ts b/src/interfaces/flags.ts index e8005e858..0fc8c4bb6 100644 --- a/src/interfaces/flags.ts +++ b/src/interfaces/flags.ts @@ -30,4 +30,4 @@ import {FlagInput} from './parser' * } * } */ -export type InferredFlags = T extends FlagInput ? F & { json: boolean | undefined; } : unknown +export type InferredFlags = T extends FlagInput ? F & {json: boolean | undefined} : unknown diff --git a/src/interfaces/help.ts b/src/interfaces/help.ts index 970aa9bea..8dacd3e70 100644 --- a/src/interfaces/help.ts +++ b/src/interfaces/help.ts @@ -1,20 +1,20 @@ export interface HelpOptions { - all?: boolean; - maxWidth: number; - stripAnsi?: boolean; + all?: boolean + maxWidth: number + stripAnsi?: boolean /** * By default, option values on flags are shown in the flag's description. This is because * long options list ruin the formatting of help. If a CLI knows all commands will not * do this, it can be turned off at a help level using this property. An individual flag * can set this using `flag.helpValue=options.join('|')`. */ - showFlagOptionsInTitle?: boolean; + showFlagOptionsInTitle?: boolean /** * By default, titles show flag values as ``. Some CLI developers may prefer titles * to show the flag name as the value. i.e. `--myflag=myflag` instead of `--myflag=`. * An individual flag can set this using `flag.helpValue=flag.name`. */ - showFlagNameInTitle?: boolean; + showFlagNameInTitle?: boolean /** * By default, the command summary is show at the top of the help and as the first line in * the command description. Repeating the summary in the command description improves readability @@ -22,14 +22,14 @@ export interface HelpOptions { * the description is treated as the summary. Some CLIs, especially with very simple commands, may * not want the duplication. */ - hideCommandSummaryInDescription?: boolean; + hideCommandSummaryInDescription?: boolean /** * Use USAGE, but some may want to use USAGE as used in gnu man pages. See help recommendations at * http://www.gnu.org/software/help2man/#--help-recommendations */ - usageHeader?: string; + usageHeader?: string /** * Use docopts as the usage. Defaults to true. */ - docopts?: boolean; + docopts?: boolean } diff --git a/src/interfaces/hooks.ts b/src/interfaces/hooks.ts index 928068c14..a6b526a5e 100644 --- a/src/interfaces/hooks.ts +++ b/src/interfaces/hooks.ts @@ -3,57 +3,60 @@ import {Config} from './config' import {Plugin} from './plugin' interface HookMeta { - options: Record; - return: any; + options: Record + return: any } export interface Hooks { - [event: string]: HookMeta; + [event: string]: HookMeta init: { - options: { id: string | undefined; argv: string[] }; - return: void; - }; + options: {id: string | undefined; argv: string[]} + return: void + } prerun: { - options: { Command: Command.Class; argv: string[] }; - return: void; - }; + options: {Command: Command.Class; argv: string[]} + return: void + } postrun: { options: { - Command: Command.Class; - result?: any; - argv: string[]; - }; - return: void; - }; + Command: Command.Class + result?: any + argv: string[] + } + return: void + } preupdate: { - options: {channel: string, version: string}; - return: void; - }; + options: {channel: string; version: string} + return: void + } update: { - options: {channel: string, version: string}; - return: void; - }; - 'command_not_found': { - options: {id: string; argv?: string[]}; - return: unknown; - }; - 'command_incomplete': { - options: {id: string; argv: string[], matches: Command.Loadable[]}; - return: unknown; - }; - 'jit_plugin_not_installed': { - options: {id: string; argv: string[]; command: Command.Loadable, pluginName: string; pluginVersion: string}; - return: unknown; - }; + options: {channel: string; version: string} + return: void + } + command_not_found: { + options: {id: string; argv?: string[]} + return: unknown + } + command_incomplete: { + options: {id: string; argv: string[]; matches: Command.Loadable[]} + return: unknown + } + jit_plugin_not_installed: { + options: {id: string; argv: string[]; command: Command.Loadable; pluginName: string; pluginVersion: string} + return: unknown + } 'plugins:preinstall': { options: { - plugin: { name: string; tag: string; type: 'npm' } | { url: string; type: 'repo' }; - }; - return: void; - }; + plugin: {name: string; tag: string; type: 'npm'} | {url: string; type: 'repo'} + } + return: void + } } -export type Hook = (this: Hook.Context, options: P[T]['options'] & {config: Config}) => Promise +export type Hook = ( + this: Hook.Context, + options: P[T]['options'] & {config: Config}, +) => Promise export namespace Hook { export type Init = Hook<'init'> @@ -67,17 +70,16 @@ export namespace Hook { export type JitPluginNotInstalled = Hook<'jit_plugin_not_installed'> export interface Context { - config: Config; - exit(code?: number): void; - error(message: string | Error, options?: {code?: string; exit?: number}): void; - warn(message: string): void; - log(message?: any, ...args: any[]): void; - debug(...args: any[]): void; + config: Config + exit(code?: number): void + error(message: string | Error, options?: {code?: string; exit?: number}): void + warn(message: string): void + log(message?: any, ...args: any[]): void + debug(...args: any[]): void } export interface Result { - successes: Array<{ result: T; plugin: Plugin }>; - failures: Array<{ error: Error; plugin: Plugin }>; + successes: Array<{result: T; plugin: Plugin}> + failures: Array<{error: Error; plugin: Plugin}> } } - diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 76e7ca5b4..bd22984ac 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -5,15 +5,7 @@ export type {HelpOptions} from './help' export type {Hook, Hooks} from './hooks' export type {Manifest} from './manifest' export type {S3Manifest} from './s3-manifest' -export type { - Arg, - BooleanFlag, - CustomOptions, - Deprecation, - Flag, - FlagDefinition, - OptionFlag, -} from './parser' +export type {Arg, BooleanFlag, CustomOptions, Deprecation, Flag, FlagDefinition, OptionFlag} from './parser' export type {PJSON} from './pjson' export type {Plugin, PluginOptions, Options} from './plugin' export type {Topic} from './topic' diff --git a/src/interfaces/manifest.ts b/src/interfaces/manifest.ts index 354a0d8ba..936068768 100644 --- a/src/interfaces/manifest.ts +++ b/src/interfaces/manifest.ts @@ -1,6 +1,6 @@ import {Command} from '../command' export type Manifest = { - version: string; - commands: {[id: string]: Command.Cached}; + version: string + commands: {[id: string]: Command.Cached} } diff --git a/src/interfaces/parser.ts b/src/interfaces/parser.ts index a201323d4..07c472891 100644 --- a/src/interfaces/parser.ts +++ b/src/interfaces/parser.ts @@ -1,48 +1,48 @@ import {AlphabetLowercase, AlphabetUppercase} from './alphabet' import {Command} from '../command' -export type FlagOutput = { [name: string]: any } -export type ArgOutput = { [name: string]: any } +export type FlagOutput = {[name: string]: any} +export type ArgOutput = {[name: string]: any} export type CLIParseErrorOptions = { parse: { - input?: ParserInput; - output?: ParserOutput; - }; + input?: ParserInput + output?: ParserOutput + } } -export type OutputArgs = { [P in keyof T]: any } -export type OutputFlags = { [P in keyof T]: any } +export type OutputArgs = {[P in keyof T]: any} +export type OutputFlags = {[P in keyof T]: any} export type ParserOutput< TFlags extends OutputFlags = any, BFlags extends OutputFlags = any, - TArgs extends OutputFlags = any + TArgs extends OutputFlags = any, > = { // Add in the --json flag so that it shows up in the types. // This is necessary because there's no way to optionally add the json flag based // on wether enableJsonFlag is set in the command. - flags: TFlags & BFlags & { json: boolean | undefined }; - args: TArgs; - argv: unknown[]; - raw: ParsingToken[]; - metadata: Metadata; - nonExistentFlags: string[]; + flags: TFlags & BFlags & {json: boolean | undefined} + args: TArgs + argv: unknown[] + raw: ParsingToken[] + metadata: Metadata + nonExistentFlags: string[] } -export type ArgToken = { type: 'arg'; arg: string; input: string } -export type FlagToken = { type: 'flag'; flag: string; input: string } +export type ArgToken = {type: 'arg'; arg: string; input: string} +export type FlagToken = {type: 'flag'; flag: string; input: string} export type ParsingToken = ArgToken | FlagToken -export type FlagUsageOptions = { displayRequired?: boolean } +export type FlagUsageOptions = {displayRequired?: boolean} export type Metadata = { - flags: { [key: string]: MetadataFlag }; + flags: {[key: string]: MetadataFlag} } export type MetadataFlag = { - setFromDefault?: boolean; - defaultHelp?: unknown; + setFromDefault?: boolean + defaultHelp?: unknown } export type ListItem = [string, string | undefined] @@ -51,8 +51,8 @@ export type List = ListItem[] export type CustomOptions = Record export type DefaultContext = { - options: T; - flags: Record; + options: T + flags: Record } /** @@ -68,7 +68,9 @@ export type FlagDefault = T | ((context: DefaultContext

= T | ((context: DefaultContext

>) => Promise) +export type FlagDefaultHelp = + | T + | ((context: DefaultContext

>) => Promise) /** * Type to define a default value for an arg. @@ -80,83 +82,85 @@ export type ArgDefault = T | ((context: DefaultContext = T | ((context: DefaultContext>) => Promise) +export type ArgDefaultHelp = + | T + | ((context: DefaultContext>) => Promise) -export type FlagRelationship = string | {name: string; when: (flags: Record) => Promise}; +export type FlagRelationship = string | {name: string; when: (flags: Record) => Promise} export type Relationship = { - type: 'all' | 'some' | 'none'; - flags: FlagRelationship[]; + type: 'all' | 'some' | 'none' + flags: FlagRelationship[] } export type Deprecation = { - to?: string; - message?: string; - version?: string | number; + to?: string + message?: string + version?: string | number } export type FlagProps = { - name: string; - char?: AlphabetLowercase | AlphabetUppercase; + name: string + char?: AlphabetLowercase | AlphabetUppercase /** * A short summary of flag usage to show in the flag list. * If not provided, description will be used. */ - summary?: string; + summary?: string /** * A description of flag usage. If summary is provided, the description * is assumed to be a longer description and will be shown in a separate * section within help. */ - description?: string; + description?: string /** * The flag label to show in help. Defaults to "[-] --" where - is * only displayed if the char is defined. */ - helpLabel?: string; + helpLabel?: string /** * Shows this flag in a separate list in the help. */ - helpGroup?: string; + helpGroup?: string /** * Accept an environment variable as input */ - env?: string; + env?: string /** * If true, the flag will not be shown in the help. */ - hidden?: boolean; + hidden?: boolean /** * If true, the flag will be required. */ - required?: boolean; + required?: boolean /** * List of flags that this flag depends on. */ - dependsOn?: string[]; + dependsOn?: string[] /** * List of flags that cannot be used with this flag. */ - exclusive?: string[]; + exclusive?: string[] /** * Exactly one of these flags must be provided. */ - exactlyOne?: string[]; + exactlyOne?: string[] /** * Define complex relationships between flags. */ - relationships?: Relationship[]; + relationships?: Relationship[] /** * Make the flag as deprecated. */ - deprecated?: true | Deprecation; + deprecated?: true | Deprecation /** * Alternate names that can be used for this flag. */ - aliases?: string[]; + aliases?: string[] /** - * Alternate short chars that can be used for this flag. - */ - charAliases?: (AlphabetLowercase | AlphabetUppercase)[]; + * Alternate short chars that can be used for this flag. + */ + charAliases?: (AlphabetLowercase | AlphabetUppercase)[] /** * Emit deprecation warning when a flag alias is provided */ @@ -165,89 +169,98 @@ export type FlagProps = { * If true, the value returned by defaultHelp will not be cached in the oclif.manifest.json. * This is helpful if the default value contains sensitive data that shouldn't be published to npm. */ - noCacheDefault?: boolean; + noCacheDefault?: boolean } export type ArgProps = { - name: string; + name: string /** * A description of flag usage. If summary is provided, the description * is assumed to be a longer description and will be shown in a separate * section within help. */ - description?: string; + description?: string /** * If true, the flag will not be shown in the help. */ - hidden?: boolean; + hidden?: boolean /** * If true, the flag will be required. */ - required?: boolean; + required?: boolean - options?: string[]; - ignoreStdin?: boolean; + options?: string[] + ignoreStdin?: boolean /** * If true, the value returned by defaultHelp will not be cached in the oclif.manifest.json. * This is helpful if the default value contains sensitive data that shouldn't be published to npm. */ - noCacheDefault?: boolean; + noCacheDefault?: boolean } export type BooleanFlagProps = FlagProps & { - type: 'boolean'; - allowNo: boolean; + type: 'boolean' + allowNo: boolean } export type OptionFlagProps = FlagProps & { - type: 'option'; - helpValue?: string; - options?: readonly string[]; - multiple?: boolean; - /** + type: 'option' + helpValue?: string + options?: readonly string[] + multiple?: boolean + /** * Delimiter to separate the values for a multiple value flag. * Only respected if multiple is set to true. Default behavior is to * separate on spaces. */ - delimiter?: ',', + delimiter?: ',' } export type FlagParserContext = Command & {token: FlagToken} -export type FlagParser = (input: I, context: FlagParserContext, opts: P & OptionFlag) => - T extends Array ? Promise : Promise +export type FlagParser = ( + input: I, + context: FlagParserContext, + opts: P & OptionFlag, +) => T extends Array ? Promise : Promise export type ArgParserContext = Command & {token: ArgToken} -export type ArgParser = (input: string, context: ArgParserContext, opts: P & Arg) => Promise +export type ArgParser = ( + input: string, + context: ArgParserContext, + opts: P & Arg, +) => Promise export type Arg = ArgProps & { - options?: T[]; - defaultHelp?: ArgDefaultHelp; - input: string[]; - default?: ArgDefault; - parse: ArgParser; + options?: T[] + defaultHelp?: ArgDefaultHelp + input: string[] + default?: ArgDefault + parse: ArgParser } export type ArgDefinition = { - (options: P & ({ required: true } | { default: ArgDefault }) & Partial>): Arg; - (options?: P & Partial>): Arg; + (options: P & ({required: true} | {default: ArgDefault}) & Partial>): Arg + (options?: P & Partial>): Arg } -export type BooleanFlag = FlagProps & BooleanFlagProps & { - /** - * specifying a default of false is the same as not specifying a default - */ - default?: FlagDefault; - parse: (input: boolean, context: FlagParserContext, opts: FlagProps & BooleanFlagProps) => Promise -} - -export type OptionFlag = FlagProps & OptionFlagProps & { - parse: FlagParser - defaultHelp?: FlagDefaultHelp; - input: string[]; - default?: FlagDefault; -} +export type BooleanFlag = FlagProps & + BooleanFlagProps & { + /** + * specifying a default of false is the same as not specifying a default + */ + default?: FlagDefault + parse: (input: boolean, context: FlagParserContext, opts: FlagProps & BooleanFlagProps) => Promise + } + +export type OptionFlag = FlagProps & + OptionFlagProps & { + parse: FlagParser + defaultHelp?: FlagDefaultHelp + input: string[] + default?: FlagDefault + } type ReturnTypeSwitches = {multiple: boolean; requiredOrDefaulted: boolean} @@ -260,16 +273,17 @@ type ReturnTypeSwitches = {multiple: boolean; requiredOrDefaulted: boolean} * - 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 +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. @@ -291,96 +305,125 @@ type FlagReturnType = export type FlagDefinition< T, P = CustomOptions, - R extends ReturnTypeSwitches = {multiple: false, requiredOrDefaulted: false} + 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>; + options: P & {multiple: false; required: true} & Partial< + OptionFlag, 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>; + options: P & {multiple: true; required: false} & Partial< + OptionFlag, 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: P & {multiple: false; required: false} & Partial< + OptionFlag, 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< + FlagReturnType, + 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< + FlagReturnType, + 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>; + 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>; + 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>; + options: P & {required: false} & Partial< + OptionFlag, P> + >, + ): OptionFlag> ( // `multiple` is set to false in options, potentially overriding the default - options: P & { multiple: false } & Partial, P>> - ): OptionFlag>; + options: P & {multiple: false} & Partial< + OptionFlag, P> + >, + ): OptionFlag> ( // Catch all for when `multiple` is not set in the options - options?: P & { multiple?: false | undefined } & Partial, P>> - ): OptionFlag>; + 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>; + options: P & {multiple: true} & Partial< + OptionFlag, P> + >, + ): OptionFlag> } export type Flag = BooleanFlag | OptionFlag export type Input = { - flags?: FlagInput; - baseFlags?: FlagInput; - enableJsonFlag?: true | false; - args?: ArgInput; - strict?: boolean; - context?: ParserContext; - '--'?: boolean; + flags?: FlagInput + baseFlags?: FlagInput + enableJsonFlag?: true | false + args?: ArgInput + strict?: boolean + context?: ParserContext + '--'?: boolean } export type ParserInput = { - argv: string[]; - flags: FlagInput; - args: ArgInput; - strict: boolean; - context: ParserContext | undefined; - '--'?: boolean; + argv: string[] + flags: FlagInput + args: ArgInput + strict: boolean + context: ParserContext | undefined + '--'?: boolean } export type ParserContext = Command & { - token?: FlagToken | ArgToken; + token?: FlagToken | ArgToken } -export type FlagInput = { [P in keyof T]: Flag } +export type FlagInput = {[P in keyof T]: Flag} -export type ArgInput = { [P in keyof T]: Arg } +export type ArgInput = {[P in keyof T]: Arg} diff --git a/src/interfaces/pjson.ts b/src/interfaces/pjson.ts index 22f0af21f..2da23f60f 100644 --- a/src/interfaces/pjson.ts +++ b/src/interfaces/pjson.ts @@ -1,115 +1,116 @@ import {HelpOptions} from './help' export interface PJSON { - [k: string]: any; - version: string; - dependencies?: {[name: string]: string}; - devDependencies?: {[name: string]: string}; + [k: string]: any + version: string + dependencies?: {[name: string]: string} + devDependencies?: {[name: string]: string} oclif: { - schema?: number; - bin?: string; - dirname?: string; - hooks?: Record; - plugins?: string[]; - }; + schema?: number + bin?: string + dirname?: string + hooks?: Record + plugins?: string[] + } } export namespace PJSON { export interface Plugin extends PJSON { - name: string; - version: string; + name: string + version: string oclif: PJSON['oclif'] & { - schema?: number; - description?: string; - topicSeparator?: ':' | ' '; - flexibleTaxonomy?: boolean; - hooks?: { [name: string]: (string | string[]) }; - commands?: string; - default?: string; - plugins?: string[]; - devPlugins?: string[]; - jitPlugins?: Record; - helpClass?: string; - helpOptions?: HelpOptions; - aliases?: { [name: string]: string | null }; - repositoryPrefix?: string; + schema?: number + description?: string + topicSeparator?: ':' | ' ' + flexibleTaxonomy?: boolean + hooks?: {[name: string]: string | string[]} + commands?: string + default?: string + plugins?: string[] + devPlugins?: string[] + jitPlugins?: Record + helpClass?: string + helpOptions?: HelpOptions + aliases?: {[name: string]: string | null} + repositoryPrefix?: string update: { - s3: S3; + s3: S3 autoupdate?: { - rollout?: number; - debounce?: number; - }; + rollout?: number + debounce?: number + } node: { - version?: string; - targets?: string[]; - }; - }; + version?: string + targets?: string[] + } + } topics?: { [k: string]: { - description?: string; - subtopics?: Plugin['oclif']['topics']; - hidden?: boolean; - }; - }; - additionalHelpFlags?: string[]; - additionalVersionFlags?: string[]; - state?: 'beta' | 'deprecated' | string; - }; + description?: string + subtopics?: Plugin['oclif']['topics'] + hidden?: boolean + } + } + additionalHelpFlags?: string[] + additionalVersionFlags?: string[] + state?: 'beta' | 'deprecated' | string + } } export interface S3 { - acl?: string; - bucket?: string; - host?: string; - xz?: boolean; - gz?: boolean; + acl?: string + bucket?: string + host?: string + xz?: boolean + gz?: boolean templates: { - target: S3.Templates; - vanilla: S3.Templates; - }; + target: S3.Templates + vanilla: S3.Templates + } } export namespace S3 { export interface Templates { - baseDir?: string; - versioned?: string; - unversioned?: string; - manifest?: string; + baseDir?: string + versioned?: string + unversioned?: string + manifest?: string } } export interface CLI extends Plugin { oclif: Plugin['oclif'] & { - schema?: number; - bin?: string; - binAliases?: string[]; - nsisCustomization?: string; - npmRegistry?: string; - scope?: string; - dirname?: string; - flexibleTaxonomy?: boolean; - jitPlugins?: Record; - }; + schema?: number + bin?: string + binAliases?: string[] + nsisCustomization?: string + npmRegistry?: string + scope?: string + dirname?: string + flexibleTaxonomy?: boolean + jitPlugins?: Record + } } export interface User extends PJSON { - private?: boolean; + private?: boolean oclif: PJSON['oclif'] & { - plugins?: (string | PluginTypes.User | PluginTypes.Link)[]; }; + plugins?: (string | PluginTypes.User | PluginTypes.Link)[] + } } export type PluginTypes = PluginTypes.User | PluginTypes.Link | {root: string} export namespace PluginTypes { export interface User { - type: 'user'; - name: string; - url?: string; - tag?: string; + type: 'user' + name: string + url?: string + tag?: string } export interface Link { - type: 'link'; - name: string; - root: string; + type: 'link' + name: string + root: string } } } diff --git a/src/interfaces/plugin.ts b/src/interfaces/plugin.ts index 772fbc10c..eb1d8f42b 100644 --- a/src/interfaces/plugin.ts +++ b/src/interfaces/plugin.ts @@ -3,26 +3,26 @@ import {PJSON} from './pjson' import {Topic} from './topic' export interface PluginOptions { - root: string; - name?: string; - type?: string; - tag?: string; - ignoreManifest?: boolean; - errorOnManifestCreate?: boolean; - respectNoCacheDefault?: boolean; - parent?: Plugin; - children?: Plugin[]; - flexibleTaxonomy?: boolean; - isRoot?: boolean; + root: string + name?: string + type?: string + tag?: string + ignoreManifest?: boolean + errorOnManifestCreate?: boolean + respectNoCacheDefault?: boolean + parent?: Plugin + children?: Plugin[] + flexibleTaxonomy?: boolean + isRoot?: boolean } export interface Options extends PluginOptions { - devPlugins?: boolean; - jitPlugins?: boolean; - userPlugins?: boolean; - channel?: string; - version?: string; - enablePerf?: boolean; + devPlugins?: boolean + jitPlugins?: boolean + userPlugins?: boolean + channel?: string + version?: string + enablePerf?: boolean plugins?: Map } @@ -30,62 +30,62 @@ export interface Plugin { /** * ../config version */ - _base: string; + _base: string /** * name from package.json */ - name: string; + name: string /** * aliases from package.json dependencies */ - alias: string; + alias: string /** * version from package.json * * example: 1.2.3 */ - version: string; + version: string /** * full package.json * * parsed with read-pkg */ - pjson: PJSON.Plugin | PJSON.CLI; + pjson: PJSON.Plugin | PJSON.CLI /** * used to tell the user how the plugin was installed * examples: core, link, user, dev */ - type: string; + type: string /** * Plugin is written in ESM or CommonJS */ - moduleType: 'module' | 'commonjs'; + moduleType: 'module' | 'commonjs' /** * base path of plugin */ - root: string; + root: string /** * npm dist-tag of plugin * only used for user plugins */ - tag?: string; + tag?: string /** * if it appears to be an npm package but does not look like it's really a CLI plugin, this is set to false */ - valid: boolean; + valid: boolean /** * True if the plugin is the root plugin. */ - isRoot: boolean; + isRoot: boolean - commands: Command.Loadable[]; - hooks: { [k: string]: string[] }; - readonly commandIDs: string[]; - readonly topics: Topic[]; - readonly hasManifest: boolean; + commands: Command.Loadable[] + 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; - load(): Promise; + findCommand(id: string, opts: {must: true}): Promise + findCommand(id: string, opts?: {must: boolean}): Promise | undefined + load(): Promise } diff --git a/src/interfaces/s3-manifest.ts b/src/interfaces/s3-manifest.ts index 0c18366cb..d81e7c042 100644 --- a/src/interfaces/s3-manifest.ts +++ b/src/interfaces/s3-manifest.ts @@ -1,14 +1,14 @@ export interface S3Manifest { - version: string; - sha: string; - gz: string; - xz?: string; - sha256gz: string; - sha256xz?: string; - baseDir: string; - rollout?: number; + version: string + sha: string + gz: string + xz?: string + sha256gz: string + sha256xz?: string + baseDir: string + rollout?: number node: { - compatible: string; - recommended: string; - }; + compatible: string + recommended: string + } } diff --git a/src/interfaces/topic.ts b/src/interfaces/topic.ts index 3765d097c..4360f0247 100644 --- a/src/interfaces/topic.ts +++ b/src/interfaces/topic.ts @@ -1,5 +1,5 @@ export interface Topic { - name: string; - description?: string; - hidden?: boolean; + name: string + description?: string + hidden?: boolean } diff --git a/src/interfaces/ts-config.ts b/src/interfaces/ts-config.ts index d871120e2..31e88934d 100644 --- a/src/interfaces/ts-config.ts +++ b/src/interfaces/ts-config.ts @@ -1,20 +1,20 @@ export interface TSConfig { compilerOptions: { - rootDir?: string; - rootDirs?: string[]; - outDir?: string; - target?: string; - esModuleInterop?: boolean; - experimentalDecorators?: boolean; - emitDecoratorMetadata?: boolean; - module?: string; - moduleResolution?: string; - sourceMap?: boolean; - jsx?: boolean; - }; + rootDir?: string + rootDirs?: string[] + outDir?: string + target?: string + esModuleInterop?: boolean + experimentalDecorators?: boolean + emitDecoratorMetadata?: boolean + module?: string + moduleResolution?: string + sourceMap?: boolean + jsx?: boolean + } 'ts-node'?: { - esm?: boolean; - experimentalSpecifierResolution?: 'node' | 'explicit'; - scope?: boolean; + esm?: boolean + experimentalSpecifierResolution?: 'node' | 'explicit' + scope?: boolean } } diff --git a/src/module-loader.ts b/src/module-loader.ts index ce715ed8a..fc5ec8a42 100644 --- a/src/module-loader.ts +++ b/src/module-loader.ts @@ -14,7 +14,7 @@ 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 => (config).type !== undefined +const isPlugin = (config: IConfig | IPlugin): config is IPlugin => (config).type !== undefined /** * Loads and returns a module. @@ -32,11 +32,11 @@ const isPlugin = (config: IConfig|IPlugin): config is IPlugin => (confi * * @returns {Promise<*>} The entire ESM module from dynamic import or CJS module by require. */ -export async function load(config: IConfig|IPlugin, modulePath: string): Promise { +export async function load(config: IConfig | IPlugin, modulePath: string): Promise { let filePath: string | undefined let isESM: boolean | undefined try { - ({isESM, filePath} = resolvePath(config, modulePath)) + ;({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') { @@ -64,11 +64,14 @@ export async function load(config: IConfig|IPlugin, modulePath: string): Promise * @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}> { +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)) + ;({isESM, filePath} = resolvePath(config, modulePath)) const module = isESM ? await import(pathToFileURL(filePath).href) : require(filePath) return {isESM, module, filePath} } catch (error: any) { @@ -97,7 +100,10 @@ export async function loadWithData(config: IConfig|IPlugin, modulePath: string): * @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}> { +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`) @@ -123,33 +129,33 @@ export async function loadWithDataFromManifest(cached: Command.Cached, modulePat } /** - * 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. - * - * @param {string} filePath - File path to test. - * - * @returns {boolean} The modulePath is an ES Module. - * @see https://www.npmjs.com/package/get-package-type - */ + * 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. + * + * @param {string} filePath - File path to test. + * + * @returns {boolean} The modulePath is an ES Module. + * @see https://www.npmjs.com/package/get-package-type + */ 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' - } + case '.js': + case '.jsx': + case '.ts': + case '.tsx': { + return getPackageType.sync(filePath) === 'module' + } - case '.mjs': - case '.mts': { - return true - } + case '.mjs': + case '.mts': { + return true + } - default: { - return false - } + default: { + return false + } } } @@ -164,7 +170,7 @@ export function isPathModule(filePath: string): boolean { * * @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} { +function resolvePath(config: IConfig | IPlugin, modulePath: string): {isESM: boolean; filePath: string} { let isESM: boolean let filePath: string | undefined @@ -172,7 +178,8 @@ function resolvePath(config: IConfig|IPlugin, modulePath: string): {isESM: boole filePath = require.resolve(modulePath) isESM = isPathModule(filePath) } catch { - filePath = (isPlugin(config) ? tsPath(config.root, modulePath, config) : tsPath(config.root, modulePath)) ?? modulePath + filePath = + (isPlugin(config) ? tsPath(config.root, modulePath, config) : tsPath(config.root, modulePath)) ?? modulePath let fileExists = false let isDirectory = false @@ -212,7 +219,7 @@ function resolvePath(config: IConfig|IPlugin, modulePath: string): {isESM: boole * * @returns {string | null} Modified file path including extension or null if file is not found. */ -function findFile(filePath: string) : string | null { +function findFile(filePath: string): string | null { // eslint-disable-next-line camelcase for (const extension of s_EXTENSIONS) { const testPath = `${filePath}${extension}` diff --git a/src/parser/errors.ts b/src/parser/errors.ts index 499120dbf..53d85c9a7 100644 --- a/src/parser/errors.ts +++ b/src/parser/errors.ts @@ -9,16 +9,16 @@ import {uniq} from '../util' export {CLIError} from '../errors' export type Validation = { - name: string; - status: 'success' | 'failed'; - validationFn: string; - reason?: string; + name: string + status: 'success' | 'failed' + validationFn: string + reason?: string } export class CLIParseError extends CLIError { public parse: CLIParseErrorOptions['parse'] - constructor(options: CLIParseErrorOptions & { message: string }) { + constructor(options: CLIParseErrorOptions & {message: string}) { options.message += '\nSee more help with --help' super(options.message) this.parse = options.parse @@ -28,11 +28,15 @@ export class CLIParseError extends CLIError { export class InvalidArgsSpecError extends CLIParseError { public args: ArgInput - constructor({args, parse}: CLIParseErrorOptions & { args: ArgInput }) { + constructor({args, parse}: CLIParseErrorOptions & {args: ArgInput}) { let message = 'Invalid argument spec' - const namedArgs = Object.values(args).filter(a => a.name) + const namedArgs = Object.values(args).filter((a) => a.name) if (namedArgs.length > 0) { - const list = renderList(namedArgs.map(a => [`${a.name} (${a.required ? 'required' : 'optional'})`, a.description] as [string, string])) + const list = renderList( + namedArgs.map( + (a) => [`${a.name} (${a.required ? 'required' : 'optional'})`, a.description] as [string, string], + ), + ) message += `:\n${list}` } @@ -44,17 +48,25 @@ export class InvalidArgsSpecError extends CLIParseError { export class RequiredArgsError extends CLIParseError { public args: Arg[] - constructor({args, parse, flagsWithMultiple}: CLIParseErrorOptions & { args: Arg[]; flagsWithMultiple?: string[] }) { + constructor({ + args, + parse, + flagsWithMultiple, + }: CLIParseErrorOptions & {args: Arg[]; flagsWithMultiple?: string[]}) { let message = `Missing ${args.length} required arg${args.length === 1 ? '' : 's'}` - const namedArgs = args.filter(a => a.name) + const namedArgs = args.filter((a) => a.name) if (namedArgs.length > 0) { - const list = renderList(namedArgs.map(a => [a.name, a.description] as [string, string])) + const list = renderList(namedArgs.map((a) => [a.name, a.description] as [string, string])) message += `:\n${list}` } if (flagsWithMultiple?.length) { - const flags = flagsWithMultiple.map(f => `--${f}`).join(', ') - message += `\n\nNote: ${flags} allow${flagsWithMultiple.length === 1 ? 's' : ''} multiple values. Because of this you need to provide all arguments before providing ${flagsWithMultiple.length === 1 ? 'that flag' : 'those flags'}.` + const flags = flagsWithMultiple.map((f) => `--${f}`).join(', ') + message += `\n\nNote: ${flags} allow${ + flagsWithMultiple.length === 1 ? 's' : '' + } multiple values. Because of this you need to provide all arguments before providing ${ + flagsWithMultiple.length === 1 ? 'that flag' : 'those flags' + }.` message += '\nAlternatively, you can use "--" to signify the end of the flags and the beginning of arguments.' } @@ -66,7 +78,7 @@ export class RequiredArgsError extends CLIParseError { export class RequiredFlagError extends CLIParseError { public flag: Flag - constructor({flag, parse}: CLIParseErrorOptions & { flag: Flag }) { + constructor({flag, parse}: CLIParseErrorOptions & {flag: Flag}) { const usage = renderList(flagUsages([flag], {displayRequired: false})) const message = `Missing required flag:\n${usage}` super({parse, message}) @@ -77,7 +89,7 @@ export class RequiredFlagError extends CLIParseError { export class UnexpectedArgsError extends CLIParseError { public args: unknown[] - constructor({parse, args}: CLIParseErrorOptions & { args: unknown[] }) { + constructor({parse, args}: CLIParseErrorOptions & {args: unknown[]}) { const message = `Unexpected argument${args.length === 1 ? '' : 's'}: ${args.join(', ')}` super({parse, message}) this.args = args @@ -87,7 +99,7 @@ export class UnexpectedArgsError extends CLIParseError { export class NonExistentFlagsError extends CLIParseError { public flags: string[] - constructor({parse, flags}: CLIParseErrorOptions & { flags: string[] }) { + constructor({parse, flags}: CLIParseErrorOptions & {flags: string[]}) { const message = `Nonexistent flag${flags.length === 1 ? '' : 's'}: ${flags.join(', ')}` super({parse, message}) this.flags = flags @@ -109,8 +121,8 @@ export class ArgInvalidOptionError extends CLIParseError { } export class FailedFlagValidationError extends CLIParseError { - constructor({parse, failed}: CLIParseErrorOptions & { failed: Validation[] }) { - const reasons = failed.map(r => r.reason) + constructor({parse, failed}: CLIParseErrorOptions & {failed: Validation[]}) { + const reasons = failed.map((r) => r.reason) const deduped = uniq(reasons) const errString = deduped.length === 1 ? 'error' : 'errors' const message = `The following ${errString} occurred:\n ${chalk.dim(deduped.join('\n '))}` diff --git a/src/parser/help.ts b/src/parser/help.ts index 7b6bfd396..09b5a90bf 100644 --- a/src/parser/help.ts +++ b/src/parser/help.ts @@ -23,6 +23,5 @@ export function flagUsage(flag: Flag, options: FlagUsageOptions = {}): [str export function flagUsages(flags: Flag[], options: FlagUsageOptions = {}): [string, string | undefined][] { if (flags.length === 0) return [] - return sortBy(flags, f => [f.char ? -1 : 1, f.char, f.name]) - .map(f => flagUsage(f, options)) + return sortBy(flags, (f) => [f.char ? -1 : 1, f.char, f.name]).map((f) => flagUsage(f, options)) } diff --git a/src/parser/index.ts b/src/parser/index.ts index 95a62791e..ef3bccf30 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -7,7 +7,7 @@ export {flagUsages} from './help' export async function parse< TFlags extends OutputFlags, BFlags extends OutputFlags, - TArgs extends OutputArgs + TArgs extends OutputArgs, >(argv: string[], options: Input): Promise> { const input = { argv, @@ -22,4 +22,3 @@ export async function parse< await validate({input, output}) return output as ParserOutput } - diff --git a/src/parser/parse.ts b/src/parser/parse.ts index fffcd68a7..1802e41ae 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -22,9 +22,12 @@ import {createInterface} from 'node:readline' let debug: any try { - debug = process.env.CLI_FLAGS_DEBUG === '1' ? require('debug')('../parser') : () => { - // noop - } + debug = + process.env.CLI_FLAGS_DEBUG === '1' + ? require('debug')('../parser') + : () => { + // noop + } } catch { debug = () => { // noop @@ -42,7 +45,7 @@ const readStdin = async (): Promise => { if (stdin.isTTY) return null - return new Promise(resolve => { + return new Promise((resolve) => { let result = '' const ac = new AbortController() const {signal} = ac @@ -54,7 +57,7 @@ const readStdin = async (): Promise => { terminal: false, }) - rl.on('line', line => { + rl.on('line', (line) => { result += line }) @@ -64,12 +67,16 @@ const readStdin = async (): Promise => { resolve(result) }) - signal.addEventListener('abort', () => { - debug('stdin aborted') - clearTimeout(timeout) - rl.close() - resolve(null) - }, {once: true}) + signal.addEventListener( + 'abort', + () => { + debug('stdin aborted') + clearTimeout(timeout) + rl.close() + resolve(null) + }, + {once: true}, + ) }) } @@ -77,30 +84,38 @@ 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) +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> { +export class Parser< + T extends ParserInput, + TFlags extends OutputFlags, + BFlags extends OutputFlags, + TArgs extends OutputArgs, +> { private readonly argv: string[] private readonly raw: ParsingToken[] = [] - private readonly booleanFlags: { [k: string]: BooleanFlag } - private readonly flagAliases: { [k: string]: BooleanFlag | OptionFlag } + private readonly booleanFlags: {[k: string]: BooleanFlag} + private readonly flagAliases: {[k: string]: BooleanFlag | OptionFlag} private readonly context: ParserContext private currentFlag?: OptionFlag constructor(private readonly input: T) { - this.context = input.context ?? {} as ParserContext + this.context = input.context ?? ({} as ParserContext) this.argv = [...input.argv] this._setNames() - this.booleanFlags = pickBy(input.flags, f => f.type === 'boolean') as any - this.flagAliases = Object.fromEntries(Object.values(input.flags).flatMap(flag => ([...flag.aliases ?? [], ...flag.charAliases ?? []]).map(a => [a, flag]))) + this.booleanFlags = pickBy(input.flags, (f) => f.type === 'boolean') as any + this.flagAliases = Object.fromEntries( + Object.values(input.flags).flatMap((flag) => + [...(flag.aliases ?? []), ...(flag.charAliases ?? [])].map((a) => [a, flag]), + ), + ) } public async parse(): Promise> { @@ -128,14 +143,14 @@ export class Parser o.type === 'flag' && o.flag === name)) { + if (!flag.multiple && this.raw.some((o) => 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) + 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') || this.findFlag(input).name) { + if (typeof input !== 'string' || this.findFlag(input).name) { throw new CLIError(`Flag --${name} expects a value`) } @@ -210,11 +225,17 @@ export class Parser { type ValueFunction = (fws: FlagWithStrategy, flags?: Record) => Promise - const parseFlagOrThrowError = async (input: any, flag: BooleanFlag | OptionFlag, context: ParserContext | undefined, token?: FlagToken) => { + const parseFlagOrThrowError = async ( + input: any, + flag: BooleanFlag | OptionFlag, + context: ParserContext | undefined, + token?: FlagToken, + ) => { if (!flag.parse) return input const ctx = { @@ -240,8 +261,8 @@ export class Parser { const tokenLength = fws.tokens?.length // user provided some input @@ -250,12 +271,13 @@ export class Parser parseFlagOrThrowError( - last(i.tokens)?.input !== `--no-${i.inputFlag.name}`, - i.inputFlag.flag, - this.context, - last(i.tokens), - ), + valueFunction: async (i) => + parseFlagOrThrowError( + last(i.tokens)?.input !== `--no-${i.inputFlag.name}`, + i.inputFlag.flag, + this.context, + last(i.tokens), + ), } } @@ -263,13 +285,28 @@ export class Parser (await Promise.all( - ((i.tokens ?? []).flatMap(token => token.input.split((i.inputFlag.flag as OptionFlag).delimiter ?? ','))) - // trim, and remove surrounding doubleQuotes (which would hav been needed if the elements contain spaces) - .map(v => 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)), + valueFunction: async (i) => + ( + await Promise.all( + (i.tokens ?? []) + .flatMap((token) => token.input.split((i.inputFlag.flag as OptionFlag).delimiter ?? ',')) + // trim, and remove surrounding doubleQuotes (which would hav been needed if the elements contain spaces) + .map((v) => + 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)), } } @@ -279,12 +316,14 @@ export class Parser Promise.all( - (fws.tokens ?? []).map(token => parseFlagOrThrowError( - validateOptions(i.inputFlag.flag as OptionFlag, token.input as string), - i.inputFlag.flag, - this.context, - token, - )), + (fws.tokens ?? []).map((token) => + parseFlagOrThrowError( + validateOptions(i.inputFlag.flag as OptionFlag, token.input as string), + i.inputFlag.flag, + this.context, + token, + ), + ), ), } } @@ -293,12 +332,13 @@ export class Parser parseFlagOrThrowError( - validateOptions(i.inputFlag.flag as OptionFlag, last(fws.tokens)?.input as string), - i.inputFlag.flag, - this.context, - last(fws.tokens), - ), + valueFunction: async (i: FlagWithStrategy) => + parseFlagOrThrowError( + validateOptions(i.inputFlag.flag as OptionFlag, last(fws.tokens)?.input as string), + i.inputFlag.flag, + this.context, + last(fws.tokens), + ), } } } @@ -309,18 +349,20 @@ export class Parser parseFlagOrThrowError( - validateOptions(i.inputFlag.flag as OptionFlag, valueFromEnv), - i.inputFlag.flag, - this.context, - ), + valueFunction: async (i: FlagWithStrategy) => + parseFlagOrThrowError( + validateOptions(i.inputFlag.flag as OptionFlag, valueFromEnv), + i.inputFlag.flag, + this.context, + ), } } if (fws.inputFlag.flag.type === 'boolean') { return { ...fws, - valueFunction: async (i: FlagWithStrategy) => isTruthy(process.env[i.inputFlag.flag.env as string] ?? 'false'), + valueFunction: async (i: FlagWithStrategy) => + isTruthy(process.env[i.inputFlag.flag.env as string] ?? 'false'), } } } @@ -329,10 +371,13 @@ export class Parser fws.inputFlag.flag.default({options: i.inputFlag.flag, flags: allFlags}) - : async () => fws.inputFlag.flag.default, + ...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, } } @@ -343,11 +388,14 @@ export class Parser { if (fws.inputFlag.flag.type === 'option' && fws.inputFlag.flag.defaultHelp) { return { - ...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) - // @ts-expect-error flag type isn't specific enough to know defaultHelp will definitely be there - : (i: FlagWithStrategy) => i.inputFlag.flag.defaultHelp, + ...fws, + helpFunction: + typeof fws.inputFlag.flag.defaultHelp === 'function' + ? (i: FlagWithStrategy, flags: Record, ...context) => + // @ts-expect-error flag type isn't specific enough to know defaultHelp will definitely be there + 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, } } @@ -355,69 +403,89 @@ export class Parser => { - const valueReferenceForHelp = fwsArrayToObject(flagsWithAllValues.filter(fws => !fws.metadata?.setFromDefault)) - return Promise.all(fwsArray.map(async fws => { - try { - if (fws.helpFunction) { - return { - ...fws, - metadata: { - ...fws.metadata, - defaultHelp: await fws.helpFunction?.(fws, valueReferenceForHelp, this.context), - }, + const valueReferenceForHelp = fwsArrayToObject(flagsWithAllValues.filter((fws) => !fws.metadata?.setFromDefault)) + return Promise.all( + fwsArray.map(async (fws) => { + try { + if (fws.helpFunction) { + return { + ...fws, + metadata: { + ...fws.metadata, + defaultHelp: await fws.helpFunction?.(fws, valueReferenceForHelp, this.context), + }, + } } + } catch { + // no-op } - } catch { - // no-op - } - return fws - })) + return fws + }), + ) } - const fwsArrayToObject = (fwsArray: FlagWithStrategy[]) => Object.fromEntries( - fwsArray.filter(fws => fws.value !== undefined) - .map(fws => [fws.inputFlag.name, fws.value]), - ) as TFlags & BFlags & { json: boolean | undefined } + const fwsArrayToObject = (fwsArray: FlagWithStrategy[]) => + 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, + name: string flag: Flag } - tokens?: FlagToken[], - valueFunction?: ValueFunction; - helpFunction?: (fws: FlagWithStrategy, flags: Record, ...args: any) => Promise; + tokens?: FlagToken[] + valueFunction?: ValueFunction + helpFunction?: (fws: FlagWithStrategy, flags: Record, ...args: any) => Promise metadata?: MetadataFlag - value?: any; + value?: any } const flagTokenMap = this.mapAndValidateFlags() - const flagsWithValues = await Promise.all(Object.entries(this.input.flags) - // we check them if they have a token, or might have env, default, or defaultHelp. Also include booleans so they get their default value - .filter(([name, flag]) => flag.type === 'boolean' || flag.env || flag.default !== undefined || 'defaultHelp' in flag || flagTokenMap.has(name)) - // match each possible flag to its token, if there is one - .map(([name, flag]): FlagWithStrategy => ({inputFlag: {name, flag}, tokens: flagTokenMap.get(name)})) - .map(fws => addValueFunction(fws)) - .filter(fws => fws.valueFunction !== undefined) - .map(fws => addHelpFunction(fws)) - // we can't apply the default values until all the other flags are resolved because `flag.default` can reference other flags - .map(async fws => (fws.metadata?.setFromDefault ? fws : {...fws, value: await fws.valueFunction?.(fws)}))) + const flagsWithValues = await Promise.all( + Object.entries(this.input.flags) + // we check them if they have a token, or might have env, default, or defaultHelp. Also include booleans so they get their default value + .filter( + ([name, flag]) => + flag.type === 'boolean' || + flag.env || + flag.default !== undefined || + 'defaultHelp' in flag || + flagTokenMap.has(name), + ) + // match each possible flag to its token, if there is one + .map(([name, flag]): FlagWithStrategy => ({inputFlag: {name, flag}, tokens: flagTokenMap.get(name)})) + .map((fws) => addValueFunction(fws)) + .filter((fws) => fws.valueFunction !== undefined) + .map((fws) => addHelpFunction(fws)) + // we can't apply the default values until all the other flags are resolved because `flag.default` can reference other flags + .map(async (fws) => (fws.metadata?.setFromDefault ? fws : {...fws, value: await fws.valueFunction?.(fws)})), + ) - const valueReference = fwsArrayToObject(flagsWithValues.filter(fws => !fws.metadata?.setFromDefault)) + const valueReference = fwsArrayToObject(flagsWithValues.filter((fws) => !fws.metadata?.setFromDefault)) - const flagsWithAllValues = await Promise.all(flagsWithValues - .map(async fws => (fws.metadata?.setFromDefault ? {...fws, value: await fws.valueFunction?.(fws, valueReference)} : fws))) + const flagsWithAllValues = await Promise.all( + flagsWithValues.map(async (fws) => + fws.metadata?.setFromDefault ? {...fws, value: await fws.valueFunction?.(fws, valueReference)} : fws, + ), + ) - const finalFlags = (flagsWithAllValues.some(fws => typeof fws.helpFunction === 'function')) ? await addDefaultHelp(flagsWithAllValues) : flagsWithAllValues + const finalFlags = flagsWithAllValues.some((fws) => typeof fws.helpFunction === 'function') + ? await addDefaultHelp(flagsWithAllValues) + : flagsWithAllValues return { flags: fwsArrayToObject(finalFlags), - metadata: {flags: Object.fromEntries(finalFlags.filter(fws => fws.metadata).map(fws => [fws.inputFlag.name, fws.metadata as MetadataFlag]))}, + metadata: { + flags: Object.fromEntries( + finalFlags.filter((fws) => fws.metadata).map((fws) => [fws.inputFlag.name, fws.metadata as MetadataFlag]), + ), + }, } } - private async _args(): Promise<{ argv: unknown[]; args: Record }> { + private async _args(): Promise<{argv: unknown[]; args: Record}> { const argv: unknown[] = [] const args = {} as Record const tokens = this._argTokens @@ -425,7 +493,7 @@ export class Parser t.arg === name) + const token = tokens.find((t) => t.arg === name) ctx.token = token! if (token) { @@ -493,13 +561,13 @@ export class Parser `--${f}`) - .join(' '), + .map((f) => `--${f}`) + .join(' '), ) } private get _argTokens(): ArgToken[] { - return this.raw.filter(o => o.type === 'arg') as ArgToken[] + return this.raw.filter((o) => o.type === 'arg') as ArgToken[] } private _setNames() { @@ -512,9 +580,9 @@ export class Parser { + private mapAndValidateFlags(): Map { const flagTokenMap = new Map() - for (const token of (this.raw.filter(o => o.type === 'flag') as FlagToken[])) { + for (const token of this.raw.filter((o) => o.type === 'flag') as FlagToken[]) { // fail fast if there are any invalid flags if (!(token.flag in this.input.flags)) { throw new CLIError(`Unexpected flag ${token.flag}`) @@ -543,18 +611,20 @@ export class Parser (this.input.flags[k].char === char && char !== undefined && this.input.flags[k].char !== undefined)) + return Object.keys(this.input.flags).find( + (k) => this.input.flags[k].char === char && char !== undefined && this.input.flags[k].char !== undefined, + ) } - private findFlag(arg: string): { name?: string, isLong: boolean } { + 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) + 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 0f7d0ea02..95ae24b84 100644 --- a/src/parser/validate.ts +++ b/src/parser/validate.ts @@ -9,10 +9,7 @@ import { } from './errors' import {uniq} from '../util' -export async function validate(parse: { - input: ParserInput; - output: ParserOutput; -}): Promise { +export async function validate(parse: {input: ParserInput; output: ParserOutput}): Promise { let cachedResolvedFlags: Record | undefined function validateArgs() { @@ -45,44 +42,46 @@ export async function validate(parse: { if (missingRequiredArgs.length > 0) { const flagsWithMultiple = Object.entries(parse.input.flags) - .filter(([_, flagDef]) => flagDef.type === 'option' && Boolean(flagDef.multiple)) - .map(([name]) => name) + .filter(([_, flagDef]) => flagDef.type === 'option' && Boolean(flagDef.multiple)) + .map(([name]) => name) throw new RequiredArgsError({parse, args: missingRequiredArgs, flagsWithMultiple}) } } async function validateFlags() { - const promises = Object.entries(parse.input.flags).flatMap(([name, flag]): Array> => { - if (parse.output.flags[name] !== undefined) { - return [ - ...flag.relationships ? validateRelationships(name, flag) : [], - ...flag.dependsOn ? [validateDependsOn(name, flag.dependsOn)] : [], - ...flag.exclusive ? [validateExclusive(name, flag.exclusive)] : [], - ...flag.exactlyOne ? [validateExactlyOne(name, flag.exactlyOne)] : [], - ] - } - - if (flag.required) { - return [{status: 'failed', name, validationFn: 'required', reason: `Missing required flag ${name}`}] - } - - if (flag.exactlyOne && flag.exactlyOne.length > 0) { - return [validateAcrossFlags(flag)] - } - - return [] - }) - - const results = (await Promise.all(promises)) - - const failed = results.filter(r => r.status === 'failed') + const promises = Object.entries(parse.input.flags).flatMap( + ([name, flag]): Array> => { + if (parse.output.flags[name] !== undefined) { + return [ + ...(flag.relationships ? validateRelationships(name, flag) : []), + ...(flag.dependsOn ? [validateDependsOn(name, flag.dependsOn)] : []), + ...(flag.exclusive ? [validateExclusive(name, flag.exclusive)] : []), + ...(flag.exactlyOne ? [validateExactlyOne(name, flag.exactlyOne)] : []), + ] + } + + if (flag.required) { + return [{status: 'failed', name, validationFn: 'required', reason: `Missing required flag ${name}`}] + } + + if (flag.exactlyOne && flag.exactlyOne.length > 0) { + return [validateAcrossFlags(flag)] + } + + return [] + }, + ) + + const results = await Promise.all(promises) + + const failed = results.filter((r) => r.status === 'failed') if (failed.length > 0) throw new FailedFlagValidationError({parse, failed}) } async function resolveFlags(flags: FlagRelationship[]): Promise> { if (cachedResolvedFlags) return cachedResolvedFlags - const promises = flags.map(async flag => { + const promises = flags.map(async (flag) => { if (typeof flag === 'string') { return [flag, parse.output.flags[flag]] } @@ -91,21 +90,22 @@ export async function validate(parse: { return result ? [flag.name, parse.output.flags[flag.name]] : null }) const resolved = await Promise.all(promises) - cachedResolvedFlags = Object.fromEntries(resolved.filter(r => r !== null) as [string, unknown][]) + cachedResolvedFlags = Object.fromEntries(resolved.filter((r) => r !== null) as [string, unknown][]) return cachedResolvedFlags } - const getPresentFlags = (flags: Record): string[] => Object.keys(flags).filter(key => key !== undefined) + const getPresentFlags = (flags: Record): string[] => + Object.keys(flags).filter((key) => key !== undefined) function validateAcrossFlags(flag: Flag): Validation { const base = {name: flag.name, validationFn: 'validateAcrossFlags'} const intersection = Object.entries(parse.input.flags) - .map(entry => entry[0]) // array of flag names - .filter(flagName => parse.output.flags[flagName] !== undefined) // with values - .filter(flagName => flag.exactlyOne && flag.exactlyOne.includes(flagName)) // and in the exactlyOne list + .map((entry) => entry[0]) // array of flag names + .filter((flagName) => parse.output.flags[flagName] !== undefined) // with values + .filter((flagName) => flag.exactlyOne && flag.exactlyOne.includes(flagName)) // and in the exactlyOne list if (intersection.length === 0) { // the command's exactlyOne may or may not include itself, so we'll use Set to add + de-dupe - const deduped = uniq(flag.exactlyOne?.map(flag => `--${flag}`) ?? []).join(', ') + const deduped = uniq(flag.exactlyOne?.map((flag) => `--${flag}`) ?? []).join(', ') const reason = `Exactly one of the following must be provided: ${deduped}` return {...base, status: 'failed', reason} } @@ -119,13 +119,15 @@ export async function validate(parse: { const keys = getPresentFlags(resolved) for (const flag of keys) { // do not enforce exclusivity for flags that were defaulted - if (parse.output.metadata.flags && parse.output.metadata.flags[flag]?.setFromDefault) - continue - if (parse.output.metadata.flags && parse.output.metadata.flags[name]?.setFromDefault) - continue + if (parse.output.metadata.flags && parse.output.metadata.flags[flag]?.setFromDefault) continue + if (parse.output.metadata.flags && parse.output.metadata.flags[name]?.setFromDefault) continue if (parse.output.flags[flag] !== undefined) { const flagValue = parse.output.metadata.flags?.[flag]?.defaultHelp ?? parse.output.flags[flag] - return {...base, status: 'failed', reason: `--${flag}=${flagValue} cannot also be provided when using --${name}`} + return { + ...base, + status: 'failed', + reason: `--${flag}=${flagValue} cannot also be provided when using --${name}`, + } } } @@ -150,10 +152,16 @@ export async function validate(parse: { const base = {name, validationFn: 'validateDependsOn'} const resolved = await resolveFlags(flags) - const foundAll = Object.values(resolved).every(val => val !== undefined) + const foundAll = Object.values(resolved).every((val) => val !== undefined) if (!foundAll) { - const formattedFlags = Object.keys(resolved).map(f => `--${f}`).join(', ') - return {...base, status: 'failed', reason: `All of the following must be provided when using --${name}: ${formattedFlags}`} + const formattedFlags = Object.keys(resolved) + .map((f) => `--${f}`) + .join(', ') + return { + ...base, + status: 'failed', + reason: `All of the following must be provided when using --${name}: ${formattedFlags}`, + } } return {...base, status: 'success'} @@ -165,33 +173,39 @@ export async function validate(parse: { const resolved = await resolveFlags(flags) const foundAtLeastOne = Object.values(resolved).some(Boolean) if (!foundAtLeastOne) { - const formattedFlags = Object.keys(resolved).map(f => `--${f}`).join(', ') - return {...base, status: 'failed', reason: `One of the following must be provided when using --${name}: ${formattedFlags}`} + const formattedFlags = Object.keys(resolved) + .map((f) => `--${f}`) + .join(', ') + return { + ...base, + status: 'failed', + reason: `One of the following must be provided when using --${name}: ${formattedFlags}`, + } } return {...base, status: 'success'} } function validateRelationships(name: string, flag: Flag): Promise[] { - return ((flag.relationships ?? []).map(relationship => { + return (flag.relationships ?? []).map((relationship) => { switch (relationship.type) { - case 'all': { - return validateDependsOn(name, relationship.flags) - } + case 'all': { + return validateDependsOn(name, relationship.flags) + } - case 'some': { - return validateSome(name, relationship.flags) - } + case 'some': { + return validateSome(name, relationship.flags) + } - case 'none': { - return validateExclusive(name, relationship.flags) - } + case 'none': { + return validateExclusive(name, relationship.flags) + } - default: { - throw new Error(`Unknown relationship type: ${relationship.type}`) + default: { + throw new Error(`Unknown relationship type: ${relationship.type}`) + } } - } - })) + }) } validateArgs() diff --git a/src/performance.ts b/src/performance.ts index 284eb2af8..0792dd84d 100644 --- a/src/performance.ts +++ b/src/performance.ts @@ -3,25 +3,25 @@ import {settings} from './settings' type Details = Record type PerfResult = { - name: string; + name: string duration: number details: Details - module: string; - method: string | undefined; - scope: string | undefined; + module: string + method: string | undefined + scope: string | undefined } type PerfHighlights = { - configLoadTime: number; - runTime: number; - initTime: number; - commandLoadTime: number; - commandRunTime: number; - pluginLoadTimes: Record; - corePluginsLoadTime: number; - userPluginsLoadTime: number; - linkedPluginsLoadTime: number; - hookRunTimes: Record>; + configLoadTime: number + runTime: number + initTime: number + commandLoadTime: number + commandRunTime: number + pluginLoadTimes: Record + corePluginsLoadTime: number + userPluginsLoadTime: number + linkedPluginsLoadTime: number + hookRunTimes: Record> } class Marker { @@ -33,7 +33,10 @@ class Marker { private startMarker: string private stopMarker: string - constructor(public name: string, public details: Details = {}) { + constructor( + public name: string, + public details: Details = {}, + ) { this.startMarker = `${this.name}-start` this.stopMarker = `${this.name}-stop` const [caller, scope] = name.split('#') @@ -76,7 +79,7 @@ export class Performance { } public static getResult(name: string): PerfResult | undefined { - return Performance.results.find(r => r.name === name) + return Performance.results.find((r) => r.name === name) } public static get highlights(): PerfHighlights { @@ -115,12 +118,12 @@ export class Performance { const markers = Object.values(Performance.markers) if (markers.length === 0) return - for (const marker of markers.filter(m => !m.stopped)) { + for (const marker of markers.filter((m) => !m.stopped)) { marker.stop() } - return new Promise(resolve => { - const perfObserver = new PerformanceObserver(items => { + return new Promise((resolve) => { + const perfObserver = new PerformanceObserver((items) => { for (const entry of items.getEntries()) { if (Performance.markers[entry.name]) { const marker = Performance.markers[entry.name] @@ -135,36 +138,47 @@ export class Performance { } } - const command = Performance.results.find(r => r.name.startsWith('config.runCommand')) - const commandLoadTime = command ? Performance.getResult(`plugin.findCommand#${command.details.plugin}.${command.details.command}`)?.duration ?? 0 : 0 + const command = Performance.results.find((r) => r.name.startsWith('config.runCommand')) + const commandLoadTime = command + ? Performance.getResult(`plugin.findCommand#${command.details.plugin}.${command.details.command}`) + ?.duration ?? 0 + : 0 - const pluginLoadTimes = Object.fromEntries(Performance.results - .filter(({name}) => name.startsWith('plugin.load#')) - .sort((a, b) => b.duration - a.duration) - .map(({scope, duration, details}) => [scope, {duration, details}])) + const pluginLoadTimes = Object.fromEntries( + Performance.results + .filter(({name}) => name.startsWith('plugin.load#')) + .sort((a, b) => b.duration - a.duration) + .map(({scope, duration, details}) => [scope, {duration, details}]), + ) const hookRunTimes = Performance.results - .filter(({name}) => name.startsWith('config.runHook#')) - .reduce((acc, perfResult) => { - const event = perfResult.details.event as string - if (event) { - if (!acc[event]) acc[event] = {} - acc[event][perfResult.scope!] = perfResult.duration - } else { - const event = perfResult.scope! - if (!acc[event]) acc[event] = {} - acc[event].total = perfResult.duration - } - - return acc - }, {} as Record>) - - const pluginLoadTimeByType = Object.fromEntries(Performance.results - .filter(({name}) => name.startsWith('config.loadPlugins#')) - .sort((a, b) => b.duration - a.duration) - .map(({scope, duration}) => [scope, duration])) - - const commandRunTime = Performance.results.find(({name}) => name.startsWith('config.runCommand#'))?.duration ?? 0 + .filter(({name}) => name.startsWith('config.runHook#')) + .reduce( + (acc, perfResult) => { + const event = perfResult.details.event as string + if (event) { + if (!acc[event]) acc[event] = {} + acc[event][perfResult.scope!] = perfResult.duration + } else { + const event = perfResult.scope! + if (!acc[event]) acc[event] = {} + acc[event].total = perfResult.duration + } + + return acc + }, + {} as Record>, + ) + + const pluginLoadTimeByType = Object.fromEntries( + Performance.results + .filter(({name}) => name.startsWith('config.loadPlugins#')) + .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, diff --git a/src/settings.ts b/src/settings.ts index 9f797f800..41ce4c569 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -5,20 +5,20 @@ export type Settings = { * Useful to set in the ./bin/dev script. * oclif.settings.debug = true; */ - debug?: boolean; + debug?: boolean /** * The path to the error.log file. * * NOTE: This is read-only and setting it will have no effect. */ - errlog?: string; + errlog?: string /** * Set the terminal width to a specified number of columns (characters) * * Environment Variable: * OCLIF_COLUMNS=80 */ - columns?: number; + columns?: number /** * Try to use ts-node to load typescript source files instead of * javascript files. @@ -29,13 +29,13 @@ export type Settings = { * Environment Variable: * NODE_ENV=development */ - tsnodeEnabled?: boolean; + tsnodeEnabled?: boolean /** * Enable performance tracking. Resulting data is available in the `perf` property of the `Config` class. * This will be overridden by the `enablePerf` property passed into Config constructor. */ - performanceEnabled?: boolean; -}; + performanceEnabled?: boolean +} // Set global.oclif to the new object if it wasn't set before if (!(global as any).oclif) (global as any).oclif = {} diff --git a/src/util/aggregate-flags.ts b/src/util/aggregate-flags.ts index bd13b9fd7..704c9fd5c 100644 --- a/src/util/aggregate-flags.ts +++ b/src/util/aggregate-flags.ts @@ -12,7 +12,5 @@ export function aggregateFlags( enableJsonFlag: boolean | undefined, ): FlagInput { const combinedFlags = {...baseFlags, ...flags} - return (enableJsonFlag - ? {json, ...combinedFlags} - : combinedFlags) as FlagInput + return (enableJsonFlag ? {json, ...combinedFlags} : combinedFlags) as FlagInput } diff --git a/src/util/cache-command.ts b/src/util/cache-command.ts index 176f98516..6d3b0579e 100644 --- a/src/util/cache-command.ts +++ b/src/util/cache-command.ts @@ -10,64 +10,79 @@ import {cacheDefaultValue} from './cache-default-value' // and flags as well as add in the json flag if enableJsonFlag is enabled. function mergePrototype(result: Command.Class, cmd: Command.Class): Command.Class { const proto = Object.getPrototypeOf(cmd) - const filteredProto = pickBy(proto, v => v !== undefined) as Command.Class + const filteredProto = pickBy(proto, (v) => v !== undefined) as Command.Class return Object.keys(proto).length > 0 ? mergePrototype({...filteredProto, ...result} as Command.Class, proto) : result } -async function cacheFlags(cmdFlags: FlagInput, respectNoCacheDefault: boolean): Promise> { - const promises = Object.entries(cmdFlags).map(async ([name, flag]) => ([name, { +async function cacheFlags( + cmdFlags: FlagInput, + respectNoCacheDefault: boolean, +): Promise> { + const promises = Object.entries(cmdFlags).map(async ([name, flag]) => [ name, - char: flag.char, - summary: flag.summary, - hidden: flag.hidden, - required: flag.required, - helpLabel: flag.helpLabel, - helpGroup: flag.helpGroup, - description: flag.description, - dependsOn: flag.dependsOn, - relationships: flag.relationships, - exclusive: flag.exclusive, - deprecated: flag.deprecated, - deprecateAliases: flag.deprecateAliases, - aliases: flag.aliases, - charAliases: flag.charAliases, - noCacheDefault: flag.noCacheDefault, - ...flag.type === 'boolean' ? { - allowNo: flag.allowNo, - type: flag.type, - } : { - type: flag.type, - helpValue: flag.helpValue, - multiple: flag.multiple, - options: flag.options, - delimiter: flag.delimiter, - default: await cacheDefaultValue(flag, respectNoCacheDefault), - hasDynamicHelp: typeof flag.defaultHelp === 'function', + { + name, + char: flag.char, + summary: flag.summary, + hidden: flag.hidden, + required: flag.required, + helpLabel: flag.helpLabel, + helpGroup: flag.helpGroup, + description: flag.description, + dependsOn: flag.dependsOn, + relationships: flag.relationships, + exclusive: flag.exclusive, + deprecated: flag.deprecated, + deprecateAliases: flag.deprecateAliases, + aliases: flag.aliases, + charAliases: flag.charAliases, + noCacheDefault: flag.noCacheDefault, + ...(flag.type === 'boolean' + ? { + allowNo: flag.allowNo, + type: flag.type, + } + : { + type: flag.type, + helpValue: flag.helpValue, + multiple: flag.multiple, + options: flag.options, + delimiter: flag.delimiter, + default: await cacheDefaultValue(flag, respectNoCacheDefault), + hasDynamicHelp: typeof flag.defaultHelp === 'function', + }), }, - }])) + ]) return Object.fromEntries(await Promise.all(promises)) } -async function cacheArgs(cmdArgs: ArgInput, respectNoCacheDefault: boolean): Promise> { - const promises = Object.entries(cmdArgs).map(async ([name, arg]) => ([name, { +async function cacheArgs( + cmdArgs: ArgInput, + respectNoCacheDefault: boolean, +): Promise> { + const promises = Object.entries(cmdArgs).map(async ([name, arg]) => [ name, - description: arg.description, - required: arg.required, - options: arg.options, - default: await cacheDefaultValue(arg, respectNoCacheDefault), - hidden: arg.hidden, - noCacheDefault: arg.noCacheDefault, - }])) + { + name, + description: arg.description, + required: arg.required, + options: arg.options, + default: await cacheDefaultValue(arg, respectNoCacheDefault), + hidden: arg.hidden, + noCacheDefault: arg.noCacheDefault, + }, + ]) return Object.fromEntries(await Promise.all(promises)) } -export async function cacheCommand(uncachedCmd: Command.Class, plugin?: IPlugin, respectNoCacheDefault = false): Promise { +export async function cacheCommand( + uncachedCmd: Command.Class, + plugin?: IPlugin, + respectNoCacheDefault = false, +): Promise { const cmd = mergePrototype(uncachedCmd, uncachedCmd) - const flags = await cacheFlags( - aggregateFlags(cmd.flags, cmd.baseFlags, cmd.enableJsonFlag), - respectNoCacheDefault, - ) + const flags = await cacheFlags(aggregateFlags(cmd.flags, cmd.baseFlags, cmd.enableJsonFlag), respectNoCacheDefault) const args = await cacheArgs(ensureArgObject(cmd.args), respectNoCacheDefault) const stdProperties = { @@ -87,7 +102,7 @@ export async function cacheCommand(uncachedCmd: Command.Class, plugin?: IPlugin, deprecateAliases: cmd.deprecateAliases, flags, args, - hasDynamicHelp: Object.values(flags).some(f => f.hasDynamicHelp), + hasDynamicHelp: Object.values(flags).some((f) => f.hasDynamicHelp), } // do not include these properties in manifest @@ -104,8 +119,8 @@ export async function cacheCommand(uncachedCmd: Command.Class, plugin?: IPlugin, // Add in any additional properties that are not standard command properties. const stdKeysAndIgnored = new Set([...Object.keys(stdProperties), ...ignoreCommandProperties]) - const keysToAdd = Object.keys(cmd).filter(property => !stdKeysAndIgnored.has(property)) - const additionalProperties = Object.fromEntries(keysToAdd.map(key => [key, (cmd as any)[key]])) + const keysToAdd = Object.keys(cmd).filter((property) => !stdKeysAndIgnored.has(property)) + const additionalProperties = Object.fromEntries(keysToAdd.map((key) => [key, (cmd as any)[key]])) return {...stdProperties, ...additionalProperties} } diff --git a/src/util/index.ts b/src/util/index.ts index df4a90ec4..b7284ff4c 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -7,9 +7,11 @@ import {readFileSync} from 'node:fs' const debug = require('debug') -export function pickBy>(obj: T, fn: (i: T[keyof T]) => boolean): Partial { - return Object.entries(obj) - .reduce((o, [k, v]) => { +export function pickBy>( + obj: T, + fn: (i: T[keyof T]) => boolean, +): Partial { + return Object.entries(obj).reduce((o, [k, v]) => { if (fn(v)) o[k] = v return o }, {} as any) @@ -93,7 +95,7 @@ export async function exists(path: string): Promise { } export const dirExists = async (input: string): Promise => { - if (!await exists(input)) { + if (!(await exists(input))) { throw new Error(`No directory found at ${input}`) } @@ -106,7 +108,7 @@ export const dirExists = async (input: string): Promise => { } export const fileExists = async (input: string): Promise => { - if (!await exists(input)) { + if (!(await exists(input))) { throw new Error(`No file found at ${input}`) } @@ -137,8 +139,10 @@ export function requireJson(...pathParts: string[]): T { * @param args Either an array of args or an object of args * @returns ArgInput */ -export function ensureArgObject(args?: any[] | ArgInput | { [name: string]: Command.Arg.Cached}): ArgInput { - return (Array.isArray(args) ? (args ?? []).reduce((x, y) => ({...x, [y.name]: y}), {} as ArgInput) : args ?? {}) as ArgInput +export function ensureArgObject(args?: any[] | ArgInput | {[name: string]: Command.Arg.Cached}): ArgInput { + return ( + Array.isArray(args) ? (args ?? []).reduce((x, y) => ({...x, [y.name]: y}), {} as ArgInput) : args ?? {} + ) as ArgInput } export function uniq(arr: T[]): T[] { @@ -179,12 +183,14 @@ 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 + return parse ? (JSON.parse(contents) as T) : contents } -export function mapValues, TResult>(obj: {[P in keyof T]: T[P]}, fn: (i: T[keyof T], k: keyof T) => TResult): {[P in keyof T]: TResult} { - return Object.entries(obj) - .reduce((o, [k, v]) => { +export function mapValues, TResult>( + obj: {[P in keyof T]: T[P]}, + fn: (i: T[keyof T], k: keyof T) => TResult, +): {[P in keyof T]: TResult} { + return Object.entries(obj).reduce((o, [k, v]) => { o[k] = fn(v as any, k as any) return o }, {} as any) diff --git a/test/cli-ux/export.test.ts b/test/cli-ux/export.test.ts index 4a62835b7..2e06dfb9d 100644 --- a/test/cli-ux/export.test.ts +++ b/test/cli-ux/export.test.ts @@ -20,4 +20,3 @@ describe('ux exports', () => { expect(typeof ExitError).to.be.equal('function') }) }) - diff --git a/test/cli-ux/fancy.ts b/test/cli-ux/fancy.ts index f6709c0d3..4cda54a82 100644 --- a/test/cli-ux/fancy.ts +++ b/test/cli-ux/fancy.ts @@ -7,16 +7,16 @@ import {ux} from '../../src/cli-ux' let count = 0 export const fancy = base -.do(async (ctx: {count: number; base: string}) => { - ctx.count = count++ - ctx.base = join(__dirname, '../tmp', `test-${ctx.count}`) - await rm(ctx.base, {recursive: true, force: true}) - const chalk = require('chalk') - chalk.level = 0 -}) -// eslint-disable-next-line unicorn/prefer-top-level-await -.finally(async () => { - await ux.done() -}) + .do(async (ctx: {count: number; base: string}) => { + ctx.count = count++ + ctx.base = join(__dirname, '../tmp', `test-${ctx.count}`) + await rm(ctx.base, {recursive: true, force: true}) + 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/index.test.ts b/test/cli-ux/index.test.ts index 866449a9a..dbbe52e76 100644 --- a/test/cli-ux/index.test.ts +++ b/test/cli-ux/index.test.ts @@ -5,17 +5,16 @@ const hyperlinker = require('hyperlinker') describe('url', () => { fancy - .env({FORCE_HYPERLINK: '1'}, {clear: true}) - .stdout() - .do(() => ux.url('sometext', 'https://google.com')) - .it('renders hyperlink', ({stdout}) => { - expect(stdout).to.equal('sometext\n') - }) + .env({FORCE_HYPERLINK: '1'}, {clear: true}) + .stdout() + .do(() => ux.url('sometext', 'https://google.com')) + .it('renders hyperlink', ({stdout}) => { + expect(stdout).to.equal('sometext\n') + }) }) describe('hyperlinker', () => { - fancy - .it('renders hyperlink', () => { + fancy.it('renders hyperlink', () => { const link = hyperlinker('sometext', 'https://google.com', {}) // eslint-disable-next-line unicorn/escape-case const expected = '\u001b]8;;https://google.com\u0007sometext\u001b]8;;\u0007' diff --git a/test/cli-ux/prompt.test.ts b/test/cli-ux/prompt.test.ts index 59ae25ce6..25d399c37 100644 --- a/test/cli-ux/prompt.test.ts +++ b/test/cli-ux/prompt.test.ts @@ -8,68 +8,67 @@ import {fancy} from './fancy' describe('prompt', () => { fancy - .stdout() - .stderr() - .end('requires input', async () => { - const promptPromise = ux.prompt('Require input?') - process.stdin.emit('data', '') - process.stdin.emit('data', 'answer') - const answer = await promptPromise - await ux.done() - expect(answer).to.equal('answer') - }) + .stdout() + .stderr() + .end('requires input', async () => { + const promptPromise = ux.prompt('Require input?') + process.stdin.emit('data', '') + process.stdin.emit('data', 'answer') + const answer = await promptPromise + await ux.done() + expect(answer).to.equal('answer') + }) fancy - .stdout() - .stderr() - .stdin('y') - .end('confirm', async () => { - const promptPromise = ux.confirm('yes/no?') - const answer = await promptPromise - await ux.done() - expect(answer).to.equal(true) - }) + .stdout() + .stderr() + .stdin('y') + .end('confirm', async () => { + const promptPromise = ux.confirm('yes/no?') + const answer = await promptPromise + await ux.done() + expect(answer).to.equal(true) + }) fancy - .stdout() - .stderr() - .stdin('n') - .end('confirm', async () => { - const promptPromise = ux.confirm('yes/no?') - const answer = await promptPromise - await ux.done() - expect(answer).to.equal(false) - }) + .stdout() + .stderr() + .stdin('n') + .end('confirm', async () => { + const promptPromise = ux.confirm('yes/no?') + const answer = await promptPromise + await ux.done() + expect(answer).to.equal(false) + }) fancy - .stdout() - .stderr() - .stdin('x') - .end('gets anykey', async () => { - const promptPromise = ux.anykey() - const answer = await promptPromise - await ux.done() - expect(answer).to.equal('x') - }) + .stdout() + .stderr() + .stdin('x') + .end('gets anykey', async () => { + const promptPromise = ux.anykey() + const answer = await promptPromise + await ux.done() + expect(answer).to.equal('x') + }) fancy - .stdout() - .stderr() - .end('does not require input', async () => { - const promptPromise = ux.prompt('Require input?', { - required: false, + .stdout() + .stderr() + .end('does not require input', async () => { + const promptPromise = ux.prompt('Require input?', { + required: false, + }) + process.stdin.emit('data', '') + const answer = await promptPromise + await ux.done() + expect(answer).to.equal('') }) - process.stdin.emit('data', '') - const answer = await promptPromise - await ux.done() - expect(answer).to.equal('') - }) fancy - .stdout() - .stderr() - .it('timeouts with no input', async () => { - await expect(ux.prompt('Require input?', {timeout: 1})) - .to.eventually.be.rejectedWith('Prompt timeout') - }) + .stdout() + .stderr() + .it('timeouts with no input', async () => { + await expect(ux.prompt('Require input?', {timeout: 1})).to.eventually.be.rejectedWith('Prompt timeout') + }) }) diff --git a/test/cli-ux/styled/object.test.ts b/test/cli-ux/styled/object.test.ts index 8c35056bd..4e14957df 100644 --- a/test/cli-ux/styled/object.test.ts +++ b/test/cli-ux/styled/object.test.ts @@ -3,9 +3,7 @@ import {expect, fancy} from 'fancy-test' import {ux} from '../../../src/cli-ux' describe('styled/object', () => { - fancy - .stdout() - .end('shows a table', output => { + fancy.stdout().end('shows a table', (output) => { ux.styledObject([ {foo: 1, bar: 1}, {foo: 2, bar: 2}, diff --git a/test/cli-ux/styled/progress.test.ts b/test/cli-ux/styled/progress.test.ts index 63ecd754a..6233bfe86 100644 --- a/test/cli-ux/styled/progress.test.ts +++ b/test/cli-ux/styled/progress.test.ts @@ -3,8 +3,7 @@ import {ux} from '../../../src/cli-ux' describe('progress', () => { // single bar - fancy - .end('single bar has default settings', _ => { + fancy.end('single bar has default settings', (_) => { const b1 = ux.progress({format: 'Example 1: Progress {bar} | {percentage}%'}) // @ts-expect-error because private member expect(b1.options.format).to.contain('Example 1: Progress') @@ -13,8 +12,7 @@ describe('progress', () => { }) // testing no settings passed, default settings created - fancy - .end('single bar, no bars array', _ => { + fancy.end('single bar, no bars array', (_) => { const b1 = ux.progress({}) // @ts-expect-error because private member expect(b1.options.format).to.contain('progress') @@ -24,9 +22,8 @@ describe('progress', () => { expect(b1.options.noTTYOutput).to.not.be.null }) // testing getProgressBar returns correct type - fancy - .end('typeof progress bar is object', _ => { + fancy.end('typeof progress bar is object', (_) => { const b1 = ux.progress({format: 'Example 1: Progress {bar} | {percentage}%'}) - expect(typeof (b1)).to.equal('object') + expect(typeof b1).to.equal('object') }) }) diff --git a/test/cli-ux/styled/table.e2e.ts b/test/cli-ux/styled/table.e2e.ts index c590209f1..ad921f706 100644 --- a/test/cli-ux/styled/table.e2e.ts +++ b/test/cli-ux/styled/table.e2e.ts @@ -3,9 +3,7 @@ import {ux} from '../../../src/cli-ux' describe('styled/table', () => { describe('null/undefined handling', () => { - fancy - .stdout() - .end('omits nulls and undefined by default', output => { + fancy.stdout().end('omits nulls and undefined by default', (output) => { const data = [{a: 1, b: '2', c: null, d: undefined}] ux.table(data, {a: {}, b: {}, c: {}, d: {}}) expect(output.stdout).to.include('1') @@ -17,10 +15,11 @@ describe('styled/table', () => { describe('scale tests', () => { const bigRows = 150_000 - fancy - .stdout() - .end('very tall tables don\'t exceed stack depth', output => { - const data = Array.from({length: bigRows}).fill({id: '123', name: 'foo', value: 'bar'}) as Record[] + fancy.stdout().end("very tall tables don't exceed stack depth", (output) => { + const data = Array.from({length: bigRows}).fill({id: '123', name: 'foo', value: 'bar'}) as Record< + string, + unknown + >[] const tallColumns = { id: {header: 'ID'}, name: {}, @@ -31,17 +30,16 @@ describe('styled/table', () => { expect(output.stdout).to.include('ID') }) - fancy - .stdout() - .end('very tall, wide tables don\'t exceed stack depth', output => { + fancy.stdout().end("very tall, wide tables don't exceed stack depth", (output) => { const columns = 100 const row = Object.fromEntries(Array.from({length: columns}).map((_, i) => [`col${i}`, 'foo'])) const data = Array.from({length: bigRows}).fill(row) as Record[] - const bigColumns = Object.fromEntries(Array.from({length: columns}).map((_, i) => [`col${i}`, {header: `col${i}`.toUpperCase()}])) + const bigColumns = Object.fromEntries( + Array.from({length: columns}).map((_, i) => [`col${i}`, {header: `col${i}`.toUpperCase()}]), + ) ux.table(data, bigColumns) expect(output.stdout).to.include('COL1') }) }) }) - diff --git a/test/cli-ux/styled/table.test.ts b/test/cli-ux/styled/table.test.ts index 6fa148a51..15058461c 100644 --- a/test/cli-ux/styled/table.test.ts +++ b/test/cli-ux/styled/table.test.ts @@ -70,14 +70,12 @@ const extendedHeader = `ID Name${ws.padEnd(14)}Web url${ws.padEnd(34)}Stack${ws // truncation rules? describe('styled/table', () => { - fancy - .end('export flags and display()', () => { - expect(typeof (ux.table.flags())).to.eq('object') - expect(typeof (ux.table)).to.eq('function') + fancy.end('export flags and display()', () => { + expect(typeof ux.table.flags()).to.eq('object') + expect(typeof ux.table).to.eq('function') }) - fancy - .end('has optional flags', _ => { + fancy.end('has optional flags', (_) => { const flags = ux.table.flags() expect(flags.columns).to.exist expect(flags.sort).to.exist @@ -89,9 +87,7 @@ describe('styled/table', () => { expect(flags['no-header']).to.exist }) - fancy - .stdout() - .end('displays table', output => { + fancy.stdout().end('displays table', (output) => { ux.table(apps, columns) expect(output.stdout).to.equal(` ID Name${ws.padEnd(14)} ─── ─────────────────${ws} @@ -100,16 +96,12 @@ describe('styled/table', () => { }) describe('columns', () => { - fancy - .stdout() - .end('use header value for id', output => { + fancy.stdout().end('use header value for id', (output) => { ux.table(apps, columns) expect(output.stdout.slice(1, 3)).to.equal('ID') }) - fancy - .stdout() - .end('shows extended columns/uses get() for value', output => { + fancy.stdout().end('shows extended columns/uses get() for value', (output) => { ux.table(apps, columns, {extended: true}) expect(output.stdout).to.equal(`${ws}${extendedHeader} ─── ───────────────── ──────────────────────────────────────── ─────────${ws} @@ -119,16 +111,12 @@ describe('styled/table', () => { }) describe('options', () => { - fancy - .stdout() - .end('shows extended columns', output => { + fancy.stdout().end('shows extended columns', (output) => { ux.table(apps, columns, {extended: true}) expect(output.stdout).to.contain(extendedHeader) }) - fancy - .stdout() - .end('shows title with divider', output => { + fancy.stdout().end('shows title with divider', (output) => { ux.table(apps, columns, {title: 'testing'}) expect(output.stdout).to.equal(`testing ======================= @@ -138,17 +126,13 @@ describe('styled/table', () => { | 321 supertable-test-2${ws}\n`) }) - fancy - .stdout() - .end('skips header', output => { + fancy.stdout().end('skips header', (output) => { ux.table(apps, columns, {'no-header': true}) expect(output.stdout).to.equal(` 123 supertable-test-1${ws} 321 supertable-test-2${ws}\n`) }) - fancy - .stdout() - .end('only displays given columns', output => { + fancy.stdout().end('only displays given columns', (output) => { ux.table(apps, columns, {columns: 'id'}) expect(output.stdout).to.equal(` ID${ws}${ws} ───${ws} @@ -156,36 +140,36 @@ describe('styled/table', () => { 321${ws}\n`) }) - fancy - .stdout() - .end('outputs in csv', output => { + fancy.stdout().end('outputs in csv', (output) => { ux.table(apps, columns, {output: 'csv'}) expect(output.stdout).to.equal(`ID,Name 123,supertable-test-1 321,supertable-test-2\n`) }) - fancy - .stdout() - .end('outputs in csv with escaped values', output => { - ux.table([ - { - id: '123\n2', - name: 'supertable-test-1', - }, - { - id: '12"3', - name: 'supertable-test-2', - }, - { - id: '123', - name: 'supertable-test-3,comma', - }, - { - id: '123', - name: 'supertable-test-4', - }, - ], columns, {output: 'csv'}) + fancy.stdout().end('outputs in csv with escaped values', (output) => { + ux.table( + [ + { + id: '123\n2', + name: 'supertable-test-1', + }, + { + id: '12"3', + name: 'supertable-test-2', + }, + { + id: '123', + name: 'supertable-test-3,comma', + }, + { + id: '123', + name: 'supertable-test-4', + }, + ], + columns, + {output: 'csv'}, + ) expect(output.stdout).to.equal(`ID,Name "123\n2","supertable-test-1" "12""3","supertable-test-2" @@ -193,26 +177,20 @@ describe('styled/table', () => { 123,supertable-test-4\n`) }) - fancy - .stdout() - .end('outputs in csv without headers', output => { + fancy.stdout().end('outputs in csv without headers', (output) => { ux.table(apps, columns, {output: 'csv', 'no-header': true}) expect(output.stdout).to.equal(`123,supertable-test-1 321,supertable-test-2\n`) }) - fancy - .stdout() - .end('outputs in csv with alias flag', output => { + fancy.stdout().end('outputs in csv with alias flag', (output) => { ux.table(apps, columns, {csv: true}) expect(output.stdout).to.equal(`ID,Name 123,supertable-test-1 321,supertable-test-2\n`) }) - fancy - .stdout() - .end('outputs in json', output => { + fancy.stdout().end('outputs in json', (output) => { ux.table(apps, columns, {output: 'json'}) expect(output.stdout).to.equal(`[ { @@ -227,9 +205,7 @@ describe('styled/table', () => { `) }) - fancy - .stdout() - .end('outputs in yaml', output => { + fancy.stdout().end('outputs in yaml', (output) => { ux.table(apps, columns, {output: 'yaml'}) expect(output.stdout).to.equal(`- id: '123' name: supertable-test-1 @@ -239,9 +215,7 @@ describe('styled/table', () => { `) }) - fancy - .stdout() - .end('sorts by property', output => { + fancy.stdout().end('sorts by property', (output) => { ux.table(apps, columns, {sort: '-name'}) expect(output.stdout).to.equal(` ID Name${ws.padEnd(14)} ─── ─────────────────${ws} @@ -249,18 +223,14 @@ describe('styled/table', () => { 123 supertable-test-1${ws}\n`) }) - fancy - .stdout() - .end('filters by property & value (partial string match)', output => { + fancy.stdout().end('filters by property & value (partial string match)', (output) => { ux.table(apps, columns, {filter: 'id=123'}) expect(output.stdout).to.equal(` ID Name${ws.padEnd(14)} ─── ─────────────────${ws} 123 supertable-test-1${ws}\n`) }) - fancy - .stdout() - .end('does not truncate', output => { + fancy.stdout().end('does not truncate', (output) => { const three = {...apps[0], id: '0'.repeat(80), name: 'supertable-test-3'} ux.table([...apps, three], columns, {filter: 'id=0', 'no-truncate': true}) expect(output.stdout).to.equal(` ID${ws.padEnd(78)} Name${ws.padEnd(14)} @@ -270,15 +240,13 @@ describe('styled/table', () => { }) describe('#flags', () => { - fancy - .end('includes only flags', _ => { + fancy.end('includes only flags', (_) => { const flags = ux.table.flags({only: 'columns'}) expect(flags.columns).to.be.a('object') expect((flags as any).sort).to.be.undefined }) - fancy - .end('excludes except flags', _ => { + fancy.end('excludes except flags', (_) => { const flags = ux.table.flags({except: 'columns'}) expect((flags as any).columns).to.be.undefined expect(flags.sort).to.be.a('object') @@ -286,9 +254,7 @@ describe('styled/table', () => { }) describe('edge cases', () => { - fancy - .stdout() - .end('ignores header case', output => { + fancy.stdout().end('ignores header case', (output) => { ux.table(apps, columns, {columns: 'iD,Name', filter: 'nAMe=supertable-test', sort: '-ID'}) expect(output.stdout).to.equal(` ID Name${ws.padEnd(14)} ─── ─────────────────${ws} @@ -296,9 +262,7 @@ describe('styled/table', () => { 123 supertable-test-1${ws}\n`) }) - fancy - .stdout() - .end('displays multiline cell', output => { + fancy.stdout().end('displays multiline cell', (output) => { /* eslint-disable camelcase */ const app3 = { build_stack: { @@ -325,31 +289,31 @@ describe('styled/table', () => { } fancy - .do(() => { - Object.assign(screen, {stdtermwidth: 9}) - process.env.CLI_UX_SKIP_TTY_CHECK = 'true' - }) - .finally(() => { - Object.assign(screen, {stdtermwidth: orig.stdtermwidth}) - process.env.CLI_UX_SKIP_TTY_CHECK = orig.CLI_UX_SKIP_TTY_CHECK - }) - .stdout({stripColor: false}) - .end('correctly truncates columns with fullwidth characters or ansi escape sequences', output => { - /* eslint-disable camelcase */ - const app4 = { - build_stack: { - name: 'heroku-16', - }, - id: '456', - name: '\u001B[31m超级衚栌—测试\u001B[0m', - web_url: 'https://supertable-test-1.herokuapp.com/', - } - /* eslint-enable camelcase */ - - ux.table([...apps, app4 as any], {name: {}}, {'no-header': true}) - expect(output.stdout).to.equal(` super
${ws} + .do(() => { + Object.assign(screen, {stdtermwidth: 9}) + process.env.CLI_UX_SKIP_TTY_CHECK = 'true' + }) + .finally(() => { + Object.assign(screen, {stdtermwidth: orig.stdtermwidth}) + process.env.CLI_UX_SKIP_TTY_CHECK = orig.CLI_UX_SKIP_TTY_CHECK + }) + .stdout({stripColor: false}) + .end('correctly truncates columns with fullwidth characters or ansi escape sequences', (output) => { + /* eslint-disable camelcase */ + const app4 = { + build_stack: { + name: 'heroku-16', + }, + id: '456', + name: '\u001B[31m超级衚栌—测试\u001B[0m', + web_url: 'https://supertable-test-1.herokuapp.com/', + } + /* eslint-enable camelcase */ + + ux.table([...apps, app4 as any], {name: {}}, {'no-header': true}) + expect(output.stdout).to.equal(` super
${ws} super
${ws} \u001B[31m超级\u001B[39m
${ws}${ws}\n`) - }) + }) }) }) diff --git a/test/cli-ux/styled/tree.test.ts b/test/cli-ux/styled/tree.test.ts index 0706d0649..c628bdec0 100644 --- a/test/cli-ux/styled/tree.test.ts +++ b/test/cli-ux/styled/tree.test.ts @@ -3,9 +3,7 @@ import {expect, fancy} from 'fancy-test' import {ux} from '../../../src/cli-ux' describe('styled/tree', () => { - fancy - .stdout() - .end('shows the tree', output => { + fancy.stdout().end('shows the tree', (output) => { const tree = ux.tree() tree.insert('foo') tree.insert('bar') diff --git a/test/command/command.test.ts b/test/command/command.test.ts index ee8991892..892284216 100644 --- a/test/command/command.test.ts +++ b/test/command/command.test.ts @@ -23,56 +23,54 @@ class CodeError extends Error { describe('command', () => { fancy - .stdout() - .do(() => Command.run([])) - .do(output => expect(output.stdout).to.equal('foo\n')) - .it('logs to stdout') + .stdout() + .do(() => Command.run([])) + .do((output) => expect(output.stdout).to.equal('foo\n')) + .it('logs to stdout') fancy - .do(async () => { - class Command extends Base { - static description = 'test command' + .do(async () => { + class Command extends Base { + static description = 'test command' - async run() { - return 101 + async run() { + return 101 + } } - } - expect(await Command.run([])).to.equal(101) - }) - .it('returns value') + expect(await Command.run([])).to.equal(101) + }) + .it('returns value') fancy - .do(() => { - class Command extends Base { - async run() { - throw new Error('new x error') + .do(() => { + class Command extends Base { + async run() { + throw new Error('new x error') + } } - } - return Command.run([]) - }) - .catch(/new x error/) - .it('errors out') + return Command.run([]) + }) + .catch(/new x error/) + .it('errors out') fancy - .stdout() - .do(() => { - class Command extends Base { - async run() { - this.exit(0) + .stdout() + .do(() => { + class Command extends Base { + async run() { + this.exit(0) + } } - } - return Command.run([]) - }) - .catch(/EEXIT: 0/) - .it('exits with 0') + return Command.run([]) + }) + .catch(/EEXIT: 0/) + .it('exits with 0') describe('parse', () => { - fancy - .stdout() - .it('has a flag', async ctx => { + fancy.stdout().it('has a flag', async (ctx) => { class CMD extends Base { static flags = { foo: Flags.string(), @@ -91,18 +89,18 @@ describe('command', () => { describe('.log()', () => { fancy - .stdout() - .do(async () => { - class CMD extends Command { - async run() { - this.log('json output: %j', {a: 'foobar'}) + .stdout() + .do(async () => { + class CMD extends Command { + async run() { + this.log('json output: %j', {a: 'foobar'}) + } } - } - await CMD.run([]) - }) - .do(ctx => expect(ctx.stdout).to.equal('json output: {"a":"foobar"}\n')) - .it('uses util.format()') + await CMD.run([]) + }) + .do((ctx) => expect(ctx.stdout).to.equal('json output: {"a":"foobar"}\n')) + .it('uses util.format()') }) describe('flags with deprecated aliases', () => { @@ -123,326 +121,331 @@ describe('command', () => { } fancy - .stdout() - .stderr() - .do(async () => CMD.run(['--username', 'astro'])) - .do(ctx => expect(ctx.stderr).to.include('Warning: The "--username" flag has been deprecated. Use "--name | -o"')) - .it('shows warning for deprecated flag alias') + .stdout() + .stderr() + .do(async () => CMD.run(['--username', 'astro'])) + .do((ctx) => + expect(ctx.stderr).to.include('Warning: The "--username" flag has been deprecated. Use "--name | -o"'), + ) + .it('shows warning for deprecated flag alias') fancy - .stdout() - .stderr() - .do(async () => CMD.run(['--target-user', 'astro'])) - .do(ctx => expect(ctx.stderr).to.include('Warning: The "--target-user" flag has been deprecated. Use "--name | -o"')) - .it('shows warning for deprecated flag alias') + .stdout() + .stderr() + .do(async () => CMD.run(['--target-user', 'astro'])) + .do((ctx) => + expect(ctx.stderr).to.include('Warning: The "--target-user" flag has been deprecated. Use "--name | -o"'), + ) + .it('shows warning for deprecated flag alias') fancy - .stdout() - .stderr() - .do(async () => CMD.run(['-u', 'astro'])) - .do(ctx => expect(ctx.stderr).to.include('Warning: The "-u" flag has been deprecated. Use "--name | -o"')) - .it('shows warning for deprecated short char flag alias') + .stdout() + .stderr() + .do(async () => CMD.run(['-u', 'astro'])) + .do((ctx) => expect(ctx.stderr).to.include('Warning: The "-u" flag has been deprecated. Use "--name | -o"')) + .it('shows warning for deprecated short char flag alias') fancy - .stdout() - .stderr() - .do(async () => CMD.run(['--name', 'username'])) - .do(ctx => expect(ctx.stderr).to.be.empty) - .it('shows no warning when using proper flag name with a value that matches a flag alias') + .stdout() + .stderr() + .do(async () => CMD.run(['--name', 'username'])) + .do((ctx) => expect(ctx.stderr).to.be.empty) + .it('shows no warning when using proper flag name with a value that matches a flag alias') fancy - .stdout() - .stderr() - .do(async () => CMD.run(['--other', 'target-user'])) - .do(ctx => expect(ctx.stderr).to.be.empty) - .it('shows no warning when using another flag with a value that matches a deprecated flag alias') + .stdout() + .stderr() + .do(async () => CMD.run(['--other', 'target-user'])) + .do((ctx) => expect(ctx.stderr).to.be.empty) + .it('shows no warning when using another flag with a value that matches a deprecated flag alias') fancy - .stdout() - .stderr() - .do(async () => CMD.run(['--name', 'u'])) - .do(ctx => expect(ctx.stderr).to.be.empty) - .it('shows no warning when proper flag name with a value that matches a short char flag alias') + .stdout() + .stderr() + .do(async () => CMD.run(['--name', 'u'])) + .do((ctx) => expect(ctx.stderr).to.be.empty) + .it('shows no warning when proper flag name with a value that matches a short char flag alias') }) describe('deprecated flags', () => { fancy - .stdout() - .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') + .stdout() + .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') + } } - } - await CMD.run(['--name', 'astro']) - }) - .do(ctx => expect(ctx.stderr).to.include('Warning: The "name" flag has been deprecated')) - .it('shows warning for deprecated flags') + await CMD.run(['--name', 'astro']) + }) + .do((ctx) => expect(ctx.stderr).to.include('Warning: The "name" flag has been deprecated')) + .it('shows warning for deprecated flags') }) describe('deprecated flags that are not provided', () => { fancy - .stdout() - .stderr() - .do(async () => { - class CMD extends Command { - static flags = { - name: Flags.string({ - deprecated: { - to: '--full-name', - version: '2.0.0', - }, - }), - force: Flags.boolean(), + .stdout() + .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') + } } - async run() { - await this.parse(CMD) - this.log('running command') - } - } - - await CMD.run(['--force']) - }) - .do(ctx => expect(ctx.stderr).to.not.include('Warning: The "name" flag has been deprecated')) - .it('does not show warning for deprecated flags if they are not provided') + await CMD.run(['--force']) + }) + .do((ctx) => expect(ctx.stderr).to.not.include('Warning: The "name" flag has been deprecated')) + .it('does not show warning for deprecated flags if they are not provided') }) describe('deprecated state', () => { fancy - .stdout() - .stderr() - .do(async () => { - class CMD extends Command { - static id = 'my:command' - static state = 'deprecated' - - async run() { - this.log('running command') + .stdout() + .stderr() + .do(async () => { + class CMD extends Command { + static id = 'my:command' + static state = 'deprecated' + + async run() { + this.log('running command') + } } - } - await CMD.run([]) - }) - .do(ctx => expect(ctx.stderr).to.include('Warning: The "my:command" command has been deprecated')) - .it('shows warning for deprecated command') + await CMD.run([]) + }) + .do((ctx) => expect(ctx.stderr).to.include('Warning: The "my:command" command has been deprecated')) + .it('shows warning for deprecated command') }) describe('deprecated state with options', () => { fancy - .stdout() - .stderr() - .do(async () => { - class CMD extends Command { - static id = 'my:command' - static state = 'deprecated' - static deprecationOptions = { - version: '2.0.0', - to: 'my:other:command', + .stdout() + .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') + } } - async run() { - this.log('running command') - } - } - - await CMD.run([]) - }) - .do(ctx => { - expect(ctx.stderr).to.include('Warning: The "my:command" command has been deprecated') - expect(ctx.stderr).to.include('in version 2.0.0') - expect(ctx.stderr).to.include('Use "my:other:command" instead') - }) - .it('shows warning for deprecated command with custom options') + await CMD.run([]) + }) + .do((ctx) => { + expect(ctx.stderr).to.include('Warning: The "my:command" command has been deprecated') + expect(ctx.stderr).to.include('in version 2.0.0') + expect(ctx.stderr).to.include('Use "my:other:command" instead') + }) + .it('shows warning for deprecated command with custom options') }) describe('stdout err', () => { fancy - .stdout() - .do(async () => { - class CMD extends Command { - async run() { - process.stdout.emit('error', new CodeError('dd')) + .stdout() + .do(async () => { + class CMD extends Command { + async run() { + process.stdout.emit('error', new CodeError('dd')) + } } - } - await CMD.run([]) - }) - .catch(/dd/) - .it('test stdout error throws') + await CMD.run([]) + }) + .catch(/dd/) + .it('test stdout error throws') fancy - .stdout() - .do(async () => { - class CMD extends Command { - async run() { - process.stdout.emit('error', new CodeError('EPIPE')) - this.log('json output: %j', {a: 'foobar'}) + .stdout() + .do(async () => { + class CMD extends Command { + async run() { + process.stdout.emit('error', new CodeError('EPIPE')) + this.log('json output: %j', {a: 'foobar'}) + } } - } - await CMD.run([]) - }) - .do(ctx => expect(ctx.stdout).to.equal('json output: {"a":"foobar"}\n')) - .it('test stdout EPIPE swallowed') + await CMD.run([]) + }) + .do((ctx) => expect(ctx.stdout).to.equal('json output: {"a":"foobar"}\n')) + .it('test stdout EPIPE swallowed') }) describe('json enabled and pass-through tests', () => { fancy - .stdout() - .do(async () => { - class CMD extends Command { - static enableJsonFlag = true - - async run() { - this.log('not json output') + .stdout() + .do(async () => { + class CMD extends Command { + static enableJsonFlag = true + + async run() { + this.log('not json output') + } } - } - const cmd = new CMD([], {} as any) - expect(cmd.jsonEnabled()).to.equal(false) - }) - .it('json enabled/pass through disabled/no --json flag/jsonEnabled() should be false') + const cmd = new CMD([], {} as any) + expect(cmd.jsonEnabled()).to.equal(false) + }) + .it('json enabled/pass through disabled/no --json flag/jsonEnabled() should be false') fancy - .stdout() - .do(async () => { - class CMD extends Command { - static enableJsonFlag = true + .stdout() + .do(async () => { + class CMD extends Command { + static enableJsonFlag = true - async run() {} - } + async run() {} + } - const cmd = new CMD(['--json'], {} as any) - expect(cmd.jsonEnabled()).to.equal(true) - }) - .it('json enabled/pass through disabled/--json flag before --/jsonEnabled() should be true') + const cmd = new CMD(['--json'], {} as any) + expect(cmd.jsonEnabled()).to.equal(true) + }) + .it('json enabled/pass through disabled/--json flag before --/jsonEnabled() should be true') fancy - .stdout() - .do(async () => { - class CMD extends Command { - static enableJsonFlag = true - async run() {} - } + .stdout() + .do(async () => { + class CMD extends Command { + static enableJsonFlag = true + async run() {} + } - // mock a scopedEnvVar being set to JSON - const cmd = new CMD([], { - bin: 'FOO', scopedEnvVar: (foo: string) => foo.includes('CONTENT_TYPE') ? 'json' : undefined, - } as any) - expect(cmd.jsonEnabled()).to.equal(true) - }) - .it('json enabled from env') + // mock a scopedEnvVar being set to JSON + const cmd = new CMD([], { + bin: 'FOO', + scopedEnvVar: (foo: string) => (foo.includes('CONTENT_TYPE') ? 'json' : undefined), + } as any) + expect(cmd.jsonEnabled()).to.equal(true) + }) + .it('json enabled from env') fancy - .stdout() - .do(async () => { - class CMD extends Command { - static enableJsonFlag = true - - async run() { - const {flags} = await cmd.parse(CMD, ['--json']) - expect(flags.json).to.equal(true, 'json flag should be true') + .stdout() + .do(async () => { + class CMD extends Command { + static enableJsonFlag = 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) - expect(cmd.jsonEnabled()).to.equal(true) - }) - .it('json enabled/pass through enabled/--json flag before --/jsonEnabled() should be true') + const cmd = new CMD(['--json'], {} as any) + expect(cmd.jsonEnabled()).to.equal(true) + }) + .it('json enabled/pass through enabled/--json flag before --/jsonEnabled() should be true') fancy - .stdout() - .do(async () => { - class CMD extends Command { - static enableJsonFlag = 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') + .stdout() + .do(async () => { + class CMD extends Command { + static enableJsonFlag = 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) - expect(cmd.jsonEnabled()).to.equal(false) - }) - .it('json enabled/pass through enabled/--json flag after --/jsonEnabled() should be false') + const cmd = new CMD(['--', '--json'], {} as any) + expect(cmd.jsonEnabled()).to.equal(false) + }) + .it('json enabled/pass through enabled/--json flag after --/jsonEnabled() should be false') fancy - .stdout() - .do(async () => { - class CMD extends Command { - static enableJsonFlag = true - - async run() { - const {flags} = await cmd.parse(CMD, ['--foo', '--json']) - expect(flags.json).to.equal(true, 'json flag should be true') + .stdout() + .do(async () => { + class CMD extends Command { + static enableJsonFlag = 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) - expect(cmd.jsonEnabled()).to.equal(true) - }) - .it('json enabled/pass through enabled/--json flag before --/extra param/jsonEnabled() should be true') + const cmd = new CMD(['foo', '--json'], {} as any) + expect(cmd.jsonEnabled()).to.equal(true) + }) + .it('json enabled/pass through enabled/--json flag before --/extra param/jsonEnabled() should be true') fancy - .stdout() - .do(async () => { - class CMD extends Command { - static enableJsonFlag = 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') + .stdout() + .do(async () => { + class CMD extends Command { + static enableJsonFlag = 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) - expect(cmd.jsonEnabled()).to.equal(false) - }) - .it('json enabled/pass through enabled/--json flag after --/extra param/jsonEnabled() should be false') + const cmd = new CMD(['--foo', '--', '--json'], {} as any) + expect(cmd.jsonEnabled()).to.equal(false) + }) + .it('json enabled/pass through enabled/--json flag after --/extra param/jsonEnabled() should be false') fancy - .stdout() - .do(async () => { - class CMD extends Command { - static enableJsonFlag = true + .stdout() + .do(async () => { + class CMD extends Command { + static enableJsonFlag = true - async run() {} - } + async run() {} + } - const cmd = new CMD(['--json', '--'], {} as any) - expect(cmd.jsonEnabled()).to.equal(true) - }) - .it('json enabled/pass through enabled/--json flag before --/jsonEnabled() should be true') + const cmd = new CMD(['--json', '--'], {} as any) + expect(cmd.jsonEnabled()).to.equal(true) + }) + .it('json enabled/pass through enabled/--json flag before --/jsonEnabled() should be true') fancy - .stdout() - .do(async () => { - class CMD extends Command { - static enableJsonFlag = false - static '--' = true + .stdout() + .do(async () => { + class CMD extends Command { + static enableJsonFlag = false + static '--' = true - async run() {} - } + async run() {} + } - const cmd = new CMD(['--json'], {} as any) - expect(cmd.jsonEnabled()).to.equal(false) - }) - .it('json disabled/pass through enable/--json flag before --/jsonEnabled() should be false') + const cmd = new CMD(['--json'], {} as any) + expect(cmd.jsonEnabled()).to.equal(false) + }) + .it('json disabled/pass through enable/--json flag before --/jsonEnabled() should be false') }) }) diff --git a/test/command/fixtures/typescript/tsconfig.json b/test/command/fixtures/typescript/tsconfig.json index 75c61de88..8d0083147 100644 --- a/test/command/fixtures/typescript/tsconfig.json +++ b/test/command/fixtures/typescript/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "outDir": "./lib", - "rootDirs": [ - "./src" - ] + "rootDirs": ["./src"] }, - "include": [ - "./src/**/*" - ] + "include": ["./src/**/*"] } diff --git a/test/command/helpers/test-help-in-lib/lib/test-help-plugin.js b/test/command/helpers/test-help-in-lib/lib/test-help-plugin.js index 77e76fd87..1bad603da 100644 --- a/test/command/helpers/test-help-in-lib/lib/test-help-plugin.js +++ b/test/command/helpers/test-help-in-lib/lib/test-help-plugin.js @@ -1,20 +1,20 @@ /* eslint-disable */ -'use strict'; -Object.defineProperty (exports, '__esModule', {value: true}); -const sinon_1 = require ('sinon'); +'use strict' +Object.defineProperty(exports, '__esModule', {value: true}) +const sinon_1 = require('sinon') class default_1 { - constructor (config, opts) { - this.showCommandHelp = sinon_1.spy (() => { - console.log ('hello from test-help-plugin #showCommandHelp in the lib folder and in compiled javascript'); - }); - this.showHelp = sinon_1.spy (() => { - console.log ('hello showHelp'); - }); - config.showCommandHelpSpy = this.showCommandHelp; - config.showHelpSpy = this.showHelp; + constructor(config, opts) { + this.showCommandHelp = sinon_1.spy(() => { + console.log('hello from test-help-plugin #showCommandHelp in the lib folder and in compiled javascript') + }) + this.showHelp = sinon_1.spy(() => { + console.log('hello showHelp') + }) + config.showCommandHelpSpy = this.showCommandHelp + config.showHelpSpy = this.showHelp } - command () { - throw new Error ('not needed for testing @oclif/command'); + command() { + throw new Error('not needed for testing @oclif/command') } } -exports.default = default_1; +exports.default = default_1 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 de25f5e50..60ccafb70 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,7 +1,7 @@ import {SinonSpy, spy} from 'sinon' import {HelpBase, Interfaces} from '../../../../../src' -export type TestHelpClassConfig = Interfaces.Config & { showCommandHelpSpy?: SinonSpy; showHelpSpy?: SinonSpy } +export type TestHelpClassConfig = Interfaces.Config & {showCommandHelpSpy?: SinonSpy; showHelpSpy?: SinonSpy} export default class extends HelpBase { constructor(config: any, opts: any) { diff --git a/test/command/main-esm.test.ts b/test/command/main-esm.test.ts index d971a48aa..93525219d 100644 --- a/test/command/main-esm.test.ts +++ b/test/command/main-esm.test.ts @@ -15,21 +15,22 @@ root = convertToFileURL(root) describe('main-esm', () => { fancy - .stdout() - .do(() => run(['plugins'], root)) - .do((output: any) => expect(output.stdout).to.equal('No plugins installed.\n')) - .it('runs plugins') + .stdout() + .do(() => run(['plugins'], root)) + .do((output: any) => expect(output.stdout).to.equal('No plugins installed.\n')) + .it('runs plugins') fancy - .stdout() - .do(() => run(['--version'], root)) - .do((output: any) => expect(output.stdout).to.equal(version + '\n')) - .it('runs --version') + .stdout() + .do(() => run(['--version'], root)) + .do((output: any) => expect(output.stdout).to.equal(version + '\n')) + .it('runs --version') fancy - .stdout() - .do(() => run(['--help'], root)) - .do((output: any) => expect(output.stdout).to.equal(`base library for oclif CLIs + .stdout() + .do(() => run(['--help'], root)) + .do((output: any) => + expect(output.stdout).to.equal(`base library for oclif CLIs VERSION ${version} @@ -44,13 +45,15 @@ COMMANDS help Display help for oclif. plugins List installed plugins. -`)) - .it('runs --help') +`), + ) + .it('runs --help') fancy - .stdout() - .do(() => run(['--help', 'foo'], convertToFileURL(resolve(__dirname, 'fixtures/esm/package.json')))) - .do((output: any) => expect(output.stdout).to.equal(`foo topic description + .stdout() + .do(() => run(['--help', 'foo'], convertToFileURL(resolve(__dirname, 'fixtures/esm/package.json')))) + .do((output: any) => + expect(output.stdout).to.equal(`foo topic description USAGE $ oclif-esm foo COMMAND @@ -61,13 +64,15 @@ TOPICS COMMANDS foo baz foo baz description -`)) - .it('runs spaced topic help') +`), + ) + .it('runs spaced topic help') fancy - .stdout() - .do(() => run(['foo', 'bar', '--help'], convertToFileURL(resolve(__dirname, 'fixtures/esm/package.json')))) - .do((output: any) => expect(output.stdout).to.equal(`foo bar topic description + .stdout() + .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 $ oclif-esm foo bar COMMAND @@ -76,18 +81,19 @@ COMMANDS foo bar fail fail description foo bar succeed succeed description -`)) - .it('runs spaced topic help v2') +`), + ) + .it('runs spaced topic help v2') fancy - .stdout() - .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') + .stdout() + .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(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') + .stdout() + .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 1b9b45583..0aa5a1f40 100644 --- a/test/command/main.test.ts +++ b/test/command/main.test.ts @@ -1,4 +1,3 @@ - import {expect} from 'chai' import {resolve} from 'node:path' import {SinonSandbox, SinonStub, createSandbox} from 'sinon' @@ -35,7 +34,7 @@ describe('main', () => { it('should run help', async () => { await run(['--help'], resolve(__dirname, '../../package.json')) - expect(stdoutStub.args.map(a => stripAnsi(a[0])).join('')).to.equal(`base library for oclif CLIs + expect(stdoutStub.args.map((a) => stripAnsi(a[0])).join('')).to.equal(`base library for oclif CLIs VERSION ${version} @@ -55,7 +54,7 @@ COMMANDS it('should show help for topics with spaces', async () => { await run(['--help', 'foo'], resolve(__dirname, 'fixtures/typescript/package.json')) - expect(stdoutStub.args.map(a => stripAnsi(a[0])).join('')).to.equal(`foo topic description + expect(stdoutStub.args.map((a) => stripAnsi(a[0])).join('')).to.equal(`foo topic description USAGE $ oclif foo COMMAND @@ -71,7 +70,7 @@ COMMANDS it('should run spaced topic help v2', async () => { await run(['foo', 'bar', '--help'], resolve(__dirname, 'fixtures/typescript/package.json')) - expect(stdoutStub.args.map(a => stripAnsi(a[0])).join('')).to.equal(`foo bar topic description + expect(stdoutStub.args.map((a) => stripAnsi(a[0])).join('')).to.equal(`foo bar topic description USAGE $ oclif foo bar COMMAND diff --git a/test/config/config.flexible.test.ts b/test/config/config.flexible.test.ts index 6b451da95..be6944696 100644 --- a/test/config/config.flexible.test.ts +++ b/test/config/config.flexible.test.ts @@ -9,12 +9,12 @@ import * as util from '../../src/util' import {join} from 'node:path' interface Options { - pjson?: any; - homedir?: string; - platform?: string; - env?: {[k: string]: string}; - commandIds?: string[]; - types?: string[]; + pjson?: any + homedir?: string + platform?: string + env?: {[k: string]: string} + commandIds?: string[] + types?: string[] } class MyCommandClass extends Command { @@ -42,10 +42,10 @@ describe('Config with flexible taxonomy', () => { types = [], }: Options = {}) => { let test = fancy - .resetConfig() - .env(env, {clear: true}) - .stub(util, 'getHomeDir', stub => stub.returns(join(homedir))) - .stub(util, 'getPlatform', stub => stub.returns(platform)) + .resetConfig() + .env(env, {clear: true}) + .stub(util, 'getHomeDir', (stub) => stub.returns(join(homedir))) + .stub(util, 'getPlatform', (stub) => stub.returns(platform)) const load = async (): Promise => {} const findCommand = async (): Promise => MyCommandClass @@ -144,117 +144,115 @@ describe('Config with flexible taxonomy', () => { // @ts-ignore return { it(expectation: string, fn: (config: Interfaces.Config) => any) { - test - .do(({config}) => fn(config)) - .it(expectation) + test.do(({config}) => fn(config)).it(expectation) return this }, } } testConfig() - .it('has populated topic index', config => { - // @ts-expect-error because private member - const topics = config._topics - expect(topics.has('foo')).to.be.true - expect(topics.has('foo:bar')).to.be.true - expect(topics.has('foo:baz')).to.be.true - }) - .it('has populated command permutation index', config => { - // @ts-expect-error because private member - 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'])) - expect(commandPermutations.get('bar:foo')).to.deep.equal(new Set(['foo:bar'])) - expect(commandPermutations.get('foo:baz')).to.deep.equal(new Set(['foo:baz'])) - expect(commandPermutations.get('baz')).to.deep.equal(new Set(['foo:baz'])) - expect(commandPermutations.get('baz:foo')).to.deep.equal(new Set(['foo:baz'])) - }) - .it('has populated command index', config => { - // @ts-expect-error because private member - const commands = config._commands - expect(commands.has('foo:bar')).to.be.true - expect(commands.has('foo:baz')).to.be.true - }) - .it('has all command id permutations', config => { - expect(config.getAllCommandIDs()).to.deep.equal([ - 'foo:bar', - 'foo:baz', - 'bar:foo', - 'baz:foo', - ]) - }) - - describe('findMatches', () => { - testConfig() - .it('finds command that contains a partial id', config => { - const matches = config.findMatches('foo', []) - expect(matches.length).to.equal(2) + .it('has populated topic index', (config) => { + // @ts-expect-error because private member + const topics = config._topics + expect(topics.has('foo')).to.be.true + expect(topics.has('foo:bar')).to.be.true + expect(topics.has('foo:baz')).to.be.true + }) + .it('has populated command permutation index', (config) => { + // @ts-expect-error because private member + 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'])) + expect(commandPermutations.get('bar:foo')).to.deep.equal(new Set(['foo:bar'])) + expect(commandPermutations.get('foo:baz')).to.deep.equal(new Set(['foo:baz'])) + expect(commandPermutations.get('baz')).to.deep.equal(new Set(['foo:baz'])) + expect(commandPermutations.get('baz:foo')).to.deep.equal(new Set(['foo:baz'])) }) - .it('finds command that contains a partial id and matching full flag', config => { - const matches = config.findMatches('foo', ['--flagB']) - expect(matches.length).to.equal(1) - expect(matches[0].id).to.equal('foo:baz') + .it('has populated command index', (config) => { + // @ts-expect-error because private member + const commands = config._commands + expect(commands.has('foo:bar')).to.be.true + expect(commands.has('foo:baz')).to.be.true }) - .it('finds command that contains a partial id and matching short flag', config => { - const matches = config.findMatches('foo', ['-a']) - expect(matches.length).to.equal(1) - expect(matches[0].id).to.equal('foo:bar') + .it('has all command id permutations', (config) => { + expect(config.getAllCommandIDs()).to.deep.equal(['foo:bar', 'foo:baz', 'bar:foo', 'baz:foo']) }) + + describe('findMatches', () => { + testConfig() + .it('finds command that contains a partial id', (config) => { + const matches = config.findMatches('foo', []) + expect(matches.length).to.equal(2) + }) + .it('finds command that contains a partial id and matching full flag', (config) => { + const matches = config.findMatches('foo', ['--flagB']) + expect(matches.length).to.equal(1) + expect(matches[0].id).to.equal('foo:baz') + }) + .it('finds command that contains a partial id and matching short flag', (config) => { + const matches = config.findMatches('foo', ['-a']) + expect(matches.length).to.equal(1) + expect(matches[0].id).to.equal('foo:bar') + }) }) describe('findCommand', () => { - testConfig() - .it('find command with no duplicates', config => { + testConfig().it('find command with no duplicates', (config) => { const command = config.findCommand('foo:bar', {must: true}) expect(command).to.have.property('pluginAlias', '@My/plugina') }) - testConfig({commandIds: ['foo:bar', 'foo:bar']}) - .it('find command with duplicates and choose the one that appears first in oclif.plugins', config => { - const command = config.findCommand('foo:bar', {must: true}) - expect(command).to.have.property('pluginAlias', '@My/pluginb') - }) + testConfig({commandIds: ['foo:bar', 'foo:bar']}).it( + 'find command with duplicates and choose the one that appears first in oclif.plugins', + (config) => { + const command = config.findCommand('foo:bar', {must: true}) + expect(command).to.have.property('pluginAlias', '@My/pluginb') + }, + ) - testConfig({types: ['core', 'user']}) - .it('find command with no duplicates core/user', config => { + testConfig({types: ['core', 'user']}).it('find command with no duplicates core/user', (config) => { const command = config.findCommand('foo:bar', {must: true}) expect(command).to.have.property('id', 'foo:bar') expect(command).to.have.property('pluginType', 'core') expect(command).to.have.property('pluginAlias', '@My/plugina') }) - testConfig({types: ['user', 'core']}) - .it('find command with no duplicates user/core', config => { + testConfig({types: ['user', 'core']}).it('find command with no duplicates user/core', (config) => { const command = config.findCommand('foo:bar', {must: true}) expect(command).to.have.property('id', 'foo:bar') expect(command).to.have.property('pluginType', 'user') expect(command).to.have.property('pluginAlias', '@My/plugina') }) - testConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['core', 'user']}) - .it('find command with duplicates core/user', config => { - const command = config.findCommand('foo:bar', {must: true}) - expect(command).to.have.property('id', 'foo:bar') - expect(command).to.have.property('pluginType', 'core') - expect(command).to.have.property('pluginAlias', '@My/plugina') - }) + testConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['core', 'user']}).it( + 'find command with duplicates core/user', + (config) => { + const command = config.findCommand('foo:bar', {must: true}) + expect(command).to.have.property('id', 'foo:bar') + expect(command).to.have.property('pluginType', 'core') + expect(command).to.have.property('pluginAlias', '@My/plugina') + }, + ) - testConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['user', 'core']}) - .it('find command with duplicates user/core', config => { - const command = config.findCommand('foo:bar', {must: true}) - expect(command).to.have.property('id', 'foo:bar') - expect(command).to.have.property('pluginType', 'core') - expect(command).to.have.property('pluginAlias', '@My/pluginb') - }) + testConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['user', 'core']}).it( + 'find command with duplicates user/core', + (config) => { + const command = config.findCommand('foo:bar', {must: true}) + expect(command).to.have.property('id', 'foo:bar') + expect(command).to.have.property('pluginType', 'core') + expect(command).to.have.property('pluginAlias', '@My/pluginb') + }, + ) - testConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['user', 'user']}) - .it('find command with duplicates user/user', config => { - const command = config.findCommand('foo:bar', {must: true}) - expect(command).to.have.property('id', 'foo:bar') - expect(command).to.have.property('pluginType', 'user') - expect(command).to.have.property('pluginAlias', '@My/plugina') - }) + testConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['user', 'user']}).it( + 'find command with duplicates user/user', + (config) => { + const command = config.findCommand('foo:bar', {must: true}) + expect(command).to.have.property('id', 'foo:bar') + expect(command).to.have.property('pluginType', 'user') + expect(command).to.have.property('pluginAlias', '@My/plugina') + }, + ) }) }) diff --git a/test/config/config.test.ts b/test/config/config.test.ts index 1b821d793..d92c9f2a7 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -8,12 +8,12 @@ import {Config, Interfaces} from '../../src' import {Command} from '../../src/command' interface Options { - pjson?: any; - homedir?: string; - platform?: string; - env?: {[k: string]: string}; - commandIds?: string[]; - types?: string[]; + pjson?: any + homedir?: string + platform?: string + env?: {[k: string]: string} + commandIds?: string[] + types?: string[] } const pjson = { @@ -47,18 +47,17 @@ const pjson = { describe('Config', () => { const testConfig = ({pjson, homedir = '/my/home', platform = 'darwin', env = {}}: Options = {}) => { let test = fancy - .resetConfig() - .env(env, {clear: true}) - .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)) + .resetConfig() + .env(env, {clear: true}) + .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()) return { hasS3Key(k: keyof Interfaces.PJSON.S3.Templates, expected: string, extra: any = {}) { - return this - .it(`renders ${k} template as ${expected}`, config => { + return this.it(`renders ${k} template as ${expected}`, (config) => { // Config.load reads the package.json to determine the version and channel // In order to allow prerelease branches to pass, we need to strip the prerelease // tag from the version and switch the channel to stable. @@ -79,13 +78,10 @@ describe('Config', () => { }) }, hasProperty(k: K | undefined, v: Interfaces.Config[K] | undefined) { - return this - .it(`has ${k}=${v}`, config => expect(config).to.have.property(k!, v)) + return this.it(`has ${k}=${v}`, (config) => expect(config).to.have.property(k!, v)) }, it(expectation: string, fn: (config: Interfaces.Config) => any) { - test - .do(({config}) => fn(config)) - .it(expectation) + test.do(({config}) => fn(config)).it(expectation) return this }, } @@ -93,62 +89,61 @@ describe('Config', () => { describe('darwin', () => { testConfig() - .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')) + .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', () => { - testConfig({pjson}) - .it('will have binAliases set', config => { + testConfig({pjson}).it('will have binAliases set', (config) => { expect(config.binAliases).to.deep.equal(['bar', 'baz']) }) - testConfig({pjson}).it('will get scoped env vars with bin aliases', config => { + testConfig({pjson}).it('will get scoped env vars with bin aliases', (config) => { expect(config.scopedEnvVarKeys('abc')).to.deep.equal(['FOO_ABC', 'BAR_ABC', 'BAZ_ABC']) }) - testConfig({pjson}).it('will get scoped env vars', config => { + testConfig({pjson}).it('will get scoped env vars', (config) => { expect(config.scopedEnvVarKey('abc')).to.equal('FOO_ABC') }) - testConfig({pjson}).it('will get scopedEnvVar', config => { + testConfig({pjson}).it('will get scopedEnvVar', (config) => { process.env.FOO_ABC = 'find me' expect(config.scopedEnvVar('abc')).to.deep.equal('find me') delete process.env.FOO_ABC }) - testConfig({pjson}).it('will get scopedEnvVar via alias', config => { + testConfig({pjson}).it('will get scopedEnvVar via alias', (config) => { process.env.BAZ_ABC = 'find me' expect(config.scopedEnvVar('abc')).to.deep.equal('find me') delete process.env.BAZ_ABC }) - testConfig({pjson}).it('will get scoped env vars', config => { + testConfig({pjson}).it('will get scoped env vars', (config) => { expect(config.scopedEnvVarKey('abc')).to.equal('FOO_ABC') }) - testConfig({pjson}).it('will get scopedEnvVarTrue', config => { + testConfig({pjson}).it('will get scopedEnvVarTrue', (config) => { process.env.FOO_ABC = 'true' expect(config.scopedEnvVarTrue('abc')).to.equal(true) delete process.env.FOO_ABC }) - testConfig({pjson}).it('will get scopedEnvVarTrue via alias', config => { + testConfig({pjson}).it('will get scopedEnvVarTrue via alias', (config) => { process.env.BAR_ABC = 'true' expect(config.scopedEnvVarTrue('abc')).to.equal(true) delete process.env.BAR_ABC }) - testConfig({pjson}).it('will get scopedEnvVarTrue=1', config => { + testConfig({pjson}).it('will get scopedEnvVarTrue=1', (config) => { process.env.FOO_ABC = '1' expect(config.scopedEnvVarTrue('abc')).to.equal(true) delete process.env.FOO_ABC }) - testConfig({pjson}).it('will get scopedEnvVarTrue=1 via alias', config => { + testConfig({pjson}).it('will get scopedEnvVarTrue=1 via alias', (config) => { process.env.BAR_ABC = '1' expect(config.scopedEnvVarTrue('abc')).to.equal(true) delete process.env.BAR_ABC @@ -157,11 +152,11 @@ describe('Config', () => { describe('linux', () => { testConfig({platform: 'linux'}) - .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')) + .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', () => { @@ -169,34 +164,37 @@ describe('Config', () => { platform: 'win32', env: {LOCALAPPDATA: '/my/home/localappdata'}, }) - .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')) + .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', () => { const target = {platform: 'darwin', arch: 'x64'} const beta = {version: '2.0.0-beta', channel: 'beta'} testConfig() - .hasS3Key('baseDir', 'oclif-cli') - .hasS3Key('manifest', 'version') - .hasS3Key('manifest', 'channels/beta/version', beta) - .hasS3Key('manifest', 'darwin-x64', target) - .hasS3Key('manifest', 'channels/beta/darwin-x64', {...beta, ...target}) - .hasS3Key('unversioned', 'oclif-cli.tar.gz') - .hasS3Key('unversioned', 'oclif-cli.tar.gz') - .hasS3Key('unversioned', 'channels/beta/oclif-cli.tar.gz', beta) - .hasS3Key('unversioned', 'channels/beta/oclif-cli.tar.gz', beta) - .hasS3Key('unversioned', 'oclif-cli-darwin-x64.tar.gz', target) - .hasS3Key('unversioned', 'oclif-cli-darwin-x64.tar.gz', target) - .hasS3Key('unversioned', 'channels/beta/oclif-cli-darwin-x64.tar.gz', {...beta, ...target}) - .hasS3Key('unversioned', 'channels/beta/oclif-cli-darwin-x64.tar.gz', {...beta, ...target}) - .hasS3Key('versioned', 'oclif-cli-v1.0.0/oclif-cli-v1.0.0.tar.gz') - .hasS3Key('versioned', 'oclif-cli-v1.0.0/oclif-cli-v1.0.0-darwin-x64.tar.gz', target) - .hasS3Key('versioned', 'channels/beta/oclif-cli-v2.0.0-beta/oclif-cli-v2.0.0-beta.tar.gz', beta) - .hasS3Key('versioned', 'channels/beta/oclif-cli-v2.0.0-beta/oclif-cli-v2.0.0-beta-darwin-x64.tar.gz', {...beta, ...target}) + .hasS3Key('baseDir', 'oclif-cli') + .hasS3Key('manifest', 'version') + .hasS3Key('manifest', 'channels/beta/version', beta) + .hasS3Key('manifest', 'darwin-x64', target) + .hasS3Key('manifest', 'channels/beta/darwin-x64', {...beta, ...target}) + .hasS3Key('unversioned', 'oclif-cli.tar.gz') + .hasS3Key('unversioned', 'oclif-cli.tar.gz') + .hasS3Key('unversioned', 'channels/beta/oclif-cli.tar.gz', beta) + .hasS3Key('unversioned', 'channels/beta/oclif-cli.tar.gz', beta) + .hasS3Key('unversioned', 'oclif-cli-darwin-x64.tar.gz', target) + .hasS3Key('unversioned', 'oclif-cli-darwin-x64.tar.gz', target) + .hasS3Key('unversioned', 'channels/beta/oclif-cli-darwin-x64.tar.gz', {...beta, ...target}) + .hasS3Key('unversioned', 'channels/beta/oclif-cli-darwin-x64.tar.gz', {...beta, ...target}) + .hasS3Key('versioned', 'oclif-cli-v1.0.0/oclif-cli-v1.0.0.tar.gz') + .hasS3Key('versioned', 'oclif-cli-v1.0.0/oclif-cli-v1.0.0-darwin-x64.tar.gz', target) + .hasS3Key('versioned', 'channels/beta/oclif-cli-v2.0.0-beta/oclif-cli-v2.0.0-beta.tar.gz', beta) + .hasS3Key('versioned', 'channels/beta/oclif-cli-v2.0.0-beta/oclif-cli-v2.0.0-beta-darwin-x64.tar.gz', { + ...beta, + ...target, + }) }) describe('options', () => { @@ -208,8 +206,7 @@ describe('Config', () => { }) }) - testConfig() - .it('has s3Url', config => { + testConfig().it('has s3Url', (config) => { const orig = config.pjson.oclif.update.s3.host config.pjson.oclif.update.s3.host = 'https://bar.com/a/' expect(config.s3Url('/b/c')).to.equal('https://bar.com/a/b/c') @@ -218,13 +215,13 @@ describe('Config', () => { testConfig({ pjson, - }) - .it('has subtopics', config => { - expect(config.topics.map(t => t.name)).to.have.members(['t1', 't1:t1-1', 't1:t1-1:t1-1-1', 't1:t1-1:t1-1-2']) + }).it('has subtopics', (config) => { + expect(config.topics.map((t) => t.name)).to.have.members(['t1', 't1:t1-1', 't1:t1-1:t1-1-1', 't1:t1-1:t1-1-2']) }) describe('findCommand', () => { - const findCommandTestConfig = ({pjson, + const findCommandTestConfig = ({ + pjson, homedir = '/my/home', platform = 'darwin', env = {}, @@ -250,7 +247,12 @@ describe('Config', () => { const commandPluginA: Command.Loadable = { strict: false, - aliases: [], args: {}, flags: {}, hidden: false, id: commandIds[0], async load(): Promise { + aliases: [], + args: {}, + flags: {}, + hidden: false, + id: commandIds[0], + async load(): Promise { return MyCommandClass }, pluginType: types[0] ?? 'core', @@ -258,14 +260,20 @@ describe('Config', () => { } const commandPluginB: Command.Loadable = { strict: false, - aliases: [], args: {}, flags: {}, hidden: false, id: commandIds[1], async load(): Promise { + aliases: [], + args: {}, + flags: {}, + hidden: false, + id: commandIds[1], + async load(): Promise { return MyCommandClass }, pluginType: types[1] ?? 'core', pluginAlias: '@My/pluginb', } const hooks = {} - const pluginA: IPlugin = {load, + const pluginA: IPlugin = { + load, findCommand, name: '@My/plugina', alias: '@My/plugina', @@ -307,12 +315,12 @@ describe('Config', () => { } const plugins = new Map().set(pluginA.name, pluginA).set(pluginB.name, pluginB) let test = fancy - .resetConfig() - .env(env, {clear: true}) - .stub(util, 'getHomeDir', stub => stub.returns(join(homedir))) - .stub(util, 'getPlatform', stub => stub.returns(platform)) + .resetConfig() + .env(env, {clear: true}) + .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)) + if (pjson) test = test.stub(util, 'readJson', (stub) => stub.resolves(pjson)) test = test.add('config', async () => { const config = await Config.load() config.plugins = plugins @@ -330,58 +338,61 @@ describe('Config', () => { // @ts-ignore return { it(expectation: string, fn: (config: Interfaces.Config) => any) { - test - .do(({config}) => fn(config)) - .it(expectation) + test.do(({config}) => fn(config)).it(expectation) return this }, } } - findCommandTestConfig() - .it('find command with no duplicates', config => { + findCommandTestConfig().it('find command with no duplicates', (config) => { const command = config.findCommand('foo:bar', {must: true}) expect(command).to.have.property('pluginAlias', '@My/plugina') }) - findCommandTestConfig({commandIds: ['foo:bar', 'foo:bar']}) - .it('find command with duplicates and choose the one that appears first in oclif.plugins', config => { - const command = config.findCommand('foo:bar', {must: true}) - expect(command).to.have.property('pluginAlias', '@My/pluginb') - }) - findCommandTestConfig({types: ['core', 'user']}) - .it('find command with no duplicates core/user', config => { - const command = config.findCommand('foo:bar', {must: true}) - expect(command).to.have.property('id', 'foo:bar') - expect(command).to.have.property('pluginType', 'core') - expect(command).to.have.property('pluginAlias', '@My/plugina') - }) - findCommandTestConfig({types: ['user', 'core']}) - .it('find command with no duplicates user/core', config => { - const command = config.findCommand('foo:bar', {must: true}) - expect(command).to.have.property('id', 'foo:bar') - expect(command).to.have.property('pluginType', 'user') - expect(command).to.have.property('pluginAlias', '@My/plugina') - }) - findCommandTestConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['core', 'user']}) - .it('find command with duplicates core/user', config => { + findCommandTestConfig({commandIds: ['foo:bar', 'foo:bar']}).it( + 'find command with duplicates and choose the one that appears first in oclif.plugins', + (config) => { + const command = config.findCommand('foo:bar', {must: true}) + expect(command).to.have.property('pluginAlias', '@My/pluginb') + }, + ) + findCommandTestConfig({types: ['core', 'user']}).it('find command with no duplicates core/user', (config) => { const command = config.findCommand('foo:bar', {must: true}) expect(command).to.have.property('id', 'foo:bar') expect(command).to.have.property('pluginType', 'core') expect(command).to.have.property('pluginAlias', '@My/plugina') }) - findCommandTestConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['user', 'core']}) - .it('find command with duplicates user/core', config => { - const command = config.findCommand('foo:bar', {must: true}) - expect(command).to.have.property('id', 'foo:bar') - expect(command).to.have.property('pluginType', 'core') - expect(command).to.have.property('pluginAlias', '@My/pluginb') - }) - findCommandTestConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['user', 'user']}) - .it('find command with duplicates user/user', config => { + findCommandTestConfig({types: ['user', 'core']}).it('find command with no duplicates user/core', (config) => { const command = config.findCommand('foo:bar', {must: true}) expect(command).to.have.property('id', 'foo:bar') expect(command).to.have.property('pluginType', 'user') expect(command).to.have.property('pluginAlias', '@My/plugina') }) + findCommandTestConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['core', 'user']}).it( + 'find command with duplicates core/user', + (config) => { + const command = config.findCommand('foo:bar', {must: true}) + expect(command).to.have.property('id', 'foo:bar') + expect(command).to.have.property('pluginType', 'core') + expect(command).to.have.property('pluginAlias', '@My/plugina') + }, + ) + findCommandTestConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['user', 'core']}).it( + 'find command with duplicates user/core', + (config) => { + const command = config.findCommand('foo:bar', {must: true}) + expect(command).to.have.property('id', 'foo:bar') + expect(command).to.have.property('pluginType', 'core') + expect(command).to.have.property('pluginAlias', '@My/pluginb') + }, + ) + findCommandTestConfig({commandIds: ['foo:bar', 'foo:bar'], types: ['user', 'user']}).it( + 'find command with duplicates user/user', + (config) => { + const command = config.findCommand('foo:bar', {must: true}) + expect(command).to.have.property('id', 'foo:bar') + expect(command).to.have.property('pluginType', 'user') + expect(command).to.have.property('pluginAlias', '@My/plugina') + }, + ) }) }) diff --git a/test/config/esm.test.ts b/test/config/esm.test.ts index 2d3b1a0d2..54dc4978d 100644 --- a/test/config/esm.test.ts +++ b/test/config/esm.test.ts @@ -11,27 +11,21 @@ const p = (p: string) => join(root, p) // This tests file URL / import.meta.url simulation. const rootAsFileURL = url.pathToFileURL(root).toString() -const withConfig = fancy -.add('config', () => Config.load(rootAsFileURL)) +const withConfig = fancy.add('config', () => Config.load(rootAsFileURL)) describe('esm', () => { - withConfig - .it('has commandsDir', ({config}) => { + withConfig.it('has commandsDir', ({config}) => { expect([...config.plugins.values()][0]).to.deep.include({ commandsDir: p('src/commands'), }) }) - withConfig - .stdout() - .it('runs esm command and prerun & postrun hooks', async ctx => { + withConfig.stdout().it('runs esm command and prerun & postrun hooks', async (ctx) => { await ctx.config.runCommand('foo:bar:baz') expect(ctx.stdout).to.equal('running esm prerun hook\nit works!\nrunning esm postrun hook\n') }) - withConfig - .stdout() - .it('runs faulty command, only prerun hook triggers', async ctx => { + withConfig.stdout().it('runs faulty command, only prerun hook triggers', async (ctx) => { try { await ctx.config.runCommand('foo:bar:fail') } catch { @@ -41,16 +35,12 @@ describe('esm', () => { expect(ctx.stdout).to.equal('running esm prerun hook\nit fails!\ncaught error\n') }) - withConfig - .stdout() - .it('runs esm command, postrun hook captures command result', async ctx => { + withConfig.stdout().it('runs esm command, postrun hook captures command result', async (ctx) => { await ctx.config.runCommand('foo:bar:test-result') expect(ctx.stdout).to.equal('running esm prerun hook\nit works!\nrunning esm postrun hook\nreturned success!\n') }) - withConfig - .stdout() - .it('runs init hook', async ctx => { + withConfig.stdout().it('runs init hook', async (ctx) => { await (ctx.config.runHook as any)('init', {id: 'myid', argv: ['foo']}) expect(ctx.stdout).to.equal('running esm init hook\n') }) diff --git a/test/config/fixtures/help/package.json b/test/config/fixtures/help/package.json index 832fdfa34..a19b46139 100644 --- a/test/config/fixtures/help/package.json +++ b/test/config/fixtures/help/package.json @@ -6,7 +6,14 @@ "files": [], "oclif": { "commands": "./src/commands", - "additionalHelpFlags": ["-h", "--mycommandhelp"], - "additionalVersionFlags": ["-v", "myversion", "version"] + "additionalHelpFlags": [ + "-h", + "--mycommandhelp" + ], + "additionalVersionFlags": [ + "-v", + "myversion", + "version" + ] } } diff --git a/test/config/fixtures/mixed-esm-cjs/src/commands/foo/bar/test-result.js b/test/config/fixtures/mixed-esm-cjs/src/commands/foo/bar/test-result.js index 755ba23aa..5b93fd61e 100644 --- a/test/config/fixtures/mixed-esm-cjs/src/commands/foo/bar/test-result.js +++ b/test/config/fixtures/mixed-esm-cjs/src/commands/foo/bar/test-result.js @@ -4,4 +4,3 @@ export default class Command { return 'returned success!' } } - diff --git a/test/config/fixtures/typescript/src/hooks/postrun.ts b/test/config/fixtures/typescript/src/hooks/postrun.ts index f78977809..98a0d304a 100644 --- a/test/config/fixtures/typescript/src/hooks/postrun.ts +++ b/test/config/fixtures/typescript/src/hooks/postrun.ts @@ -1,4 +1,3 @@ - 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/fixtures/typescript/tsconfig.json b/test/config/fixtures/typescript/tsconfig.json index 75c61de88..8d0083147 100644 --- a/test/config/fixtures/typescript/tsconfig.json +++ b/test/config/fixtures/typescript/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "outDir": "./lib", - "rootDirs": [ - "./src" - ] + "rootDirs": ["./src"] }, - "include": [ - "./src/**/*" - ] + "include": ["./src/**/*"] } diff --git a/test/config/help.config.test.ts b/test/config/help.config.test.ts index 3742c9455..71cf27819 100644 --- a/test/config/help.config.test.ts +++ b/test/config/help.config.test.ts @@ -13,16 +13,14 @@ const root = resolve(__dirname, 'fixtures/help') // This tests file URL / import.meta.url simulation. const rootAsFileURL = pathToFileURL(root).toString() -const withConfig = fancy -.add('config', () => Config.load(rootAsFileURL)) +const withConfig = fancy.add('config', () => Config.load(rootAsFileURL)) describe('help and version flag additions', () => { - withConfig - .it('has help and version additions', ({config}) => { + withConfig.it('has help and version additions', ({config}) => { expect(config.pjson.oclif.additionalHelpFlags).to.have.lengthOf(2) expect(config.pjson.oclif.additionalVersionFlags).to.have.lengthOf(3) const mergedHelpFlags = getHelpFlagAdditions(config) - expect(mergedHelpFlags).to.deep.equal(['--help', ...config.pjson.oclif.additionalHelpFlags as string[]]) + expect(mergedHelpFlags).to.deep.equal(['--help', ...(config.pjson.oclif.additionalHelpFlags as string[])]) expect(helpAddition(['-h'], config)).to.be.true expect(helpAddition(['help'], config)).to.be.false expect(helpAddition(['--mycommandhelp'], config)).to.be.true @@ -34,18 +32,18 @@ describe('help and version flag additions', () => { }) withConfig - .do(({config}) => delete config.pjson.oclif.additionalHelpFlags) - .it('has version additions', ({config}) => { - expect(config.pjson.oclif.additionalHelpFlags).to.not.be.ok - expect(config.pjson.oclif.additionalVersionFlags).to.have.lengthOf(3) - const mergedHelpFlags = getHelpFlagAdditions(config) - expect(mergedHelpFlags).to.deep.equal(['--help']) - expect(helpAddition(['-h'], config)).to.be.false - expect(helpAddition(['help'], config)).to.be.false - expect(helpAddition(['mycommandhelp'], config)).to.be.false - expect(versionAddition(['-v'], config)).to.be.true - expect(versionAddition(['version'], config)).to.be.true - expect(versionAddition(['myversion'], config)).to.be.true - expect(versionAddition(['notmyversion'], config)).to.be.false - }) + .do(({config}) => delete config.pjson.oclif.additionalHelpFlags) + .it('has version additions', ({config}) => { + expect(config.pjson.oclif.additionalHelpFlags).to.not.be.ok + expect(config.pjson.oclif.additionalVersionFlags).to.have.lengthOf(3) + const mergedHelpFlags = getHelpFlagAdditions(config) + expect(mergedHelpFlags).to.deep.equal(['--help']) + expect(helpAddition(['-h'], config)).to.be.false + expect(helpAddition(['help'], config)).to.be.false + expect(helpAddition(['mycommandhelp'], config)).to.be.false + expect(versionAddition(['-v'], config)).to.be.true + expect(versionAddition(['version'], config)).to.be.true + expect(versionAddition(['myversion'], config)).to.be.true + expect(versionAddition(['notmyversion'], config)).to.be.false + }) }) diff --git a/test/config/mixed-cjs-esm.test.ts b/test/config/mixed-cjs-esm.test.ts index 411275fde..b8f65f8fb 100644 --- a/test/config/mixed-cjs-esm.test.ts +++ b/test/config/mixed-cjs-esm.test.ts @@ -7,27 +7,21 @@ import {expect, fancy} from './test' const root = resolve(__dirname, 'fixtures/mixed-cjs-esm') const p = (p: string) => join(root, p) -const withConfig = fancy -.add('config', () => Config.load(root)) +const withConfig = fancy.add('config', () => Config.load(root)) describe('mixed-cjs-esm', () => { - withConfig - .it('has commandsDir', ({config}) => { + withConfig.it('has commandsDir', ({config}) => { expect([...config.plugins.values()][0]).to.deep.include({ commandsDir: p('src/commands'), }) }) - withConfig - .stdout() - .it('runs mixed-cjs-esm command and prerun & postrun hooks', async ctx => { + withConfig.stdout().it('runs mixed-cjs-esm command and prerun & postrun hooks', async (ctx) => { await ctx.config.runCommand('foo:bar:baz') expect(ctx.stdout).to.equal('running mixed-cjs-esm prerun hook\nit works!\nrunning mixed-cjs-esm postrun hook\n') }) - withConfig - .stdout() - .it('runs faulty command, only prerun hook triggers', async ctx => { + withConfig.stdout().it('runs faulty command, only prerun hook triggers', async (ctx) => { try { await ctx.config.runCommand('foo:bar:fail') } catch { @@ -37,16 +31,14 @@ describe('mixed-cjs-esm', () => { expect(ctx.stdout).to.equal('running mixed-cjs-esm prerun hook\nit fails!\ncaught error\n') }) - withConfig - .stdout() - .it('runs mixed-cjs-esm command, postrun hook captures command result', async ctx => { + withConfig.stdout().it('runs mixed-cjs-esm command, postrun hook captures command result', async (ctx) => { await ctx.config.runCommand('foo:bar:test-result') - expect(ctx.stdout).to.equal('running mixed-cjs-esm prerun hook\nit works!\nrunning mixed-cjs-esm postrun hook\nreturned success!\n') + expect(ctx.stdout).to.equal( + 'running mixed-cjs-esm prerun hook\nit works!\nrunning mixed-cjs-esm postrun hook\nreturned success!\n', + ) }) - withConfig - .stdout() - .it('runs init hook', async ctx => { + withConfig.stdout().it('runs init hook', async (ctx) => { await (ctx.config.runHook as any)('init', {id: 'myid', argv: ['foo']}) expect(ctx.stdout).to.equal('running mixed-cjs-esm init hook\n') }) diff --git a/test/config/mixed-esm-cjs.test.ts b/test/config/mixed-esm-cjs.test.ts index 4d6ed3703..cceb363bf 100644 --- a/test/config/mixed-esm-cjs.test.ts +++ b/test/config/mixed-esm-cjs.test.ts @@ -7,27 +7,21 @@ import {expect, fancy} from './test' const root = resolve(__dirname, 'fixtures/mixed-esm-cjs') const p = (p: string) => join(root, p) -const withConfig = fancy -.add('config', () => Config.load(root)) +const withConfig = fancy.add('config', () => Config.load(root)) describe('mixed-cjs-esm', () => { - withConfig - .it('has commandsDir', ({config}) => { + withConfig.it('has commandsDir', ({config}) => { expect([...config.plugins.values()][0]).to.deep.include({ commandsDir: p('src/commands'), }) }) - withConfig - .stdout() - .it('runs mixed-esm-cjs command and prerun & postrun hooks', async ctx => { + withConfig.stdout().it('runs mixed-esm-cjs command and prerun & postrun hooks', async (ctx) => { await ctx.config.runCommand('foo:bar:baz') expect(ctx.stdout).to.equal('running mixed-esm-cjs prerun hook\nit works!\nrunning mixed-esm-cjs postrun hook\n') }) - withConfig - .stdout() - .it('runs faulty command, only prerun hook triggers', async ctx => { + withConfig.stdout().it('runs faulty command, only prerun hook triggers', async (ctx) => { try { await ctx.config.runCommand('foo:bar:fail') } catch { @@ -37,16 +31,14 @@ describe('mixed-cjs-esm', () => { expect(ctx.stdout).to.equal('running mixed-esm-cjs prerun hook\nit fails!\ncaught error\n') }) - withConfig - .stdout() - .it('runs mixed-esm-cjs command, postrun hook captures command result', async ctx => { + withConfig.stdout().it('runs mixed-esm-cjs command, postrun hook captures command result', async (ctx) => { await ctx.config.runCommand('foo:bar:test-result') - expect(ctx.stdout).to.equal('running mixed-esm-cjs prerun hook\nit works!\nrunning mixed-esm-cjs postrun hook\nreturned success!\n') + expect(ctx.stdout).to.equal( + 'running mixed-esm-cjs prerun hook\nit works!\nrunning mixed-esm-cjs postrun hook\nreturned success!\n', + ) }) - withConfig - .stdout() - .it('runs init hook', async ctx => { + withConfig.stdout().it('runs init hook', async (ctx) => { await (ctx.config.runHook as any)('init', {id: 'myid', argv: ['foo']}) expect(ctx.stdout).to.equal('running mixed-esm-cjs init hook\n') }) diff --git a/test/config/test.ts b/test/config/test.ts index 1e7866017..fe843c7dd 100644 --- a/test/config/test.ts +++ b/test/config/test.ts @@ -2,8 +2,7 @@ import {fancy as base} from 'fancy-test' import {Interfaces} from '../../src' -export const fancy = base -.register('resetConfig', () => ({ +export const fancy = base.register('resetConfig', () => ({ run(ctx: {config: Interfaces.Config}) { // @ts-ignore delete ctx.config diff --git a/test/config/typescript.test.ts b/test/config/typescript.test.ts index 27d20c5cb..bfd8c85bd 100644 --- a/test/config/typescript.test.ts +++ b/test/config/typescript.test.ts @@ -7,27 +7,21 @@ import {expect, fancy} from './test' const root = resolve(__dirname, 'fixtures/typescript') const p = (p: string) => join(root, p) -const withConfig = fancy -.add('config', () => Config.load(root)) +const withConfig = fancy.add('config', () => Config.load(root)) describe('typescript', () => { - withConfig - .it('has commandsDir', ({config}) => { + withConfig.it('has commandsDir', ({config}) => { expect([...config.plugins.values()][0]).to.deep.include({ commandsDir: p('src/commands'), }) }) - withConfig - .stdout() - .it('runs ts command and prerun & postrun hooks', async ctx => { + withConfig.stdout().it('runs ts command and prerun & postrun hooks', async (ctx) => { await ctx.config.runCommand('foo:bar:baz') expect(ctx.stdout).to.equal('running ts prerun hook\nit works!\nrunning ts postrun hook\n') }) - withConfig - .stdout() - .it('runs faulty command, only prerun hook triggers', async ctx => { + withConfig.stdout().it('runs faulty command, only prerun hook triggers', async (ctx) => { try { await ctx.config.runCommand('foo:bar:fail') } catch { @@ -37,16 +31,12 @@ describe('typescript', () => { expect(ctx.stdout).to.equal('running ts prerun hook\nit fails!\ncaught error\n') }) - withConfig - .stdout() - .it('runs ts command, postrun hook captures command result', async ctx => { + withConfig.stdout().it('runs ts command, postrun hook captures command result', async (ctx) => { await ctx.config.runCommand('foo:bar:test-result') expect(ctx.stdout).to.equal('running ts prerun hook\nit works!\nrunning ts postrun hook\nreturned success!\n') }) - withConfig - .stdout() - .it('runs init hook', async ctx => { + withConfig.stdout().it('runs init hook', async (ctx) => { // to-do: fix union types await (ctx.config.runHook as any)('init', {id: 'myid', argv: ['foo']}) expect(ctx.stdout).to.equal('running ts init hook\n') diff --git a/test/config/util.test.ts b/test/config/util.test.ts index 6fad20e6a..3f05fcffe 100644 --- a/test/config/util.test.ts +++ b/test/config/util.test.ts @@ -3,23 +3,14 @@ import {collectUsableIds, getCommandIdPermutations} from '../../src/config/util' describe('util', () => { describe('collectUsableIds', () => { - test - .it('returns all usable command ids', async () => { + test.it('returns all usable command ids', async () => { const ids = collectUsableIds(['foo:bar:baz', 'one:two:three']) - expect(ids).to.deep.equal(new Set([ - 'foo', - 'foo:bar', - 'foo:bar:baz', - 'one', - 'one:two', - 'one:two:three', - ])) + expect(ids).to.deep.equal(new Set(['foo', 'foo:bar', 'foo:bar:baz', 'one', 'one:two', 'one:two:three'])) }) }) describe('getCommandIdPermutations', () => { - test - .it('returns all usable command ids', async () => { + test.it('returns all usable command ids', async () => { const permutations = getCommandIdPermutations('foo:bar:baz') expect(permutations).to.deep.equal([ 'foo:bar:baz', @@ -35,22 +26,30 @@ describe('util', () => { const numberOfPermutations = (commandID: string): number => { const num = commandID.split(':').length let result = 1 - for (let i = 2; i <= num; i++) - result *= i + for (let i = 2; i <= num; i++) result *= i return result } - test - .it('returns the correct number of permutations', async () => { + test.it('returns the correct number of permutations', async () => { expect(getCommandIdPermutations('one').length).to.equal(numberOfPermutations('one')) expect(getCommandIdPermutations('one:two').length).to.equal(numberOfPermutations('one:two')) expect(getCommandIdPermutations('one:two:three').length).to.equal(numberOfPermutations('one:two:three')) expect(getCommandIdPermutations('one:two:three:four').length).to.equal(numberOfPermutations('one:two:three:four')) - expect(getCommandIdPermutations('one:two:three:four:five').length).to.equal(numberOfPermutations('one:two:three:four:five')) - expect(getCommandIdPermutations('one:two:three:four:five:six').length).to.equal(numberOfPermutations('one:two:three:four:five:six')) - expect(getCommandIdPermutations('one:two:three:four:five:six:seven').length).to.equal(numberOfPermutations('one:two:three:four:five:six:seven')) - expect(getCommandIdPermutations('one:two:three:four:five:six:seven:eight').length).to.equal(numberOfPermutations('one:two:three:four:five:six:seven:eight')) - expect(getCommandIdPermutations('one:two:three:four:five:six:seven:eight:nine').length).to.equal(numberOfPermutations('one:two:three:four:five:six:seven:eight:nine')) + expect(getCommandIdPermutations('one:two:three:four:five').length).to.equal( + numberOfPermutations('one:two:three:four:five'), + ) + expect(getCommandIdPermutations('one:two:three:four:five:six').length).to.equal( + numberOfPermutations('one:two:three:four:five:six'), + ) + expect(getCommandIdPermutations('one:two:three:four:five:six:seven').length).to.equal( + numberOfPermutations('one:two:three:four:five:six:seven'), + ) + expect(getCommandIdPermutations('one:two:three:four:five:six:seven:eight').length).to.equal( + numberOfPermutations('one:two:three:four:five:six:seven:eight'), + ) + expect(getCommandIdPermutations('one:two:three:four:five:six:seven:eight:nine').length).to.equal( + numberOfPermutations('one:two:three:four:five:six:seven:eight:nine'), + ) }) }) }) diff --git a/test/errors/error.test.ts b/test/errors/error.test.ts index 15ab20a52..534b2d98a 100644 --- a/test/errors/error.test.ts +++ b/test/errors/error.test.ts @@ -4,98 +4,102 @@ import {PrettyPrintableError} from '../../src/interfaces/errors' describe('error', () => { fancy - .do(() => { - error('An error happened!') - }) - .catch((error: PrettyPrintableError) => { - expect(error.message).to.equal('An error happened!') - }) - .it('throws an error using a string argument') - - fancy - .do(() => { - error('An error happened!', {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']}) - }) - .catch((error: PrettyPrintableError) => { - expect(error.message).to.equal('An error happened!') - expect(error.code).to.equal('ERR') - expect(error.ref).to.equal('https://oclif.com/error') - expect(error.suggestions).to.deep.equal(['rm -rf node_modules']) - }) - .it('attaches pretty print properties to a new error from options') + .do(() => { + error('An error happened!') + }) + .catch((error: PrettyPrintableError) => { + expect(error.message).to.equal('An error happened!') + }) + .it('throws an error using a string argument') fancy - .do(() => { - error(new Error('An existing error object error!'), {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']}) - }) - .catch((error: PrettyPrintableError) => { - expect(error.message).to.equal('An existing error object error!') - expect(error.code).to.equal('ERR') - expect(error.ref).to.equal('https://oclif.com/error') - expect(error.suggestions).to.deep.equal(['rm -rf node_modules']) - }) - .it('attached pretty print properties from options to an existing error object') + .do(() => { + error('An error happened!', {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']}) + }) + .catch((error: PrettyPrintableError) => { + expect(error.message).to.equal('An error happened!') + expect(error.code).to.equal('ERR') + expect(error.ref).to.equal('https://oclif.com/error') + expect(error.suggestions).to.deep.equal(['rm -rf node_modules']) + }) + .it('attaches pretty print properties to a new error from options') fancy - .do(() => { - const e: any = new Error('An existing error object error!') - e.code = 'ORIG_ERR' - e.ref = 'ORIG_REF' - e.suggestions = ['ORIG_SUGGESTION'] - error(e, {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']}) - }) - .catch((error: PrettyPrintableError) => { - expect(error.code).to.equal('ORIG_ERR') - expect(error.ref).to.equal('ORIG_REF') - expect(error.suggestions).to.deep.equal(['ORIG_SUGGESTION']) - }) - .it('preserves original pretty printable properties and is not overwritten by options') + .do(() => { + error(new Error('An existing error object error!'), { + code: 'ERR', + ref: 'https://oclif.com/error', + suggestions: ['rm -rf node_modules'], + }) + }) + .catch((error: PrettyPrintableError) => { + expect(error.message).to.equal('An existing error object error!') + expect(error.code).to.equal('ERR') + expect(error.ref).to.equal('https://oclif.com/error') + expect(error.suggestions).to.deep.equal(['rm -rf node_modules']) + }) + .it('attached pretty print properties from options to an existing error object') fancy - .stdout() - .stderr() - .do(() => { - error('an error is reported but is not rethrown', {exit: false}) - }) - // there is no .catch here because the error is not rethrown - // however it should be outputted - .it('does not rethrow error when exit: false option is set', ctx => { - expect(ctx.stderr).to.contain('Error: an error is reported but is not rethrown') - expect(ctx.stdout).to.equal('') - }) - - describe('applying oclif errors', () => { - fancy .do(() => { - error(new Error('An existing error object error!')) + const e: any = new Error('An existing error object error!') + e.code = 'ORIG_ERR' + e.ref = 'ORIG_REF' + e.suggestions = ['ORIG_SUGGESTION'] + error(e, {code: 'ERR', ref: 'https://oclif.com/error', suggestions: ['rm -rf node_modules']}) }) - .catch((error: any) => { - const defaultErrorCode = 2 - expect(error.oclif.exit).to.equal(defaultErrorCode) + .catch((error: PrettyPrintableError) => { + expect(error.code).to.equal('ORIG_ERR') + expect(error.ref).to.equal('ORIG_REF') + expect(error.suggestions).to.deep.equal(['ORIG_SUGGESTION']) }) - .it('adds oclif exit code to errors by default') + .it('preserves original pretty printable properties and is not overwritten by options') - fancy + fancy + .stdout() + .stderr() .do(() => { - error(new Error('An existing error object error!'), {exit: 9001}) + error('an error is reported but is not rethrown', {exit: false}) }) - .catch((error: any) => { - expect(error.oclif.exit).to.equal(9001) + // there is no .catch here because the error is not rethrown + // however it should be outputted + .it('does not rethrow error when exit: false option is set', (ctx) => { + expect(ctx.stderr).to.contain('Error: an error is reported but is not rethrown') + expect(ctx.stdout).to.equal('') }) - .it('applies the exit property on options to the error object') + describe('applying oclif errors', () => { fancy - .do(() => { - const e: any = new Error('An existing error object error!') - e.oclif = { - code: 'ORIG_EXIT_CODE', - } + .do(() => { + error(new Error('An existing error object error!')) + }) + .catch((error: any) => { + const defaultErrorCode = 2 + expect(error.oclif.exit).to.equal(defaultErrorCode) + }) + .it('adds oclif exit code to errors by default') - error(e) - }) - .catch((error: any) => { - expect(error.oclif.code).to.equal('ORIG_EXIT_CODE') - }) - .it('preserves original oclif exitable error properties and is not overwritten by options') + fancy + .do(() => { + error(new Error('An existing error object error!'), {exit: 9001}) + }) + .catch((error: any) => { + expect(error.oclif.exit).to.equal(9001) + }) + .it('applies the exit property on options to the error object') + + fancy + .do(() => { + const e: any = new Error('An existing error object error!') + e.oclif = { + code: 'ORIG_EXIT_CODE', + } + + error(e) + }) + .catch((error: any) => { + expect(error.oclif.code).to.equal('ORIG_EXIT_CODE') + }) + .it('preserves original oclif exitable error properties and is not overwritten by options') }) }) diff --git a/test/errors/handle.test.ts b/test/errors/handle.test.ts index 9582cde10..df89a323f 100644 --- a/test/errors/handle.test.ts +++ b/test/errors/handle.test.ts @@ -24,111 +24,111 @@ describe('handle', () => { }) fancy - .stdout() - .stderr() - .it('hides an exit error', async ctx => { - await handle(new ExitError(0)) - expect(ctx.stdout).to.equal('') - expect(ctx.stderr).to.equal('') - expect(exitStub.firstCall.firstArg).to.equal(0) - }) + .stdout() + .stderr() + .it('hides an exit error', async (ctx) => { + await handle(new ExitError(0)) + expect(ctx.stdout).to.equal('') + expect(ctx.stderr).to.equal('') + expect(exitStub.firstCall.firstArg).to.equal(0) + }) fancy - .stdout() - .stderr() - .it('prints error', async ctx => { - const error = new Error('foo bar baz') as Error & {skipOclifErrorHandling: boolean} - error.skipOclifErrorHandling = false - await handle(error) - expect(ctx.stdout).to.equal('') - expect(ctx.stderr).to.include('foo bar baz') - }) + .stdout() + .stderr() + .it('prints error', async (ctx) => { + const error = new Error('foo bar baz') as Error & {skipOclifErrorHandling: boolean} + error.skipOclifErrorHandling = false + await handle(error) + expect(ctx.stdout).to.equal('') + expect(ctx.stderr).to.include('foo bar baz') + }) fancy - .stdout() - .stderr() - .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 - await handle(error) - expect(ctx.stdout).to.equal('') - expect(ctx.stderr).to.equal('') - }) + .stdout() + .stderr() + .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 + await handle(error) + expect(ctx.stdout).to.equal('') + expect(ctx.stderr).to.equal('') + }) fancy - .stderr() - .do(() => { - config.errlog = errlog - }) - .finally(() => { - config.errlog = undefined - }) - .it('logs when errlog is set', async ctx => { - 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(exitStub.firstCall.firstArg).to.equal(2) - }) + .stderr() + .do(() => { + config.errlog = errlog + }) + .finally(() => { + config.errlog = undefined + }) + .it('logs when errlog is set', async (ctx) => { + 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(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) - }) + .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) - }) + .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) - }) + .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) - }) + .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() - .stdout() - .it('exits without displaying anything', async ctx => { - try { - exitErrorThrower(9000) - } catch (error: any) { - await handle(error) - } + .stderr() + .stdout() + .it('exits without displaying anything', async (ctx) => { + try { + exitErrorThrower(9000) + } catch (error: any) { + await handle(error) + } - expect(ctx.stdout).to.equal('') - expect(ctx.stderr).to.equal('') - expect(exitStub.firstCall.firstArg).to.equal(9000) - }) + expect(ctx.stdout).to.equal('') + expect(ctx.stderr).to.equal('') + expect(exitStub.firstCall.firstArg).to.equal(9000) + }) }) }) diff --git a/test/errors/pretty-print.test.ts b/test/errors/pretty-print.test.ts index 5b7259419..cea73bcd4 100644 --- a/test/errors/pretty-print.test.ts +++ b/test/errors/pretty-print.test.ts @@ -6,45 +6,39 @@ import {config} from '../../src/errors/config' const stripAnsi = require('strip-ansi') describe('pretty-print', () => { - fancy - .it('pretty prints an error', async () => { + fancy.it('pretty prints an error', async () => { const sampleError: Error & PrettyPrintableError = new Error('Something very serious has gone wrong with the flags!') sampleError.ref = 'https://oclif.io/docs/flags' sampleError.code = 'OCLIF_BAD_FLAG' sampleError.suggestions = ['Try using using a good flag'] - expect( - stripAnsi(prettyPrint(sampleError)), - ).to.equal(` Error: Something very serious has gone wrong with the flags! + expect(stripAnsi(prettyPrint(sampleError))).to + .equal(` Error: Something very serious has gone wrong with the flags! Code: OCLIF_BAD_FLAG Try this: Try using using a good flag Reference: https://oclif.io/docs/flags`) }) - fancy - .it('pretty prints multiple suggestions', async () => { + fancy.it('pretty prints multiple suggestions', async () => { const sampleError: Error & PrettyPrintableError = new Error('Something very serious has gone wrong with the flags!') sampleError.suggestions = ['Use a good flag', 'Use no flags'] - expect( - stripAnsi(prettyPrint(sampleError)), - ).to.equal(` Error: Something very serious has gone wrong with the flags! + expect(stripAnsi(prettyPrint(sampleError))).to + .equal(` Error: Something very serious has gone wrong with the flags! Try this: * Use a good flag * Use no flags`) }) - fancy - .it('pretty prints with omitted fields', async () => { + fancy.it('pretty prints with omitted fields', async () => { const sampleError = new Error('Something very serious has gone wrong with the flags!') - expect( - stripAnsi(prettyPrint(sampleError)), - ).to.equal(' Error: Something very serious has gone wrong with the flags!') + expect(stripAnsi(prettyPrint(sampleError))).to.equal( + ' Error: Something very serious has gone wrong with the flags!', + ) }) describe('CLI Error properties', () => { - fancy - .it('supports the bang property', async () => { + fancy.it('supports the bang property', async () => { class SampleCLIError extends CLIError { get bang() { return '>>>' @@ -55,8 +49,7 @@ describe('pretty-print', () => { expect(stripAnsi(prettyPrint(sampleError))).to.equal(' >>> Error: This is a CLI error') }) - fancy - .it('supports the \'name\' message prefix property', async () => { + fancy.it("supports the 'name' message prefix property", async () => { const defaultBang = process.platform === 'win32' ? '»' : '›' const sampleError = new CLIError('This is a CLI error') sampleError.name = 'Errorz' @@ -76,8 +69,7 @@ describe('pretty-print', () => { config.debug = initialConfigDebug }) - fancy - .it('shows the stack for an error', async () => { + fancy.it('shows the stack for an error', async () => { const error = new Error('oh no!') error.stack = 'this is the error stack property' expect(prettyPrint(error)).to.equal('this is the error stack property') diff --git a/test/errors/warn.test.ts b/test/errors/warn.test.ts index 90fb1b7ea..d51827aa5 100644 --- a/test/errors/warn.test.ts +++ b/test/errors/warn.test.ts @@ -8,17 +8,17 @@ const errlog = join(__dirname, '../tmp/mytest/warn.log') describe('warn', () => { fancy - .stderr() - .do(() => { - config.errlog = errlog - }) - .finally(() => { - config.errlog = undefined - }) - .it('warns', async ctx => { - warn('foo!') - expect(ctx.stderr).to.contain('Warning: foo!') - await config.errorLogger!.flush() - expect(await readFile(errlog, 'utf8')).to.contain('Warning: foo!') - }) + .stderr() + .do(() => { + config.errlog = errlog + }) + .finally(() => { + config.errlog = undefined + }) + .it('warns', async (ctx) => { + warn('foo!') + expect(ctx.stderr).to.contain('Warning: foo!') + await config.errorLogger!.flush() + expect(await readFile(errlog, 'utf8')).to.contain('Warning: foo!') + }) }) diff --git a/test/help/_test-help-class.ts b/test/help/_test-help-class.ts index be8391651..44eea87e0 100644 --- a/test/help/_test-help-class.ts +++ b/test/help/_test-help-class.ts @@ -5,7 +5,7 @@ import {HelpBase} from '../../src' -export default class extends HelpBase { +export default class extends HelpBase { async showHelp(): Promise { console.log('help') } diff --git a/test/help/docopts.test.ts b/test/help/docopts.test.ts index 12eaca7ff..658197747 100644 --- a/test/help/docopts.test.ts +++ b/test/help/docopts.test.ts @@ -4,90 +4,104 @@ import {Flags} from '../../src' describe('doc opts', () => { it('shows required string field', async () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.string({ - name: 'testFlag', - description: 'test', - required: true, - char: 'f', - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.string({ + name: 'testFlag', + description: 'test', + required: true, + char: 'f', + }), + }, + } as any) expect(usage).to.contain(' -f ') }) it('shows optional boolean field', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.boolean({ - name: 'testFlag', - description: 'test', - char: 'f', - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.boolean({ + name: 'testFlag', + description: 'test', + char: 'f', + }), + }, + } as any) // boolean fields don't have a value expect(usage).to.contain(' [-f]') }) it('shows no short char', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.string({ - name: 'testFlag', - description: 'test', - options: ['a', 'b'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.string({ + name: 'testFlag', + description: 'test', + options: ['a', 'b'], + }), + }, + } as any) expect(usage).to.contain(' [--testFlag a|b]') }) it('shows url type', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + }), + }, + } as any) expect(usage).to.contain(' [-s ]') }) it('does not show hidden type', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - hidden: true, - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + hidden: true, + }), + }, + } as any) expect(usage).to.not.contain(' [-s ]') }) it('shows optional one-way depended fields', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - }), - testFlag2: Flags.string({ - name: 'testFlag2', - description: 'test', - char: 'f', - dependsOn: ['testFlag'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + }), + testFlag2: Flags.string({ + name: 'testFlag2', + description: 'test', + char: 'f', + dependsOn: ['testFlag'], + }), + }, + } as any) expect(usage).to.contain(' [-f -s ]') }) it('shows one-way depended field on required field', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - required: true, - }), - testFlag2: Flags.string({ - name: 'testFlag2', - description: 'test', - char: 'f', - dependsOn: ['testFlag'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + required: true, + }), + testFlag2: Flags.string({ + name: 'testFlag2', + description: 'test', + char: 'f', + dependsOn: ['testFlag'], + }), + }, + } as any) // If a flag depends on a required flag, then it is optional. // So this should technically be "(-f [-s ])" but // does that even make sense anymore since -f will always be there? @@ -95,143 +109,159 @@ describe('doc opts', () => { expect(usage).to.contain(' (-f -s )') }) it('shows required one-way depended field on optional field', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - }), - testFlag2: Flags.string({ - name: 'testFlag2', - description: 'test', - char: 'f', - required: true, - dependsOn: ['testFlag'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + }), + testFlag2: Flags.string({ + name: 'testFlag2', + description: 'test', + char: 'f', + required: true, + dependsOn: ['testFlag'], + }), + }, + } as any) // If the required flag depends on an optional, it isn't really optional. expect(usage).to.contain(' (-f -s )') }) it('shows optional one-way exclusive fields', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - }), - testFlag2: Flags.string({ - name: 'testFlag2', - description: 'test', - char: 'f', - exclusive: ['testFlag'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + }), + testFlag2: Flags.string({ + name: 'testFlag2', + description: 'test', + char: 'f', + exclusive: ['testFlag'], + }), + }, + } as any) expect(usage).to.contain(' [-f | -s ]') }) it('shows one-way exclusive field on required field', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - required: true, - }), - testFlag2: Flags.string({ - name: 'testFlag2', - description: 'test', - char: 'f', - exclusive: ['testFlag'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + required: true, + }), + testFlag2: Flags.string({ + name: 'testFlag2', + description: 'test', + char: 'f', + exclusive: ['testFlag'], + }), + }, + } as any) expect(usage).to.contain(' (-f | -s )') }) it('shows required one-way exclusive field on optional field', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - }), - testFlag2: Flags.string({ - name: 'testFlag2', - description: 'test', - char: 'f', - required: true, - exclusive: ['testFlag'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + }), + testFlag2: Flags.string({ + name: 'testFlag2', + description: 'test', + char: 'f', + required: true, + exclusive: ['testFlag'], + }), + }, + } as any) expect(usage).to.contain(' (-f | -s )') }) it('shows option one-way exclusive field on optional field', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - }), - testFlag2: Flags.string({ - name: 'testFlag2', - description: 'test', - char: 'f', - required: true, - exclusive: ['testFlag'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + }), + testFlag2: Flags.string({ + name: 'testFlag2', + description: 'test', + char: 'f', + required: true, + exclusive: ['testFlag'], + }), + }, + } as any) expect(usage).to.contain(' (-f | -s )') }) it('shows optional exclusive fields defined twice', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - exclusive: ['testFlag2'], - }), - testFlag2: Flags.string({ - name: 'testFlag2', - description: 'test', - char: 'f', - exclusive: ['testFlag'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + exclusive: ['testFlag2'], + }), + testFlag2: Flags.string({ + name: 'testFlag2', + description: 'test', + char: 'f', + exclusive: ['testFlag'], + }), + }, + } as any) expect(usage).to.contain(' [-s | -f ]') }) it('shows optional two-way depended fields', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - dependsOn: ['testFlag2'], - }), - testFlag2: Flags.string({ - name: 'testFlag2', - description: 'test', - char: 'f', - dependsOn: ['testFlag'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + dependsOn: ['testFlag2'], + }), + testFlag2: Flags.string({ + name: 'testFlag2', + description: 'test', + char: 'f', + dependsOn: ['testFlag'], + }), + }, + } as any) expect(usage).to.contain(' [-s -f ]') }) it('shows required two-way depended fields', () => { - const usage = DocOpts.generate({flags: { - testFlag: Flags.url({ - name: 'testFlag', - description: 'test', - char: 's', - required: true, - dependsOn: ['testFlag2'], - }), - testFlag2: Flags.string({ - name: 'testFlag2', - description: 'test', - char: 'f', - required: true, - dependsOn: ['testFlag'], - }), - }} as any) + const usage = DocOpts.generate({ + flags: { + testFlag: Flags.url({ + name: 'testFlag', + description: 'test', + char: 's', + required: true, + dependsOn: ['testFlag2'], + }), + testFlag2: Flags.string({ + name: 'testFlag2', + description: 'test', + char: 'f', + required: true, + dependsOn: ['testFlag'], + }), + }, + } as any) expect(usage).to.contain(' (-s -f )') }) }) diff --git a/test/help/fixtures/fixtures.ts b/test/help/fixtures/fixtures.ts index 4a1661891..228b80a8a 100644 --- a/test/help/fixtures/fixtures.ts +++ b/test/help/fixtures/fixtures.ts @@ -126,3 +126,20 @@ export const DbTopic: Topic = { name: 'db', description: 'This topic is for the db topic', } + +// deprecateAliases +export class DeprecateAliases extends Command { + static id = 'foo:bar' + + static aliases = ['foo:bar:alias'] + + static deprecateAliases = true + + static flags = {} + + 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 f3a816dc6..ba0fd0e49 100644 --- a/test/help/format-command-with-options.test.ts +++ b/test/help/format-command-with-options.test.ts @@ -13,35 +13,38 @@ class Command extends Base { } const test = base -.loadConfig() -.add('help', ctx => new TestHelp(ctx.config as any)) -.register('commandHelp', commandHelp) + .loadConfig() + .add('help', (ctx) => new TestHelp(ctx.config as any)) + .register('commandHelp', commandHelp) describe('formatCommand', () => { test - .commandHelp(class extends Command { - static id = 'apps:create' + .commandHelp( + class extends Command { + 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'}), - } - }) - .it('handles multi-line help output', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + 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 ] [-l ] @@ -66,31 +69,35 @@ DESCRIPTION ALIASES $ oclif app:init - $ oclif create`)) + $ oclif create`), + ) describe('arg and flag multiline handling', () => { test - .commandHelp(class extends Command { - static id = 'apps:create' - - static description = 'description of apps:create' - - static aliases = ['app:init', 'create'] - - 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'}), - } - }) - .it('show args and flags side by side when their output do not exceed 4 lines ', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static id = 'apps:create' + + static description = 'description of apps:create' + + static aliases = ['app:init', 'create'] + + 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'}), + } + }, + ) + .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 ] @@ -116,30 +123,34 @@ OPTIONS ALIASES $ oclif app:init - $ oclif create`)) + $ oclif create`), + ) test - .commandHelp(class extends Command { - static id = 'apps:create' - - static description = 'description of apps:create' - - static aliases = ['app:init', 'create'] - - 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'}), - } - }) - .it('shows stacked args and flags when the lines exceed 4', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static id = 'apps:create' + + static description = 'description of apps:create' + + static aliases = ['app:init', 'create'] + + 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'}), + } + }, + ) + .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 ] @@ -173,28 +184,33 @@ OPTIONS ALIASES $ oclif app:init - $ oclif create`)) + $ oclif create`), + ) }) describe('description', () => { test - .commandHelp(class extends Command { - static id = 'apps:create' - - static description = 'description of apps:create\nthese values are after and will show up in the command description' - - static aliases = ['app:init', 'create'] - - static args = { - // eslint-disable-next-line camelcase - app_name: Args.string({description: 'app to use'}), - } - - 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 + .commandHelp( + class extends Command { + static id = 'apps:create' + + static description = + 'description of apps:create\nthese values are after and will show up in the command description' + + static aliases = ['app:init', 'create'] + + static args = { + // eslint-disable-next-line camelcase + app_name: Args.string({description: 'app to use'}), + } + + 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] ARGUMENTS @@ -208,54 +224,67 @@ DESCRIPTION ALIASES $ oclif app:init - $ oclif create`)) + $ oclif create`), + ) test - .commandHelp(class extends Command { - static id = 'apps:create' - - 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 + .commandHelp( + class extends Command { + static id = 'apps:create' + + 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 DESCRIPTION - The oclif CLI has apps:create`)) + The oclif CLI has apps:create`), + ) }) - describe(('flags'), () => { + describe('flags', () => { test - .commandHelp(class extends Command { - static id = 'apps:create' - - static flags = { - myenum: flags.string({ - options: ['a', 'b', 'c'], - }), - } - }) - .it('outputs flag enum', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static id = 'apps:create' + + 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] OPTIONS - --myenum=a|b|c`)) + --myenum=a|b|c`), + ) test - .commandHelp(class extends Command { - static id = 'apps:create' - - static args = { - arg1: Args.string({default: '.'}), - arg2: Args.string({default: '.', description: 'arg2 desc'}), - arg3: Args.string({description: 'arg3 desc'}), - } - - static flags = { - flag1: flags.string({default: '.'}), - flag2: flags.string({default: '.', description: 'flag2 desc'}), - flag3: flags.string({description: 'flag3 desc'}), - } - }).it('outputs with default flag options', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static id = 'apps:create' + + static args = { + arg1: Args.string({default: '.'}), + arg2: Args.string({default: '.', description: 'arg2 desc'}), + arg3: Args.string({description: 'arg3 desc'}), + } + + static flags = { + flag1: flags.string({default: '.'}), + flag2: flags.string({default: '.', description: 'flag2 desc'}), + flag3: flags.string({description: 'flag3 desc'}), + } + }, + ) + .it('outputs with default flag options', (ctx: any) => + expect(ctx.commandHelp).to.equal(`USAGE $ oclif apps:create [ARG1] [ARG2] [ARG3] [--flag1 ] [--flag2 ] [--flag3 ] @@ -267,144 +296,189 @@ ARGUMENTS OPTIONS --flag1=flag1 [default: .] --flag2=flag2 [default: .] flag2 desc - --flag3=flag3 flag3 desc`)) + --flag3=flag3 flag3 desc`), + ) test - .commandHelp(class extends Command { - static id = 'apps:create' - - static flags = { - opt: flags.boolean({allowNo: true}), - } - }) - .it('outputs with with no options', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static id = 'apps:create' + + 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] OPTIONS - --[no-]opt`)) + --[no-]opt`), + ) }) - describe('args', () => { + describe('args', () => { test - .commandHelp(class extends Command { - static id = 'apps:create' - - 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 + .commandHelp( + class extends Command { + static id = 'apps:create' + + 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] ARGUMENTS - ARG1 (option1|option2) Show the options`)) + ARG1 (option1|option2) Show the options`), + ) }) describe('usage', () => { test - .commandHelp(class extends Command { - static id = 'apps:create' - - 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`)) + .commandHelp( + class extends Command { + static id = 'apps:create' + + 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 usage = ['<%= config.bin %>', '<%= command.id %> usage'] - }) - .it('outputs usage arrays with templates', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static id = 'apps:create' + + static usage = ['<%= config.bin %>', '<%= command.id %> usage'] + }, + ) + .it('outputs usage arrays with templates', (ctx: any) => + expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif - $ oclif apps:create usage`)) + $ oclif apps:create usage`), + ) test - .commandHelp(class extends Command { - static id = 'apps:create' - - static usage = undefined - }) - .it('defaults usage when not specified', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE - $ oclif apps:create`)) + .commandHelp( + class extends Command { + static id = 'apps:create' + + static usage = undefined + }, + ) + .it('defaults usage when not specified', (ctx: any) => + expect(ctx.commandHelp).to.equal(`USAGE + $ oclif apps:create`), + ) }) describe('examples', () => { test - .commandHelp(class extends Command { - static examples = ['it handles a list of examples', 'more example text'] - }) - .it('outputs multiple examples', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static examples = ['it handles a list of examples', 'more example text'] + }, + ) + .it('outputs multiple examples', (ctx: any) => + expect(ctx.commandHelp).to.equal(`USAGE $ oclif EXAMPLES it handles a list of examples - more example text`)) + more example text`), + ) test - .commandHelp(class extends Command { - static examples = ['it handles a single example'] - }) - .it('outputs a single example', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static examples = ['it handles a single example'] + }, + ) + .it('outputs a single example', (ctx: any) => + expect(ctx.commandHelp).to.equal(`USAGE $ oclif EXAMPLES - it handles a single example`)) + it handles a single example`), + ) test - .commandHelp(class extends Command { - static id = 'oclif:command' - - 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 + .commandHelp( + class extends Command { + static id = 'oclif:command' + + 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 EXAMPLES the bin is oclif - the command id is oclif:command`)) + the command id is oclif:command`), + ) test - .commandHelp(class extends Command { - static id = 'oclif:command' - - static examples = ['<%= config.bin %> <%= command.id %> --help'] - }) - .it('formats if command', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static id = 'oclif:command' + + static examples = ['<%= config.bin %> <%= command.id %> --help'] + }, + ) + .it('formats if command', (ctx: any) => + expect(ctx.commandHelp).to.equal(`USAGE $ oclif oclif:command EXAMPLES - $ oclif oclif:command --help`)) + $ oclif oclif:command --help`), + ) test - .commandHelp(class extends Command { - static id = 'oclif:command' - - 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 + .commandHelp( + class extends Command { + static id = 'oclif:command' + + 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 EXAMPLES Prints out help. - $ oclif oclif:command --help`)) + $ oclif oclif:command --help`), + ) test - .commandHelp(class extends Command { - static id = 'oclif:command' - - static examples = [{description: 'Prints out help.', command: '<%= config.bin %> <%= command.id %> --help'}] - }) - .it('formats example object', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static id = 'oclif:command' + + 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 EXAMPLES Prints out help. - $ oclif oclif:command --help`)) + $ oclif oclif:command --help`), + ) }) }) diff --git a/test/help/format-command.test.ts b/test/help/format-command.test.ts index 72bcfb258..4df21cd93 100644 --- a/test/help/format-command.test.ts +++ b/test/help/format-command.test.ts @@ -13,40 +13,43 @@ class Command extends Base { } const test = base -.loadConfig() -.add('help', ctx => new TestHelp(ctx.config as any)) -.register('commandHelp', commandHelp) + .loadConfig() + .add('help', (ctx) => new TestHelp(ctx.config as any)) + .register('commandHelp', commandHelp) describe('formatCommand', () => { test - .commandHelp(class extends Command { - static { - this.id = 'apps:create' + .commandHelp( + class extends Command { + static { + this.id = 'apps:create' - this.aliases = ['app:init', 'create'] + this.aliases = ['app:init', 'create'] - this.description = `first line + this.description = `first line multiline help` - this.enableJsonFlag = true - - this.args = { - // eslint-disable-next-line camelcase - app_name: Args.string({description: 'app to use'}), - } - - 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)}), - 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 + this.enableJsonFlag = true + + this.args = { + // eslint-disable-next-line camelcase + app_name: Args.string({description: 'app to use'}), + } + + 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)}), + 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] [--json] [-f ] [--force] [--ss] [-r ] [-l ] @@ -76,35 +79,39 @@ DESCRIPTION ALIASES $ oclif app:init - $ oclif create`)) + $ oclif create`), + ) describe('arg and flag multiline handling', () => { test - .commandHelp(class extends Command { - static { - this.id = 'apps:create' - - this.description = 'description of apps:create' - - this.aliases = ['app:init', 'create'] - - this.enableJsonFlag = true - - this.args = { - // eslint-disable-next-line camelcase - app_name: Args.string({description: 'app to use'.repeat(35)}), - } - - 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 + .commandHelp( + class extends Command { + static { + this.id = 'apps:create' + + this.description = 'description of apps:create' + + this.aliases = ['app:init', 'create'] + + this.enableJsonFlag = true + + this.args = { + // eslint-disable-next-line camelcase + app_name: Args.string({description: 'app to use'.repeat(35)}), + } + + 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] [-r ] @@ -136,34 +143,38 @@ DESCRIPTION ALIASES $ oclif app:init - $ oclif create`)) + $ oclif create`), + ) test - .commandHelp(class extends Command { - static { - this.id = 'apps:create' - - this.description = 'description of apps:create' - - this.aliases = ['app:init', 'create'] - - this.enableJsonFlag = true - - this.args = { - // eslint-disable-next-line camelcase - app_name: Args.string({description: 'app to use'.repeat(35)}), - } - - 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 + .commandHelp( + class extends Command { + static { + this.id = 'apps:create' + + this.description = 'description of apps:create' + + this.aliases = ['app:init', 'create'] + + this.enableJsonFlag = true + + this.args = { + // eslint-disable-next-line camelcase + app_name: Args.string({description: 'app to use'.repeat(35)}), + } + + 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] [-r ] @@ -203,59 +214,72 @@ DESCRIPTION ALIASES $ oclif app:init - $ oclif create`)) + $ oclif create`), + ) }) describe('summary', () => { test - .commandHelp(class extends Command { - static id = 'test:summary' - - static summary = 'one line summary' - }) - .it('no description header if only a summary', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE - $ oclif test:summary`)) + .commandHelp( + class extends Command { + static id = 'test:summary' + + static summary = 'one line summary' + }, + ) + .it('no description header if only a summary', (ctx: any) => + expect(ctx.commandHelp).to.equal(`USAGE + $ oclif test:summary`), + ) test - .commandHelp(class extends Command { - static id = 'test:summary' + .commandHelp( + class extends Command { + static id = 'test:summary' - static summary = 'one line summary' + static summary = 'one line summary' - static description = 'description that is much longer than the summary' - }) - .it('outputs the summary at the top of the help and description', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + static description = 'description that is much longer than the summary' + }, + ) + .it('outputs the summary at the top of the help and description', (ctx: any) => + expect(ctx.commandHelp).to.equal(`USAGE $ oclif test:summary DESCRIPTION one line summary - description that is much longer than the summary`)) + description that is much longer than the summary`), + ) }) describe('description', () => { test - .commandHelp(class extends Command { - static { - this.id = 'apps:create' - - this.description = 'description of apps:create\n\nthese values are after and will show up in the command description' - - this.aliases = ['app:init', 'create'] - - this.enableJsonFlag = true - - this.args = { - // eslint-disable-next-line camelcase - app_name: Args.string({description: 'app to use'}), - } - - 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 + .commandHelp( + class extends Command { + static { + this.id = 'apps:create' + + this.description = + 'description of apps:create\n\nthese values are after and will show up in the command description' + + this.aliases = ['app:init', 'create'] + + this.enableJsonFlag = true + + this.args = { + // eslint-disable-next-line camelcase + app_name: Args.string({description: 'app to use'}), + } + + 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] ARGUMENTS @@ -274,29 +298,37 @@ DESCRIPTION ALIASES $ oclif app:init - $ oclif create`)) + $ oclif create`), + ) test - .commandHelp(class extends Command { - static id = 'apps:create' - - 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 + .commandHelp( + class extends Command { + static id = 'apps:create' + + 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 DESCRIPTION root part of the description - The oclif CLI has apps:create`)) + The oclif CLI has apps:create`), + ) test - .commandHelp(class extends Command { - static id = 'apps:create' - - 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 + .commandHelp( + class extends Command { + static id = 'apps:create' + + 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 DESCRIPTION @@ -304,64 +336,77 @@ DESCRIPTION using both carriage - return and new line`)) + return and new line`), + ) }) const myEnumValues = ['a', 'b', 'c'] - describe(('flags'), () => { + describe('flags', () => { test - .commandHelp(class extends Command { - static { - this.id = 'apps:create' - - this.flags = { - myenum: flags.string({ - description: 'the description', - options: myEnumValues, - }), - } - } - }) - .it('outputs flag enum', (ctx: any) => expect(ctx.commandHelp).to.equal(`USAGE + .commandHelp( + class extends Command { + static { + this.id = 'apps:create' + + 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] FLAGS --myenum=