From 60e03fcfc1ee532313f299843d49848f909b7a1a Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 1 Dec 2020 00:08:07 +0000 Subject: [PATCH] chore(NA): tool to find plugins circular dependencies between plugins (#82867) (#84588) * chore(NA): update gitignore to include first changes from moving into a single package.json * chore(NA): update gitignore * chore(NA): move all the dependencies into the single package.json and apply changes to bootstrap * chore(NA): fix types problems after the single package json * chore(NA): include code to find the dependencies used across the code * chore(NA): introduce pure lockfile for install dependencies on build * chore(NA): update clean task to not delete anything from xpack node_modules * chore(NA): update gitignore to remove development temporary rules * chore(NA): update notice file * chore(NA): update jest snapshots * chore(NA): fix whitelisted licenses to include a new specify form of an already included one * chore(NA): remove check lockfile symlinks from child projects * chore(NA): fix eslint and add missing declared deps on single pkg json * chore(NA): correctly update notice * chore(NA): fix failing jest test for storyshots.test.tsx * chore(NA): fix cypress multi reporter path * chore(NA): fix Project tests check * chore(NA): fix problem with logic to detect used dependes on oss build * chore(NA): include correct x-pack plugins dep discovery * chore(NA): discover entries under dynamic requires on vis_type_timelion * chore(NA): remove canvas * chore(NA): add initial code to find circular deps * chore(NA): ground work to integrate the circular deps scripts * chore(NA): add correct filtering to find circular dependenices feature * chore(NA): add ci mode flag into circular deps script * chore(NA): feature complete circular dependencies detect script * chore(NA): merge and solve conflicts with master * chore(NA): remove unwanted changes * chore(NA): remove unwanted changes on kbn storybook * chore(NA): hook find circular deps tool into ci * chore(NA): remove previous find plugin circular deps script * chore(NA): add type for circular dep list * chore(NA): add type for circular dep list for allowed list * chore(NA): allow CI to fail check * chore(NA): update deps allowed list * chore(NA): run search circular deps script over examples too * docs(NA): adds cli description * chore(NA): use plugin search paths to build entries to find circular deps * chore(NA): update allowed list * chore(NA): snapshot update for kbn optimizer test * chore(NA): update dpdm version * chore(NA): remove thirdParty flag * chore(NA): update docs to include info about the new tool * docs(NA): update to link PR instead of the issue * chore(NA): update debug logs to always output allowedList * fix(NA): correctly list found differences number * chore(NA): remove quiet flag * fix(NA): correctly fail the CI if circular deps are found * chore(NA): complete list of found circular deps * chore(NA): used named capturing group into the regex * docs(NA): update typescript best practices docs and styleguide * chore(NA): introduce quick filter option flag Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- STYLEGUIDE.md | 18 ++ .../best-practices/typescript.asciidoc | 4 +- package.json | 1 + ....js => find_plugins_with_circular_deps.js} | 2 +- src/dev/plugin_discovery/find_plugins.ts | 7 +- src/dev/run_find_plugin_circular_deps.ts | 73 ------ .../run_find_plugins_with_circular_deps.ts | 214 ++++++++++++++++++ .../checks/plugins_with_circular_deps.sh | 6 + vars/tasks.groovy | 1 + yarn.lock | 36 ++- 10 files changed, 283 insertions(+), 79 deletions(-) rename scripts/{find_plugin_circular_deps.js => find_plugins_with_circular_deps.js} (93%) delete mode 100644 src/dev/run_find_plugin_circular_deps.ts create mode 100644 src/dev/run_find_plugins_with_circular_deps.ts create mode 100644 test/scripts/checks/plugins_with_circular_deps.sh diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md index 94bb40ab3ff2e..cb75452a28cd2 100644 --- a/STYLEGUIDE.md +++ b/STYLEGUIDE.md @@ -589,6 +589,24 @@ Do not use setters, they cause more problems than they can solve. [sideeffect]: http://en.wikipedia.org/wiki/Side_effect_(computer_science) +### Avoid circular dependencies + +As part of a future effort to use correct and idempotent build tools we need our code to be +able to be represented as a directed acyclic graph. We must avoid having circular dependencies +both on code and type imports to achieve that. One of the most critical parts is the plugins +code. We've developed a tool to identify plugins with circular dependencies which +has allowed us to build a list of plugins who have circular dependencies +between each other. + +When building plugins we should avoid importing from plugins +who are known to have circular dependencies at the moment as well as introducing +new circular dependencies. You can run the same tool we use on our CI locally by +typing `node scripts/find_plugins_with_circular_deps --debug`. It will error out in +case new circular dependencies has been added with your changes +(which will also happen in the CI) as well as print out the current list of +the known circular dependencies which, as mentioned before, should not be imported +by your code until the circular dependencies on these have been solved. + ## SASS files When writing a new component, create a sibling SASS file of the same name and import directly into the **top** of the JS/TS component file. Doing so ensures the styles are never separated or lost on import and allows for better modularization (smaller individual plugin asset footprint). diff --git a/docs/developer/best-practices/typescript.asciidoc b/docs/developer/best-practices/typescript.asciidoc index 6d298f92b841e..f6db3fdffcb6a 100644 --- a/docs/developer/best-practices/typescript.asciidoc +++ b/docs/developer/best-practices/typescript.asciidoc @@ -19,7 +19,7 @@ More details are available in the https://www.typescriptlang.org/docs/handbook/p ==== Caveats This architecture imposes several limitations to which we must comply: -- Projects cannot have circular dependencies. Even though the Kibana platform doesn't support circular dependencies between Kibana plugins, TypeScript (and ES6 modules) does allow circular imports between files. So in theory, you may face a problem when migrating to the TS project references and you will have to resolve this circular dependency. https://github.com/elastic/kibana/issues/78162 is going to provide a tool to find such problem places. +- Projects cannot have circular dependencies. Even though the Kibana platform doesn't support circular dependencies between Kibana plugins, TypeScript (and ES6 modules) does allow circular imports between files. So in theory, you may face a problem when migrating to the TS project references and you will have to resolve this circular dependency. We've built a tool that can be used to find such problems. Please read the prerequisites section below to know how to use it. - A project must emit its type declaration. It's not always possible to generate a type declaration if the compiler cannot infer a type. There are two basic cases: 1. Your plugin exports a type inferring an internal type declared in Kibana codebase. In this case, you'll have to either export an internal type or to declare an exported type explicitly. @@ -30,6 +30,8 @@ This architecture imposes several limitations to which we must comply: Since project refs rely on generated `d.ts` files, the migration order does matter. You can migrate your plugin only when all the plugin dependencies already have migrated. It creates a situation where commonly used plugins (such as `data` or `kibana_react`) have to migrate first. Run `node scripts/find_plugins_without_ts_refs.js --id your_plugin_id` to get a list of plugins that should be switched to TS project refs to unblock your plugin migration. +Additionally, in order to migrate into project refs, you also need to make sure your plugin doesn't have circular dependencies with other plugins both on code and type imports. We run a job in the CI for each PR trying to find if new circular dependencies are being added which runs our tool with `node scripts/find_plugins_with_circular_deps`. However there are also a couple of circular dependencies already identified and that are in an allowed list to be solved. You also need to make sure your plugin don't rely in any other plugin into that allowed list. For a complete overview of the circular dependencies both found and in the allowed list as well as the complete circular dependencies path please run the following script locally with the debug flag `node scripts/find_plugins_with_circular_deps --debug` . + [discrete] ==== Implementation - Make sure all the plugins listed as dependencies in *requiredPlugins*, *optionalPlugins* & *requiredBundles* properties of `kibana.json` manifest file have migrated to TS project references. diff --git a/package.json b/package.json index 27e4466c22cfc..2a937d8b4a1f0 100644 --- a/package.json +++ b/package.json @@ -616,6 +616,7 @@ "delete-empty": "^2.0.0", "dependency-check": "^4.1.0", "diff": "^4.0.1", + "dpdm": "3.5.0", "ejs": "^3.1.5", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", diff --git a/scripts/find_plugin_circular_deps.js b/scripts/find_plugins_with_circular_deps.js similarity index 93% rename from scripts/find_plugin_circular_deps.js rename to scripts/find_plugins_with_circular_deps.js index 6b0661cb841b4..138fec33fd6b4 100644 --- a/scripts/find_plugin_circular_deps.js +++ b/scripts/find_plugins_with_circular_deps.js @@ -18,4 +18,4 @@ */ require('../src/setup_node_env'); -require('../src/dev/run_find_plugin_circular_deps'); +require('../src/dev/run_find_plugins_with_circular_deps'); diff --git a/src/dev/plugin_discovery/find_plugins.ts b/src/dev/plugin_discovery/find_plugins.ts index 4e7c34698c964..e07c503e21330 100644 --- a/src/dev/plugin_discovery/find_plugins.ts +++ b/src/dev/plugin_discovery/find_plugins.ts @@ -17,9 +17,12 @@ * under the License. */ import Path from 'path'; -import { REPO_ROOT } from '@kbn/dev-utils'; import { getPluginSearchPaths } from '@kbn/config'; -import { KibanaPlatformPlugin, simpleKibanaPlatformPluginDiscovery } from '@kbn/dev-utils'; +import { + KibanaPlatformPlugin, + REPO_ROOT, + simpleKibanaPlatformPluginDiscovery, +} from '@kbn/dev-utils'; export interface SearchOptions { oss: boolean; diff --git a/src/dev/run_find_plugin_circular_deps.ts b/src/dev/run_find_plugin_circular_deps.ts deleted file mode 100644 index 501e2c4fed048..0000000000000 --- a/src/dev/run_find_plugin_circular_deps.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { run } from '@kbn/dev-utils'; -import { findPlugins, getPluginDeps, SearchErrors } from './plugin_discovery'; - -interface AllOptions { - examples?: boolean; - extraPluginScanDirs?: string[]; -} - -run( - async ({ flags, log }) => { - const { examples = false, extraPluginScanDirs = [] } = flags as AllOptions; - - const pluginMap = findPlugins({ - oss: false, - examples, - extraPluginScanDirs, - }); - - const allErrors = new Map(); - for (const pluginId of pluginMap.keys()) { - const { errors } = getPluginDeps({ - pluginMap, - id: pluginId, - }); - - for (const [errorId, error] of errors) { - if (!allErrors.has(errorId)) { - allErrors.set(errorId, error); - } - } - } - - if (allErrors.size > 0) { - allErrors.forEach((error) => { - log.warning( - `Circular refs detected: ${[...error.stack, error.to].map((p) => `[${p}]`).join(' --> ')}` - ); - }); - } - }, - { - flags: { - boolean: ['examples'], - default: { - examples: false, - }, - allowUnexpected: false, - help: ` - --examples Include examples folder - --extraPluginScanDirs Include extra scan folder - `, - }, - } -); diff --git a/src/dev/run_find_plugins_with_circular_deps.ts b/src/dev/run_find_plugins_with_circular_deps.ts new file mode 100644 index 0000000000000..65a03a87525d7 --- /dev/null +++ b/src/dev/run_find_plugins_with_circular_deps.ts @@ -0,0 +1,214 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import dedent from 'dedent'; +import { parseDependencyTree, parseCircular, prettyCircular } from 'dpdm'; +import { relative } from 'path'; +import { getPluginSearchPaths } from '@kbn/config'; +import { REPO_ROOT, run } from '@kbn/dev-utils'; + +interface Options { + debug?: boolean; + filter?: string; +} + +type CircularDepList = Set; + +const allowedList: CircularDepList = new Set([ + 'src/plugins/charts -> src/plugins/expressions', + 'src/plugins/charts -> src/plugins/vis_default_editor', + 'src/plugins/data -> src/plugins/embeddable', + 'src/plugins/data -> src/plugins/expressions', + 'src/plugins/data -> src/plugins/ui_actions', + 'src/plugins/embeddable -> src/plugins/ui_actions', + 'src/plugins/expressions -> src/plugins/visualizations', + 'src/plugins/vis_default_editor -> src/plugins/visualizations', + 'src/plugins/vis_default_editor -> src/plugins/visualize', + 'src/plugins/visualizations -> src/plugins/visualize', + 'x-pack/plugins/actions -> x-pack/plugins/case', + 'x-pack/plugins/apm -> x-pack/plugins/infra', + 'x-pack/plugins/lists -> x-pack/plugins/security_solution', + 'x-pack/plugins/security -> x-pack/plugins/spaces', +]); + +run( + async ({ flags, log }) => { + const { debug, filter } = flags as Options; + const foundList: CircularDepList = new Set(); + + const pluginSearchPathGlobs = getPluginSearchPaths({ + rootDir: REPO_ROOT, + oss: false, + examples: true, + }).map((pluginFolderPath) => `${relative(REPO_ROOT, pluginFolderPath)}/**/*`); + + const depTree = await parseDependencyTree(pluginSearchPathGlobs, { + context: REPO_ROOT, + }); + + // Build list of circular dependencies as well as the circular dependencies full paths + const circularDependenciesFullPaths = parseCircular(depTree).filter((circularDeps) => { + const first = circularDeps[0]; + const last = circularDeps[circularDeps.length - 1]; + const matchRegex = /(?(src|x-pack)\/plugins|examples|x-pack\/examples)\/(?[^\/]*)\/.*/; + const firstMatch = first.match(matchRegex); + const lastMatch = last.match(matchRegex); + + if ( + firstMatch?.groups?.pluginFolder && + firstMatch?.groups?.pluginName && + lastMatch?.groups?.pluginFolder && + lastMatch?.groups?.pluginName + ) { + const firstPlugin = `${firstMatch.groups.pluginFolder}/${firstMatch.groups.pluginName}`; + const lastPlugin = `${lastMatch.groups.pluginFolder}/${lastMatch.groups.pluginName}`; + const sortedPlugins = [firstPlugin, lastPlugin].sort(); + + // Exclude if both plugin paths involved in the circular dependency + // doesn't includes the provided filter + if (filter && !firstPlugin.includes(filter) && !lastPlugin.includes(filter)) { + return false; + } + + if (firstPlugin !== lastPlugin) { + foundList.add(`${sortedPlugins[0]} -> ${sortedPlugins[1]}`); + return true; + } + } + + return false; + }); + + if (!debug && filter) { + log.warning( + dedent(` + !!!!!!!!!!!!!! WARNING: FILTER WITHOUT DEBUG !!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! Using the --filter flag without using --debug flag ! + ! will not allow you to see the filtered list of ! + ! the correct results. ! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + `) + ); + } + + if (debug && filter) { + log.warning( + dedent(` + !!!!!!!!!!!!!!! WARNING: FILTER FLAG IS ON !!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! Be aware the following results are not complete as ! + ! --filter flag has been passed. Ignore suggestions ! + ! to update the allowedList or any reports of failures ! + ! or successes. ! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + The following filter has peen passed: ${filter} + `) + ); + } + + // Log the full circular dependencies path if we are under debug flag + if (debug && circularDependenciesFullPaths.length > 0) { + log.debug( + dedent(` + !!!!!!!!!!!!!! CIRCULAR DEPENDENCIES FOUND !!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! Circular dependencies were found, you can find below ! + ! all the paths involved. ! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + `) + ); + log.debug(`${prettyCircular(circularDependenciesFullPaths)}\n`); + } + + // Always log the result of comparing the found list with the allowed list + const diffSet = (first: CircularDepList, second: CircularDepList) => + new Set([...first].filter((circularDep) => !second.has(circularDep))); + + const printList = (list: CircularDepList) => { + return Array.from(list) + .sort() + .reduce((listStr, entry) => { + return listStr ? `${listStr}\n'${entry}',` : `'${entry}',`; + }, ''); + }; + + const foundDifferences = diffSet(foundList, allowedList); + + if (debug && !foundDifferences.size) { + log.debug( + dedent(` + !!!!!!!!!!!!!!!!! UP TO DATE ALLOWED LIST !!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! The declared circular dependencies allowed list is up ! + ! to date and includes every plugin listed in above paths. ! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + The allowed circular dependencies list is (#${allowedList.size}): + ${printList(allowedList)} + `) + ); + } + + if (foundDifferences.size > 0) { + log.error( + dedent(` + !!!!!!!!!!!!!!!!! OUT OF DATE ALLOWED LIST !!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! The declared circular dependencies allowed list is out ! + ! of date. Please run the following locally to know more: ! + ! ! + ! 'node scripts/find_plugins_with_circular_deps --debug' ! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + The allowed circular dependencies list is (#${allowedList.size}): + ${printList(allowedList)} + + The found circular dependencies list is (#${foundList.size}): + ${printList(foundList)} + + The differences between both are (#${foundDifferences.size}): + ${printList(foundDifferences)} + + FAILED: circular dependencies in the allowed list declared on the file '${__filename}' did not match the found ones. + `) + ); + + process.exit(1); + } + + log.success('None non allowed circular dependencies were found'); + }, + { + description: + 'Searches circular dependencies between plugins located under src/plugins, x-pack/plugins, examples and x-pack/examples', + flags: { + boolean: ['debug'], + string: ['filter'], + default: { + debug: false, + }, + help: ` + --debug Run the script in debug mode which enables detailed path logs for circular dependencies + --filter It will only include in the results circular deps where the plugin paths contains parts of the passed string in the filter + `, + }, + } +); diff --git a/test/scripts/checks/plugins_with_circular_deps.sh b/test/scripts/checks/plugins_with_circular_deps.sh new file mode 100644 index 0000000000000..77880243538d2 --- /dev/null +++ b/test/scripts/checks/plugins_with_circular_deps.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +checks-reporter-with-killswitch "Check plugins with circular dependencies" \ + node scripts/find_plugins_with_circular_deps diff --git a/vars/tasks.groovy b/vars/tasks.groovy index b6bcc0d93f9c0..89302e91ad479 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -12,6 +12,7 @@ def check() { kibanaPipeline.scriptTask('Check i18n', 'test/scripts/checks/i18n.sh'), kibanaPipeline.scriptTask('Check File Casing', 'test/scripts/checks/file_casing.sh'), kibanaPipeline.scriptTask('Check Licenses', 'test/scripts/checks/licenses.sh'), + kibanaPipeline.scriptTask('Check Plugins With Circular Dependencies', 'test/scripts/checks/plugins_with_circular_deps.sh'), kibanaPipeline.scriptTask('Verify NOTICE', 'test/scripts/checks/verify_notice.sh'), kibanaPipeline.scriptTask('Test Projects', 'test/scripts/checks/test_projects.sh'), kibanaPipeline.scriptTask('Test Hardening', 'test/scripts/checks/test_hardening.sh'), diff --git a/yarn.lock b/yarn.lock index a4815f4dc74ae..a7b4375e70c8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4689,6 +4689,13 @@ dependencies: "@types/jquery" "*" +"@types/fs-extra@^8.0.0": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068" + integrity sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w== + dependencies: + "@types/node" "*" + "@types/geojson@*", "@types/geojson@7946.0.7": version "7946.0.7" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" @@ -12044,6 +12051,22 @@ dotignore@^0.1.2: dependencies: minimatch "^3.0.4" +dpdm@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/dpdm/-/dpdm-3.5.0.tgz#414402f21928694bc86cfe8e3583dc8fc97d013e" + integrity sha512-bff2gDpYyzmIOMwRp0Bsk0T4e/qgLRCeuGHZYEsJV0LRzuTUkXirCiLcme7Ebu/LVoQ8yAKiody5/1e51tsmFw== + dependencies: + "@types/fs-extra" "^8.0.0" + "@types/glob" "^7.1.1" + "@types/yargs" "^13.0.0" + chalk "^2.4.2" + fs-extra "^8.1.0" + glob "^7.1.4" + ora "^4.0.3" + tslib "^1.10.0" + typescript "^3.5.3" + yargs "^13.3.0" + dtrace-provider@~0.8: version "0.8.8" resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" @@ -14260,6 +14283,15 @@ fs-extra@^7.0.0, fs-extra@^7.0.1, fs-extra@~7.0.1: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^9.0.0, fs-extra@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" @@ -21478,7 +21510,7 @@ ora@^3.0.0: strip-ansi "^5.2.0" wcwidth "^1.0.1" -ora@^4.0.4: +ora@^4.0.3, ora@^4.0.4: version "4.1.1" resolved "https://registry.yarnpkg.com/ora/-/ora-4.1.1.tgz#566cc0348a15c36f5f0e979612842e02ba9dddbc" integrity sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A== @@ -27811,7 +27843,7 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -typescript@4.1.2, typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.7.2: +typescript@4.1.2, typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5, typescript@^3.5.3, typescript@~3.7.2: version "4.1.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==