From 3a50c3b1e0a941d3821030638e163af0123e1cdf Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 28 Sep 2023 16:34:53 -0600 Subject: [PATCH 01/11] chore: update prettier config --- .editorconfig | 11 ---- .eslintrc.json | 3 + package.json | 7 +- src/config/config.ts | 152 +++++++++++++++++++++++++------------------ yarn.lock | 18 +++-- 5 files changed, 111 insertions(+), 80 deletions(-) delete mode 100644 .editorconfig 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..253b10e6d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,6 +3,9 @@ "oclif", "oclif-typescript" ], + "plugins": [ + "eslint-config-prettier" + ], "rules": { "sort-imports": "error", "unicorn/prefer-module": "off", diff --git a/package.json b/package.json index 43c7814d8..2af65cb1b 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@commitlint/config-conventional": "^12.1.4", "@oclif/plugin-help": "^5.2.8", "@oclif/plugin-plugins": "^3.3.0", - "@oclif/prettier-config": "^0.1.1", + "@oclif/prettier-config": "^0.2.0", "@oclif/test": "^3.0.0-beta.1", "@types/ansi-styles": "^3.2.1", "@types/benchmark": "^2.1.2", @@ -63,6 +63,7 @@ "eslint": "^8.49.0", "eslint-config-oclif": "^5.0.0", "eslint-config-oclif-typescript": "^2.0.1", + "eslint-config-prettier": "^9.0.0", "fancy-test": "^3.0.0-beta.2", "globby": "^11.1.0", "husky": "6", @@ -70,10 +71,11 @@ "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 +109,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/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/yarn.lock b/yarn.lock index 30b5a6612..b4235d48c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -820,10 +820,10 @@ validate-npm-package-name "^5.0.0" yarn "^1.22.18" -"@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/prettier-config@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@oclif/prettier-config/-/prettier-config-0.2.0.tgz#ce4fd25fa43da802c74706b6872fd9c166ebf3f1" + integrity sha512-kFjaP1ZU+kGx+9dS/xo8fgDdZDWXeFPRusantF9ZI2NznoHPfiP6ljgPik7UyQ4hOlStMkRSj8YHwG1bhXplYQ== "@oclif/test@^3.0.0-beta.1": version "3.0.0-beta.1" @@ -2628,6 +2628,11 @@ eslint-config-oclif@^5.0.0: eslint-plugin-node "^11.1.0" eslint-plugin-unicorn "^48.0.1" +eslint-config-prettier@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f" + integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== + eslint-config-xo-space@^0.34.0: version "0.34.0" resolved "https://registry.yarnpkg.com/eslint-config-xo-space/-/eslint-config-xo-space-0.34.0.tgz#974db7f7091edc23e2f29cc98acaa1be78ac87e5" @@ -5434,6 +5439,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" + integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== + pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" From aaca4385c489327bad421a166d60899ae682b5b7 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 28 Sep 2023 16:51:50 -0600 Subject: [PATCH 02/11] chore: prettier and lint-staged --- .eslintrc.json | 5 +- .husky/pre-commit | 4 + package.json | 10 +- yarn.lock | 384 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 382 insertions(+), 21 deletions(-) create mode 100755 .husky/pre-commit diff --git a/.eslintrc.json b/.eslintrc.json index 253b10e6d..c4b8e9a6b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,10 +1,11 @@ { "extends": [ "oclif", - "oclif-typescript" + "oclif-typescript", + "prettier" ], "plugins": [ - "eslint-config-prettier" + "prettier" ], "rules": { "sort-imports": "error", 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/package.json b/package.json index 2af65cb1b..4cfa15423 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@commitlint/config-conventional": "^12.1.4", "@oclif/plugin-help": "^5.2.8", "@oclif/plugin-plugins": "^3.3.0", - "@oclif/prettier-config": "^0.2.0", + "@oclif/prettier-config": "^0.2.1", "@oclif/test": "^3.0.0-beta.1", "@types/ansi-styles": "^3.2.1", "@types/benchmark": "^2.1.2", @@ -64,9 +64,11 @@ "eslint-config-oclif": "^5.0.0", "eslint-config-oclif-typescript": "^2.0.1", "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "5.0.0", "fancy-test": "^3.0.0-beta.2", "globby": "^11.1.0", "husky": "6", + "lint-staged": "^14.0.1", "madge": "^6.1.0", "mocha": "^10.2.0", "nock": "^13.3.0", @@ -120,5 +122,9 @@ "test:perf": "ts-node test/perf/parser.perf.ts", "test": "nyc mocha --forbid-only \"test/**/*.test.ts\"" }, - "types": "lib/index.d.ts" + "types": "lib/index.d.ts", + "lint-staged": { + "+(src|test)/**/*.+(ts|js|json)": ["prettier --write"], + "*.ts": ["eslint --fix"] + } } diff --git a/yarn.lock b/yarn.lock index b4235d48c..3ccbef7eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -820,10 +820,10 @@ validate-npm-package-name "^5.0.0" yarn "^1.22.18" -"@oclif/prettier-config@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@oclif/prettier-config/-/prettier-config-0.2.0.tgz#ce4fd25fa43da802c74706b6872fd9c166ebf3f1" - integrity sha512-kFjaP1ZU+kGx+9dS/xo8fgDdZDWXeFPRusantF9ZI2NznoHPfiP6ljgPik7UyQ4hOlStMkRSj8YHwG1bhXplYQ== +"@oclif/prettier-config@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@oclif/prettier-config/-/prettier-config-0.2.1.tgz#1def9f38134f9bfb229257f48a35f7d0d183dc78" + integrity sha512-XB8kwQj8zynXjIIWRm+6gO/r8Qft2xKtwBMSmq1JRqtA6TpwpqECqiu8LosBCyg2JBXuUy2lU23/L98KIR7FrQ== "@oclif/test@^3.0.0-beta.1": version "3.0.0-beta.1" @@ -838,6 +838,18 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@pkgr/utils@^2.3.1": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" + integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== + dependencies: + cross-spawn "^7.0.3" + fast-glob "^3.3.0" + is-glob "^4.0.3" + open "^9.1.0" + picocolors "^1.0.0" + tslib "^2.6.0" + "@sigstore/bundle@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-1.1.0.tgz#17f8d813b09348b16eeed66a8cf1c3d6bd3d04f1" @@ -1380,6 +1392,13 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: dependencies: type-fest "^0.21.3" +ansi-escapes@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-5.0.0.tgz#b6a0caf0eef0c41af190e9a749e0c00ec04bb2a6" + integrity sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA== + dependencies: + type-fest "^1.0.2" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -1414,7 +1433,7 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -ansi-styles@^6.1.0: +ansi-styles@^6.0.0, ansi-styles@^6.1.0: version "6.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== @@ -1628,6 +1647,11 @@ benchmark@^2.1.4: lodash "^4.17.4" platform "^1.3.3" +big-integer@^1.6.44: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + bin-links@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-4.0.2.tgz#13321472ea157e9530caded2b7281496d698665b" @@ -1652,6 +1676,13 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" +bplist-parser@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" + integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== + dependencies: + big-integer "^1.6.44" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1667,7 +1698,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -1717,6 +1748,13 @@ builtins@^5.0.0: dependencies: semver "^7.0.0" +bundle-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" + integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== + dependencies: + run-applescript "^5.0.0" + cacache@^17.0.0, cacache@^17.0.4, cacache@^17.1.3: version "17.1.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.1.4.tgz#b3ff381580b47e85c6e64f801101508e26604b35" @@ -1810,6 +1848,11 @@ chai@^4.3.7: pathval "^1.1.1" type-detect "^4.0.5" +chalk@5.3.0, chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -1827,11 +1870,6 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -1910,6 +1948,13 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-cursor@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea" + integrity sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg== + dependencies: + restore-cursor "^4.0.0" + cli-progress@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" @@ -1931,6 +1976,14 @@ cli-table3@^0.6.3: optionalDependencies: "@colors/colors" "1.5.0" +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -1988,6 +2041,11 @@ color-support@^1.1.3: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +colorette@^2.0.20: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + columnify@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" @@ -1996,6 +2054,11 @@ columnify@^1.6.0: strip-ansi "^6.0.1" wcwidth "^1.0.0" +commander@11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-11.0.0.tgz#43e19c25dbedc8256203538e8d7e9346877a6f67" + integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ== + 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" @@ -2199,6 +2262,24 @@ deep-is@^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-browser-id@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" + integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== + dependencies: + bplist-parser "^0.2.0" + untildify "^4.0.0" + +default-browser@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" + integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== + dependencies: + bundle-name "^3.0.0" + default-browser-id "^3.0.0" + execa "^7.1.1" + titleize "^3.0.0" + 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" @@ -2222,6 +2303,11 @@ define-data-property@^1.0.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" @@ -2741,6 +2827,14 @@ eslint-plugin-node@^11.1.0: resolve "^1.10.1" semver "^6.1.0" +eslint-plugin-prettier@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz#6887780ed95f7708340ec79acfdf60c35b9be57a" + integrity sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.8.5" + eslint-plugin-unicorn@^48.0.1: version "48.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-48.0.1.tgz#a6573bc1687ae8db7121fdd8f92394b6549a6959" @@ -2890,11 +2984,46 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +execa@7.2.0, execa@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" + integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + exponential-backoff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" @@ -2935,6 +3064,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" @@ -2946,7 +3080,7 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.3.1: +fast-glob@^3.3.0, fast-glob@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== @@ -3228,6 +3362,11 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-stream@^6.0.0, get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -3509,6 +3648,16 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +human-signals@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" + integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== + humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -3729,6 +3878,11 @@ is-docker@^2.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -3739,6 +3893,11 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" @@ -3753,6 +3912,13 @@ is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + is-interactive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" @@ -3840,6 +4006,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -4253,11 +4424,44 @@ libnpmversion@^4.0.2: proc-log "^3.0.0" semver "^7.3.7" +lilconfig@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +lint-staged@^14.0.1: + version "14.0.1" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-14.0.1.tgz#57dfa3013a3d60762d9af5d9c83bdb51291a6232" + integrity sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw== + dependencies: + chalk "5.3.0" + commander "11.0.0" + debug "4.3.4" + execa "7.2.0" + lilconfig "2.1.0" + listr2 "6.6.1" + micromatch "4.0.5" + pidtree "0.6.0" + string-argv "0.3.2" + yaml "2.3.1" + +listr2@6.6.1: + version "6.6.1" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-6.6.1.tgz#08b2329e7e8ba6298481464937099f4a2cd7f95d" + integrity sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg== + dependencies: + cli-truncate "^3.1.0" + colorette "^2.0.20" + eventemitter3 "^5.0.1" + log-update "^5.0.1" + rfdc "^1.3.0" + wrap-ansi "^8.1.0" + load-json-file@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-5.3.0.tgz#4d3c1e01fa1c03ea78a60ac7af932c9ce53403f3" @@ -4311,6 +4515,17 @@ log-symbols@4.1.0, log-symbols@^4.0.0, log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" +log-update@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-5.0.1.tgz#9e928bf70cb183c1f0c9e91d9e6b7115d597ce09" + integrity sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw== + dependencies: + ansi-escapes "^5.0.0" + cli-cursor "^4.0.0" + slice-ansi "^5.0.0" + strip-ansi "^7.0.1" + wrap-ansi "^8.0.1" + loupe@^2.3.1: version "2.3.4" resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" @@ -4455,11 +4670,24 @@ meow@^9.0.0: type-fest "^0.18.0" yargs-parser "^20.2.3" +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +micromatch@4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" @@ -4473,6 +4701,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -4919,6 +5152,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + npm-user-validate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-2.0.0.tgz#7b69bbbff6f7992a1d9a8968d52fd6b6db5431b6" @@ -5111,13 +5351,30 @@ once@^1.3.0: dependencies: wrappy "1" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: 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" +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +open@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" + integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== + dependencies: + default-browser "^4.0.0" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + is-wsl "^2.2.0" + optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -5293,6 +5550,11 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + path-parse@^1.0.6, path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -5333,6 +5595,16 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pidtree@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" @@ -5439,6 +5711,13 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + prettier@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" @@ -5784,6 +6063,14 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +restore-cursor@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9" + integrity sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== + 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" @@ -5794,6 +6081,11 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -5801,6 +6093,13 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +run-applescript@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" + integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== + dependencies: + execa "^5.0.0" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -5951,7 +6250,7 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.2, signal-exit@^3.0.7: +signal-exit@^3.0.2, signal-exit@^3.0.3, 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== @@ -6010,6 +6309,14 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" @@ -6114,6 +6421,11 @@ stream-to-array@^2.3.0: dependencies: any-promise "^1.1.0" +string-argv@0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" + integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== + "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" @@ -6123,7 +6435,7 @@ stream-to-array@^2.3.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1, string-width@^5.1.2: +string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== @@ -6206,6 +6518,16 @@ strip-bom@^4.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -6270,6 +6592,14 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +synckit@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" + integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== + dependencies: + "@pkgr/utils" "^2.3.1" + tslib "^2.5.0" + tapable@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -6323,6 +6653,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== +titleize@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" + integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== + 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" @@ -6412,7 +6747,7 @@ tslib@^2.5.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== -tslib@^2.6.2: +tslib@^2.6.0, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -6482,6 +6817,11 @@ type-fest@^0.8.0, type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^1.0.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + typed-array-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" @@ -6577,6 +6917,11 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + 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" @@ -6729,7 +7074,7 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^8.1.0: +wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== @@ -6781,6 +7126,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" + integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== + yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" From de1f7301d9da08feb536ee6e94b1cf000e6a9f1c Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 28 Sep 2023 16:53:19 -0600 Subject: [PATCH 03/11] chore: reformat everything --- src/args.ts | 8 +- src/cli-ux/action/base.ts | 32 +- src/cli-ux/action/spinner.ts | 12 +- 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/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 | 108 +-- 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/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 | 433 +++++----- 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 +- 118 files changed, 4524 insertions(+), 3999 deletions(-) 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..65bf4e798 100644 --- a/src/cli-ux/action/spinner.ts +++ b/src/cli-ux/action/spinner.ts @@ -34,10 +34,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() @@ -89,8 +89,6 @@ export default class SpinnerAction extends ActionBase { } 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/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..e936fc32c 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 @@ -176,8 +180,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 +196,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 +213,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 +223,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 +254,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 +268,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 +315,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 +339,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 +352,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..13d3c6bfe 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' + ? // @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, } } @@ -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/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=