From 2159c0b970a0090f8bf21ff59e63dea1e788b5f9 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 5 Jan 2022 11:14:37 -0700 Subject: [PATCH] feat: add integration tests (#339) * feat: add integration tests * chore: use spaces in sf commands * chore: clean up --- .circleci/config.yml | 17 +- package.json | 5 +- test/integration/plugins.e2e.ts | 268 ++++++++++++++++++++++++++++++++ test/integration/sf.e2e.ts | 45 ++++++ test/integration/util.ts | 125 +++++++++++++++ yarn.lock | 121 +++++++++++++- 6 files changed, 578 insertions(+), 3 deletions(-) create mode 100644 test/integration/plugins.e2e.ts create mode 100644 test/integration/sf.e2e.ts create mode 100644 test/integration/util.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 96cf7b48d..12f808dec 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,6 +18,20 @@ workflows: - latest - lts - maintenance + alias: unit-tests + - release-management/test-package: + matrix: + parameters: + os: + - linux + - windows + node_version: + - latest + - lts + - maintenance + command: + - yarn test:e2e + alias: integration-tests - release-management/release-package: context: SF-CLI-RELEASE-PROCESS filters: @@ -25,7 +39,8 @@ workflows: only: main github-release: true requires: - - release-management/test-package + - unit-tests + - integration-tests dependabot-automerge: triggers: - schedule: diff --git a/package.json b/package.json index d00a7191d..40baa25b6 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@oclif/linewrap": "^1.0.0", "chalk": "^4.1.2", "clean-stack": "^3.0.1", - "cli-ux": "6.0.5", + "cli-ux": "^6.0.6", "debug": "^4.3.3", "fs-extra": "^9.1.0", "get-package-type": "^0.1.0", @@ -40,6 +40,7 @@ "@types/node-notifier": "^8.0.2", "@types/proxyquire": "^1.3.28", "@types/semver": "^7.3.9", + "@types/shelljs": "^0.8.10", "@types/strip-ansi": "^5.2.1", "@types/wrap-ansi": "^3.0.0", "chai": "^4.3.4", @@ -54,6 +55,7 @@ "mocha": "^8.4.0", "nock": "^13.2.1", "proxyquire": "^2.1.3", + "shelljs": "^0.8.4", "shx": "^0.3.3", "sinon": "^11.1.2", "ts-node": "^9.1.1", @@ -96,6 +98,7 @@ "posttest": "yarn lint", "prepack": "yarn run build", "test": "mocha --forbid-only \"test/**/*.test.ts\"", + "test:e2e": "mocha \"test/**/*.e2e.ts\" --timeout 600000", "pretest": "yarn build --noEmit && tsc -p test --noEmit" }, "types": "lib/index.d.ts" diff --git a/test/integration/plugins.e2e.ts b/test/integration/plugins.e2e.ts new file mode 100644 index 000000000..9b57c2921 --- /dev/null +++ b/test/integration/plugins.e2e.ts @@ -0,0 +1,268 @@ +import * as os from 'os' +import {expect} from 'chai' +import {Executor, Result, setup} from './util' + +describe('oclif plugins', () => { + let executor: Executor + before(async () => { + executor = await setup(__filename, { + repo: 'git@github.com:oclif/hello-world.git', + plugins: [ + '@oclif/plugin-autocomplete', + '@oclif/plugin-commands', + '@oclif/plugin-help', + '@oclif/plugin-not-found', + '@oclif/plugin-plugins', + '@oclif/plugin-update', + '@oclif/plugin-version', + '@oclif/plugin-which', + ], + }) + }) + + describe('plugin-help', () => { + describe(' help', () => { + let help: Result + before(async () => { + help = await executor.executeCommand('help') + }) + + it('should show description', () => { + expect(help.output).to.include('oclif example Hello World CLI') + }) + it('should show version', () => { + expect(help.output).to.include('VERSION\n oclif-hello-world/0.0.0') + }) + it('should show usage', () => { + expect(help.output).to.include('USAGE\n $ oclif-hello-world [COMMAND]') + }) + it('should show topics', () => { + expect(help.output).to.include('TOPICS\n plugins') + }) + it('should show commands', () => { + const regex = /COMMANDS\n\s\sautocomplete|\s\scommands|\s\shelp|\s\splugins|\s\sversion|\s\supdate|\s\swhich/ + expect(regex.test(help.output!)).to.be.true + }) + }) + + describe('help ', () => { + let help: Result + before(async () => { + help = await executor.executeCommand('help plugins') + }) + + it('should show summary', () => { + expect(help.output).to.include('List installed plugins.') + }) + it('should show usage', () => { + expect(help.output).to.include('USAGE\n $ oclif-hello-world plugins [--core]') + }) + it('should show description', () => { + expect(help.output).to.include('DESCRIPTION\n List installed plugins.') + }) + it('should show examples', () => { + expect(help.output).to.include('EXAMPLES\n $ oclif-hello-world plugins') + }) + it('should show commands', () => { + const regex = /COMMANDS\n\s\splugins:inspect|\s\splugins:install|\s\splugins:link|\s\splugins:uninstall|\s\splugins:update/ + expect(regex.test(help.output!)).to.be.true + }) + }) + + describe('help ', () => { + let help: Result + before(async () => { + help = await executor.executeCommand('help plugins:install') + }) + + it('should show summary', () => { + expect(help.output).to.include('Installs a plugin into the CLI.') + }) + it('should show usage', () => { + expect(help.output).to.include('USAGE\n $ oclif-hello-world plugins:install PLUGIN...') + }) + it('should show arguments', () => { + expect(help.output).to.include('ARGUMENTS\n PLUGIN Plugin to install.') + }) + it('should show flags', () => { + expect(help.output).to.include('FLAGS\n') + expect(help.output).to.include('-f, --force Run yarn install with force flag.') + expect(help.output).to.include('-h, --help Show CLI help.') + expect(help.output).to.include('-v, --verbose') + }) + it('should show description', () => { + expect(help.output).to.include('DESCRIPTION\n Installs a plugin into the CLI.') + }) + it('should show aliases', () => { + expect(help.output).to.include('ALIASES\n $ oclif-hello-world plugins:add') + }) + it('should show examples', () => { + expect(help.output).to.include('EXAMPLES\n') + expect(help.output).to.include('$ oclif-hello-world plugins:install myplugin') + expect(help.output).to.include('$ oclif-hello-world plugins:install https://github.com/someuser/someplugin') + expect(help.output).to.include('$ oclif-hello-world plugins:install someuser/someplugin') + }) + }) + }) + + describe('plugin-commands', () => { + let commands: Result + + it('should show commands', async () => { + commands = await executor.executeCommand('commands') + expect(commands.output).to.include('commands') + expect(commands.output).to.include('help') + expect(commands.output).to.include('plugins') + expect(commands.output).to.include('plugins:inspect') + expect(commands.output).to.include('plugins:install') + expect(commands.output).to.include('plugins:link') + expect(commands.output).to.include('plugins:uninstall') + expect(commands.output).to.include('plugins:update') + expect(commands.output).to.include('version') + expect(commands.output).to.include('which') + }) + + it('should fliter commands', async () => { + commands = await executor.executeCommand('commands --filter "command=plugins"') + expect(commands.output).to.include('plugins') + expect(commands.output).to.include('plugins:inspect') + expect(commands.output).to.include('plugins:install') + expect(commands.output).to.include('plugins:link') + expect(commands.output).to.include('plugins:uninstall') + expect(commands.output).to.include('plugins:update') + + expect(commands.output).to.not.include('commands') + expect(commands.output).to.not.include('help') + expect(commands.output).to.not.include('version') + expect(commands.output).to.not.include('which') + }) + + it('should extedned columns', async () => { + commands = await executor.executeCommand('commands --extended') + expect(commands.output).to.include('Command') + expect(commands.output).to.include('Summary') + expect(commands.output).to.include('Description') + expect(commands.output).to.include('Usage') + expect(commands.output).to.include('Plugin') + expect(commands.output).to.include('Type') + expect(commands.output).to.include('Hidden') + }) + + it('should filter columns', async () => { + commands = await executor.executeCommand('commands --columns Command') + expect(commands.output).to.include('Command') + expect(commands.output).to.not.include('Summary') + }) + + it('should show commands in csv', async () => { + commands = await executor.executeCommand('commands --csv') + expect(commands.output).to.include('Command,Summary\n') + expect(commands.output).to.include('commands') + expect(commands.output).to.include('help') + expect(commands.output).to.include('plugins') + expect(commands.output).to.include('plugins:inspect') + expect(commands.output).to.include('plugins:install') + expect(commands.output).to.include('plugins:link') + expect(commands.output).to.include('plugins:uninstall') + expect(commands.output).to.include('plugins:update') + expect(commands.output).to.include('version') + expect(commands.output).to.include('which') + }) + + it('should show commands in json', async () => { + commands = await executor.executeCommand('commands --json') + const json = JSON.parse(commands.output!) as Array<{ id: string }> + const commandIds = json.map(j => j.id) + expect(commandIds).to.include('commands') + expect(commandIds).to.include('help') + expect(commandIds).to.include('plugins') + expect(commandIds).to.include('plugins:inspect') + expect(commandIds).to.include('plugins:install') + expect(commandIds).to.include('plugins:link') + expect(commandIds).to.include('plugins:uninstall') + expect(commandIds).to.include('plugins:update') + expect(commandIds).to.include('version') + expect(commandIds).to.include('which') + }) + }) + + describe('plugin-plugins', () => { + afterEach(async () => { + await executor.executeCommand('plugins:uninstall @oclif/plugin-warn-if-update-available') + }) + + describe('installing a plugin by name', () => { + it('should install the plugin', async () => { + const result = await executor.executeCommand('plugins:install @oclif/plugin-warn-if-update-available') + expect(result.code).to.equal(0) + expect(result.output).to.include('@oclif/plugin-warn-if-update-available... installed v') + + const pluginsResult = await executor.executeCommand('plugins') + expect(pluginsResult.code).to.equal(0) + expect(pluginsResult.output).to.include('@oclif/plugin-warn-if-update-available') + }) + }) + + describe('installing a plugin by github url', () => { + after(async () => { + await executor.executeCommand('plugins:uninstall @oclif/plugin-warn-if-update-available') + }) + + it('should install the plugin', async () => { + const result = await executor.executeCommand('plugins:install https://github.com/oclif/plugin-warn-if-update-available') + expect(result.code).to.equal(0) + + const pluginsResult = await executor.executeCommand('plugins') + expect(pluginsResult.code).to.equal(0) + expect(pluginsResult.output).to.include('@oclif/plugin-warn-if-update-available') + }) + }) + + describe('forcefully installing a plugin', () => { + it('should install the plugin', async () => { + const result = await executor.executeCommand('plugins:install @oclif/plugin-warn-if-update-available --force') + expect(result.code).to.equal(0) + expect(result.output).to.include('@oclif/plugin-warn-if-update-available... installed v') + + const pluginsResult = await executor.executeCommand('plugins') + expect(pluginsResult.code).to.equal(0) + expect(pluginsResult.output).to.include('@oclif/plugin-warn-if-update-available') + }) + }) + + describe('uninstalling a plugin', () => { + beforeEach(async () => { + await executor.executeCommand('plugins:install @oclif/plugin-warn-if-update-available') + }) + + it('should uninstall the plugin', async () => { + const result = await executor.executeCommand('plugins:uninstall @oclif/plugin-warn-if-update-available') + expect(result.code).to.equal(0) + expect(result.output).to.include('success Uninstalled packages.Uninstalling @oclif/plugin-warn-if-update-available... done\n') + + const pluginsResult = await executor.executeCommand('plugins') + expect(pluginsResult.code).to.equal(0) + expect(pluginsResult.output).to.not.include('@oclif/plugin-warn-if-update-available') + }) + }) + }) + + describe('plugin-version', () => { + let version: Result + before(async () => { + version = await executor.executeCommand('version') + }) + + it('should show version', () => expect(version.output).to.include('oclif-hello-world/0.0.0')) + it('should show platform', () => expect(version.output).to.include(process.platform)) + it('should show arch', () => expect(version.output).to.include(os.arch())) + it('should show node version', () => expect(version.output).to.include(process.version)) + }) + + describe('plugin-which', () => { + it('should show the plugin that a command belongs to', async () => { + const result = await executor.executeCommand('which plugins:install') + expect(result.output).to.include('@oclif/plugin-plugins') + }) + }) +}) diff --git a/test/integration/sf.e2e.ts b/test/integration/sf.e2e.ts new file mode 100644 index 000000000..66848a370 --- /dev/null +++ b/test/integration/sf.e2e.ts @@ -0,0 +1,45 @@ +import {expect} from 'chai' +import {Executor, setup} from './util' + +describe('Salesforce CLI (sf)', () => { + let executor: Executor + before(async () => { + executor = await setup(__filename, {repo: 'git@github.com:salesforcecli/cli.git'}) + }) + + it('should show custom help', async () => { + const help = await executor.executeCommand('deploy metadata --help') + /** + * Regex matches that the help output matches this form: + * + * @example + * + * + * USAGE + * + * + * FLAGS + * + * + * GLOBAL FLAGS + * + * + * DESCRIPTION + * + * + * EXAMPLES + * + * + * FLAG DESCRIPTIONS + * + * + * CONFIGURATION VARIABLES + * + * + * ENVIRONMENT VARIABLES + * + */ + const regex = /^[A-Z].*\n\nUSAGE[\S\s]*\n\nFLAGS[\S\s]*\n\nGLOBAL FLAGS[\S\s]*\n\nDESCRIPTION[\S\s]*\n\nEXAMPLES[\S\s]*\n\nFLAG DESCRIPTIONS[\S\s]*\n\nCONFIGURATION VARIABLES[\S\s]*\n\nENVIRONMENT VARIABLES[\S\s]*$/g + expect(regex.test(help.output!)).to.be.true + }) +}) diff --git a/test/integration/util.ts b/test/integration/util.ts new file mode 100644 index 000000000..29525df18 --- /dev/null +++ b/test/integration/util.ts @@ -0,0 +1,125 @@ + +import {rm} from 'shelljs' +import {mkdirp} from 'fs-extra' +import * as cp from 'child_process' +import * as chalk from 'chalk' +import * as fs from 'fs' +import * as os from 'os' +import * as path from 'path' + +export type ExecError = cp.ExecException & { stderr: string; stdout: string }; + +export interface Result { + code: number; + output?: string; + error?: ExecError +} + +export interface Options { + repo: string; + plugins?: string[]; +} + +function updatePkgJson(testDir: string, obj: Record): void { + const pkgJsonFile = path.join(testDir, 'package.json') + const pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile, 'utf-8')) + obj.dependencies = Object.assign(pkgJson.dependencies || {}, obj.dependencies || {}) + obj.resolutions = Object.assign(pkgJson.resolutions || {}, obj.resolutions || {}) + const updated = Object.assign(pkgJson, obj) + fs.writeFileSync(pkgJsonFile, JSON.stringify(updated, null, 2)) +} + +export class Executor { + // eslint-disable-next-line no-useless-constructor + public constructor(private testDir: string) {} + + public executeInTestDir(cmd: string, silent = true): Promise { + return this.exec(cmd, this.testDir, silent) + } + + public executeCommand(cmd: string): Promise { + const executable = process.platform === 'win32' ? path.join('bin', 'run.cmd') : path.join('bin', 'run') + return this.executeInTestDir(`${executable} ${cmd} 2>&1`) + } + + public exec(cmd: string, cwd = process.cwd(), silent = true): Promise { + return new Promise(resolve => { + if (silent) { + try { + const r = cp.execSync(cmd, {stdio: 'pipe', cwd}) + resolve({code: 0, output: r.toString()}) + } catch (error) { + const err = error as ExecError + resolve({code: 1, error: err, output: err.stdout.toString()}) + } + } else { + console.log(chalk.cyan(cmd)) + cp.execSync(cmd, {stdio: 'inherit', cwd}) + resolve({code: 0}) + } + }) + } +} + +// eslint-disable-next-line valid-jsdoc +/** + * Setup for integration tests. + * + * Clones the hello-world repo from github + * Adds the local version of @oclif/core to the package.json + * Adds relevant oclif plugins + * Builds the package + * + * Environment Variables + * - OCLIF_CORE_E2E_TEST_DIR: the directory that you want the setup to happen in + * - OCLIF_CORE_E2E_SKIP_SETUP: skip all the setup steps (useful if iterating on tests) + */ +export async function setup(testFile: string, options: Options): Promise { + const testFileName = path.basename(testFile) + const location = path.join(process.env.OCLIF_CORE_E2E_TEST_DIR || os.tmpdir(), testFileName) + const [name] = options.repo.match(/(?<=\/).+?(?=\.)/) ?? ['hello-world'] + const testDir = path.join(location, name) + const executor = new Executor(testDir) + + console.log(chalk.cyan(`${testFileName}:`), testDir) + + if (process.env.OCLIF_CORE_E2E_SKIP_SETUP === 'true') { + console.log(chalk.yellow.bold('OCLIF_CORE_E2E_SKIP_SETUP is true. Skipping test setup...')) + return executor + } + + await mkdirp(location) + rm('-rf', testDir) + + const clone = `git clone ${options.repo} ${testDir}` + console.log(chalk.cyan(`${testFileName}:`), clone) + await executor.exec(clone) + + console.log(chalk.cyan(`${testFileName}:`), 'Updating package.json') + const dependencies = {'@oclif/core': `file:${path.resolve('.')}`} + + if (options.plugins) { + // eslint-disable-next-line unicorn/prefer-object-from-entries + const pluginDeps = options.plugins.reduce((x, y) => ({...x, [y]: 'latest'}), {}) + updatePkgJson(testDir, { + resolutions: {'@oclif/core': path.resolve('.')}, + dependencies: Object.assign(dependencies, pluginDeps), + oclif: {plugins: options.plugins}, + }) + } else { + updatePkgJson(testDir, { + resolutions: {'@oclif/core': path.resolve('.')}, + dependencies, + }) + } + + const install = 'yarn' + console.log(chalk.cyan(`${testFileName}:`), install) + await executor.executeInTestDir(install) + + const build = 'yarn build' + console.log(chalk.cyan(`${testFileName}:`), build) + await executor.executeInTestDir(build) + + return executor +} diff --git a/yarn.lock b/yarn.lock index 989bb9200..d3bae8ba3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -444,6 +444,52 @@ is-wsl "^2.1.1" tslib "^2.0.0" +"@oclif/core@1.0.10": + version "1.0.10" + resolved "https://registry.npmjs.org/@oclif/core/-/core-1.0.10.tgz#5fd01d572e44d372b7279ee0f49b4860e14b6e4e" + integrity sha512-L+IcNU3NoYxwz5hmHfcUlOJ3dpgHRsIj1kAmI9CKEJHq5gBVKlP44Ot179Jke1jKRKX2g9N42izbmlh0SNpkkw== + dependencies: + "@oclif/linewrap" "^1.0.0" + chalk "^4.1.2" + clean-stack "^3.0.1" + cli-ux "6.0.5" + debug "^4.3.3" + fs-extra "^9.1.0" + get-package-type "^0.1.0" + globby "^11.0.4" + indent-string "^4.0.0" + is-wsl "^2.2.0" + lodash "^4.17.21" + semver "^7.3.5" + string-width "^4.2.3" + strip-ansi "^6.0.1" + tslib "^2.3.1" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + +"@oclif/core@^1.0.11": + version "1.0.11" + resolved "https://registry.npmjs.org/@oclif/core/-/core-1.0.11.tgz#5ad1258c0c756073744ac8b9403e9314757d949f" + integrity sha512-CQw9dbzh+BTF73cp53mnDZnTJF4hth7oab761Si4pEW18PDYDLyOv7TnEtJkvPu4rlTaGBh2jge9BDXgwye4vQ== + dependencies: + "@oclif/linewrap" "^1.0.0" + chalk "^4.1.2" + clean-stack "^3.0.1" + cli-ux "6.0.5" + debug "^4.3.3" + fs-extra "^9.1.0" + get-package-type "^0.1.0" + globby "^11.0.4" + indent-string "^4.0.0" + is-wsl "^2.2.0" + lodash "^4.17.21" + semver "^7.3.5" + string-width "^4.2.3" + strip-ansi "^6.0.1" + tslib "^2.3.1" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + "@oclif/core@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@oclif/core/-/core-1.0.4.tgz#79ba3ed554441c3c08de38c3109275f3d9a1c188" @@ -536,6 +582,27 @@ widest-line "^3.1.0" wrap-ansi "^7.0.0" +"@oclif/core@file:/Users/mdonnalley/repos/oclif/core": + version "1.0.10" + dependencies: + "@oclif/linewrap" "^1.0.0" + chalk "^4.1.2" + clean-stack "^3.0.1" + cli-ux "^6.0.6" + debug "^4.3.3" + fs-extra "^9.1.0" + get-package-type "^0.1.0" + globby "^11.0.4" + indent-string "^4.0.0" + is-wsl "^2.2.0" + lodash "^4.17.21" + semver "^7.3.5" + string-width "^4.2.3" + strip-ansi "^6.0.1" + tslib "^2.3.1" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + "@oclif/errors@^1.2.1", "@oclif/errors@^1.2.2", "@oclif/errors@^1.3.3", "@oclif/errors@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.3.5.tgz#a1e9694dbeccab10fe2fe15acb7113991bed636c" @@ -674,6 +741,14 @@ dependencies: "@types/node" "*" +"@types/glob@*": + version "7.2.0" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + "@types/indent-string@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/indent-string/-/indent-string-4.0.1.tgz#fb4e6b8cdd8e94f70c105e78fb5d357a767b7193" @@ -696,6 +771,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.177.tgz#f70c0d19c30fab101cad46b52be60363c43c4578" integrity sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw== +"@types/minimatch@*": + version "3.0.5" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + "@types/minimist@^1.2.0": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" @@ -750,6 +830,14 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== +"@types/shelljs@^0.8.10": + version "0.8.10" + resolved "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.10.tgz#c33079c9b41a9d6f788f98f1d8a68f880a14f454" + integrity sha512-nhBdUA/n0nRo1B6E4BuRnUvllYAqal4T9zd91ZDnBh+qQMQTwvxmJHx6xEn/0vdjP2kqEA5eVeLazs4nMxeuFg== + dependencies: + "@types/glob" "*" + "@types/node" "*" + "@types/sinon@*": version "10.0.2" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.2.tgz#f360d2f189c0fd433d14aeb97b9d705d7e4cc0e4" @@ -1349,6 +1437,37 @@ cli-ux@^6.0.4: supports-hyperlinks "^2.1.0" tslib "^2.0.0" +cli-ux@^6.0.6: + version "6.0.6" + resolved "https://registry.npmjs.org/cli-ux/-/cli-ux-6.0.6.tgz#00536bf6038f195b0a1a2589f61ce5e625e75870" + integrity sha512-CvL4qmV78VhnbyHTswGjpDSQtU+oj3hT9DP9L6yMOwiTiNv0nMjMEV/8zou4CSqO6PtZ2A8qnlZDgAc07Js+aw== + dependencies: + "@oclif/core" "1.0.10" + "@oclif/linewrap" "^1.0.0" + "@oclif/screen" "^1.0.4 " + ansi-escapes "^4.3.0" + ansi-styles "^4.2.0" + cardinal "^2.1.1" + chalk "^4.1.0" + clean-stack "^3.0.0" + cli-progress "^3.9.1" + extract-stack "^2.0.0" + fs-extra "^8.1" + hyperlinker "^1.0.0" + indent-string "^4.0.0" + is-wsl "^2.2.0" + js-yaml "^3.13.1" + lodash "^4.17.21" + natural-orderby "^2.0.1" + object-treeify "^1.1.4" + password-prompt "^1.1.2" + semver "^7.3.2" + string-width "^4.2.0" + strip-ansi "^6.0.0" + supports-color "^8.1.0" + supports-hyperlinks "^2.1.0" + tslib "^2.0.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -3119,7 +3238,7 @@ shebang-regex@^3.0.0: shelljs@^0.8.4: version "0.8.4" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" + resolved "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== dependencies: glob "^7.0.0"