refactor: reduce number of leaked anys (#7465)

This commit is contained in:
Joshua Chen 2022-05-23 00:30:32 +08:00 committed by GitHub
parent 6e62bba30f
commit 89b0fff128
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 121 additions and 89 deletions

View file

@ -28,7 +28,11 @@ type PackageJsonFile = {
async function getPackagesJsonFiles(): Promise<PackageJsonFile[]> {
const files = await Globby('packages/*/package.json');
return Promise.all(
files.map((file) => fs.readJSON(file).then((content) => ({file, content}))),
files.map((file) =>
fs
.readJSON(file)
.then((content: PackageJsonFile['content']) => ({file, content})),
),
);
}

View file

@ -22,7 +22,11 @@ type TsconfigFile = {
async function getTsconfigFiles(): Promise<TsconfigFile[]> {
const files = await Globby('packages/*/tsconfig.*');
return Promise.all(
files.map((file) => fs.readJSON(file).then((content) => ({file, content}))),
files.map((file) =>
fs
.readJSON(file)
.then((content: TsconfigFile['content']) => ({file, content})),
),
);
}

View file

@ -65,7 +65,7 @@ async function askForPackageManagerChoice(): Promise<PackageManager> {
.map((p) => ({title: p, value: p}));
return (
await prompts(
(await prompts(
{
type: 'select',
name: 'packageManager',
@ -77,7 +77,7 @@ async function askForPackageManagerChoice(): Promise<PackageManager> {
logger.info`Falling back to name=${defaultPackageManager}`;
},
},
)
)) as {packageManager: PackageManager}
).packageManager;
}
@ -203,7 +203,7 @@ async function getGitCommand(gitStrategy: GitStrategy): Promise<string> {
case 'copy':
return 'git clone --recursive --depth 1';
case 'custom': {
const {command} = await prompts(
const {command} = (await prompts(
{
type: 'text',
name: 'command',
@ -215,7 +215,7 @@ async function getGitCommand(gitStrategy: GitStrategy): Promise<string> {
logger.info`Falling back to code=${'git clone'}`;
},
},
);
)) as {command: string};
return command ?? 'git clone';
}
case 'deep':
@ -245,7 +245,7 @@ async function getSiteName(
}
return reqName;
}
const {siteName} = await prompts(
const {siteName} = (await prompts(
{
type: 'text',
name: 'siteName',
@ -259,7 +259,7 @@ async function getSiteName(
process.exit(1);
},
},
);
)) as {siteName: string};
return siteName;
}
@ -324,7 +324,7 @@ async function getSource(
const template = cliOptions.gitStrategy
? 'Git repository'
: (
await prompts(
(await prompts(
{
type: 'select',
name: 'template',
@ -337,10 +337,10 @@ async function getSource(
process.exit(1);
},
},
)
)) as {template: Template | 'Git repository' | 'Local template'}
).template;
if (template === 'Git repository') {
const {gitRepoUrl} = await prompts(
const {gitRepoUrl} = (await prompts(
{
type: 'text',
name: 'gitRepoUrl',
@ -359,7 +359,7 @@ async function getSource(
process.exit(1);
},
},
);
)) as {gitRepoUrl: string};
let strategy = cliOptions.gitStrategy;
if (!strategy) {
({strategy} = await prompts(
@ -393,7 +393,7 @@ async function getSource(
strategy: strategy ?? 'deep',
};
} else if (template === 'Local template') {
const {templateDir} = await prompts(
const {templateDir} = (await prompts(
{
type: 'text',
name: 'templateDir',
@ -418,7 +418,7 @@ async function getSource(
process.exit(1);
},
},
);
)) as {templateDir: string};
return {
type: 'local',
path: templateDir,
@ -442,7 +442,7 @@ async function getSource(
}
async function updatePkg(pkgPath: string, obj: {[key: string]: unknown}) {
const pkg = await fs.readJSON(pkgPath);
const pkg = (await fs.readJSON(pkgPath)) as {[key: string]: unknown};
const newPkg = Object.assign(pkg, obj);
await fs.outputFile(pkgPath, `${JSON.stringify(newPkg, null, 2)}\n`);

View file

@ -92,8 +92,12 @@ async function readMetadataPath(metadataPath: string) {
*
* `{image: "./myImage.png"}` => `{image: require("./myImage.png")}`
*/
function createAssetsExportCode(assets: {[key: string]: unknown}) {
if (Object.keys(assets).length === 0) {
function createAssetsExportCode(assets: unknown) {
if (
typeof assets !== 'object' ||
!assets ||
Object.keys(assets).length === 0
) {
return 'undefined';
}
@ -101,7 +105,7 @@ function createAssetsExportCode(assets: {[key: string]: unknown}) {
function createAssetValueCode(assetValue: unknown): string | undefined {
if (Array.isArray(assetValue)) {
const arrayItemCodes = assetValue.map(
(item) => createAssetValueCode(item) ?? 'undefined',
(item: unknown) => createAssetValueCode(item) ?? 'undefined',
);
return `[${arrayItemCodes.join(', ')}]`;
}
@ -119,7 +123,7 @@ function createAssetsExportCode(assets: {[key: string]: unknown}) {
const assetEntries = Object.entries(assets);
const codeLines = assetEntries
.map(([key, value]) => {
.map(([key, value]: [string, unknown]) => {
const assetRequireCode = createAssetValueCode(value);
return assetRequireCode ? `"${key}": ${assetRequireCode},` : undefined;
})
@ -227,7 +231,7 @@ ${JSON.stringify(frontMatter, null, 2)}`;
: undefined;
const metadata = metadataJsonString
? JSON.parse(metadataJsonString)
? (JSON.parse(metadataJsonString) as {[key: string]: unknown})
: undefined;
const assets =

View file

@ -13,13 +13,14 @@ import removePosition from 'unist-util-remove-position';
import toString from 'mdast-util-to-string';
import visit from 'unist-util-visit';
import slug from '../index';
import type {Plugin} from 'unified';
function process(doc, plugins = []) {
function process(doc: string, plugins: Plugin[] = []) {
const processor = remark().use({plugins: [...plugins, slug]});
return removePosition(processor.runSync(processor.parse(doc)), true);
}
function heading(label, id) {
function heading(label: string, id: string) {
return u(
'heading',
{depth: 2, data: {id, hProperties: {id}}},

View file

@ -35,9 +35,11 @@ declare module '@generated/registry' {
declare module '@generated/routes' {
import type {RouteConfig as RRRouteConfig} from 'react-router-config';
import type Loadable from 'react-loadable';
type RouteConfig = RRRouteConfig & {
path: string;
component: ReturnType<typeof Loadable>;
};
const routes: RouteConfig[];
export default routes;

View file

@ -13,7 +13,11 @@ import {
URISchema,
} from '@docusaurus/utils-validation';
import {GlobExcludeDefault} from '@docusaurus/utils';
import type {PluginOptions, Options} from '@docusaurus/plugin-content-blog';
import type {
PluginOptions,
Options,
FeedType,
} from '@docusaurus/plugin-content-blog';
import type {OptionValidationContext} from '@docusaurus/types';
export const DEFAULT_OPTIONS: PluginOptions = {
@ -101,7 +105,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
Joi.alternatives().conditional(
Joi.string().equal('all', 'rss', 'atom', 'json'),
{
then: Joi.custom((val) =>
then: Joi.custom((val: FeedType | 'all') =>
val === 'all' ? ['rss', 'atom', 'json'] : [val],
),
},

View file

@ -53,7 +53,7 @@ async function createVersionedSidebarFile({
// Tests depend on non-default export for mocking.
export async function cliDocsVersionCommand(
version: string,
version: unknown,
{id: pluginId, path: docsPath, sidebarPath}: PluginOptions,
{siteDir, i18n}: LoadContext,
): Promise<void> {
@ -70,7 +70,7 @@ export async function cliDocsVersionCommand(
}
// Load existing versions.
let versions = [];
let versions: string[] = [];
const versionsJSONFile = getVersionsFilePath(siteDir, pluginId);
if (await fs.pathExists(versionsJSONFile)) {
versions = await fs.readJSON(versionsJSONFile);

View file

@ -100,7 +100,7 @@ export default async function pluginContentDocs(
.command(command)
.arguments('<version>')
.description(commandDescription)
.action((version) => {
.action((version: unknown) => {
cliDocsVersionCommand(version, options, context);
});
},

View file

@ -98,7 +98,7 @@ const OptionsSchema = Joi.object<PluginOptions>({
Joi.function(),
// Convert boolean values to functions
Joi.alternatives().conditional(Joi.boolean(), {
then: Joi.custom((val) =>
then: Joi.custom((val: boolean) =>
val ? DefaultNumberPrefixParser : DisabledNumberPrefixParser,
),
}),

View file

@ -95,7 +95,7 @@ async function readVersionsFile(
): Promise<string[] | null> {
const versionsFilePath = getVersionsFilePath(siteDir, pluginId);
if (await fs.pathExists(versionsFilePath)) {
const content = await fs.readJSON(versionsFilePath);
const content: unknown = await fs.readJSON(versionsFilePath);
validateVersionNames(content);
return content;
}

View file

@ -19,8 +19,9 @@ function BrowserOnlyReactJson(props: ReactJsonViewProps) {
return (
<BrowserOnly>
{() => {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const ReactJson = require('react-json-view').default;
const {default: ReactJson} =
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
require('react-json-view') as typeof import('react-json-view');
return <ReactJson {...props} />;
}}
</BrowserOnly>

View file

@ -320,6 +320,6 @@ if (typeof window !== 'undefined') {
addLegacyAppInstalledEventsListeners();
// Then try to register the SW using lazy/dynamic imports
registerSW().catch((e) => console.error('registerSW failed', e));
registerSW().catch((e: unknown) => console.error('registerSW failed', e));
}
}

View file

@ -35,7 +35,7 @@ export default async function createSitemap(
}
// https://github.com/staylor/react-helmet-async/pull/167
const meta = head[route]?.meta.toComponent() as unknown as
| ReactElement[]
| ReactElement<{name?: string; content?: string}>[]
| undefined;
return !meta?.some(
(tag) => tag.props.name === 'robots' && tag.props.content === 'noindex',

View file

@ -25,8 +25,10 @@ import styles from './styles.module.css';
// A very rough duck type, but good enough to guard against mistakes while
// allowing customization
function isTabItem(comp: ReactElement): comp is ReactElement<TabItemProps> {
return typeof comp.props.value !== 'undefined';
function isTabItem(
comp: ReactElement<object>,
): comp is ReactElement<TabItemProps> {
return 'value' in comp.props;
}
function TabsComponent(props: Props): JSX.Element {

View file

@ -196,9 +196,7 @@ function CollapsibleBase({
className,
disableSSRStyle,
}: CollapsibleBaseProps) {
// any because TS is a pain for HTML element refs, see https://twitter.com/sebastienlorber/status/1412784677795110914
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const collapsibleRef = useRef<any>(null);
const collapsibleRef = useRef<HTMLElement>(null);
useCollapseAnimation({collapsibleRef, collapsed, animation});
@ -206,7 +204,7 @@ function CollapsibleBase({
<As
// @ts-expect-error: the "too complicated type" is produced from
// "CollapsibleElementType" being a huge union
ref={collapsibleRef}
ref={collapsibleRef as RefObject<never>} // Refs are contravariant, which is not expressible in TS
style={disableSSRStyle ? undefined : getSSRStyle(collapsed)}
onTransitionEnd={(e: React.TransitionEvent) => {
if (e.propertyName !== 'height') {

View file

@ -318,7 +318,7 @@ export function useDocRouteMetadata({
}
// For now, the sidebarName is added as route config: not ideal!
const sidebarName = currentDocRoute.sidebar;
const sidebarName = currentDocRoute.sidebar as string;
const sidebarItems = sidebarName
? versionMetadata.docsSidebars[sidebarName]

View file

@ -23,7 +23,7 @@ import type {TranslationFileContent} from '@docusaurus/types';
async function getPackageCodePath(packageName: string) {
const packagePath = path.join(__dirname, '../..', packageName);
const packageJsonPath = path.join(packagePath, 'package.json');
const {main} = await fs.readJSON(packageJsonPath);
const {main} = (await fs.readJSON(packageJsonPath)) as {main: string};
const packageSrcPath = path.join(packagePath, path.dirname(main));
return packageSrcPath;
}

View file

@ -11,7 +11,7 @@ const JoiFrontMatterString: Joi.Extension = {
type: 'string',
base: Joi.string(),
// Fix Yaml that tries to auto-convert many things to string out of the box
prepare: (value) => {
prepare: (value: unknown) => {
if (typeof value === 'number' || value instanceof Date) {
return {value: value.toString()};
}

View file

@ -40,10 +40,10 @@ export const URISchema = Joi.alternatives(
Joi.string().uri({allowRelative: true}),
// This custom validation logic is required notably because Joi does not
// accept paths like /a/b/c ...
Joi.custom((val, helpers) => {
Joi.custom((val: unknown, helpers) => {
try {
// eslint-disable-next-line no-new
new URL(val);
new URL(String(val));
return val;
} catch {
return helpers.error('any.invalid');
@ -55,7 +55,7 @@ export const URISchema = Joi.alternatives(
});
export const PathnameSchema = Joi.string()
.custom((val) => {
.custom((val: string) => {
if (!isValidPathname(val)) {
throw new Error();
}

View file

@ -17,8 +17,9 @@ export const NODE_MINOR_VERSION = parseInt(
);
/** Docusaurus core version. */
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
export const DOCUSAURUS_VERSION = require('../package.json').version;
export const DOCUSAURUS_VERSION =
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
(require('../package.json') as {version: string}).version;
/**
* Can be overridden with cli option `--out-dir`. Code should generally use

View file

@ -280,7 +280,10 @@ This can happen if you use special characters in front matter values (try using
}
function unwrapMarkdownLinks(line: string): string {
return line.replace(/\[(?<alt>[^\]]+)\]\([^)]+\)/g, (match, p1) => p1);
return line.replace(
/\[(?<alt>[^\]]+)\]\([^)]+\)/g,
(match, p1: string) => p1,
);
}
function addHeadingId(

View file

@ -53,7 +53,8 @@ function getTransformOptions(isServer: boolean): TransformOptions {
// better to explicitly specify the version so that it can reuse the
// helper better. See https://github.com/babel/babel/issues/10261
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
version: require('@babel/runtime/package.json').version,
version: (require('@babel/runtime/package.json') as {version: string})
.version,
regenerator: true,
useESModules: true,
// Undocumented option that lets us encapsulate our runtime, ensuring

View file

@ -70,7 +70,7 @@ class PendingNavigation extends React.Component<Props, State> {
this.routeUpdateCleanupCb?.();
this.setState({nextRouteHasLoaded: true});
})
.catch((e) => console.warn(e));
.catch((e: unknown) => console.warn(e));
return false;
}

View file

@ -19,8 +19,5 @@ import {matchRoutes} from 'react-router-config';
export default function preload(pathname: string): Promise<void[]> {
const matches = matchRoutes(routes, pathname);
return Promise.all(
// @ts-expect-error: ComponentCreator injected this method.
matches.map((match) => match.route.component?.preload?.()),
);
return Promise.all(matches.map((match) => match.route.component.preload?.()));
}

View file

@ -37,7 +37,7 @@ export default function Loading({
maxWidth: '50%',
width: '100%',
}}>
<p>{error.message}</p>
<p>{String(error)}</p>
<div>
<button type="button" onClick={retry}>
Retry

View file

@ -78,7 +78,7 @@ export async function start(
logger.success`Docusaurus website is running at url=${newOpenUrl}.`;
}
})
.catch((err) => {
.catch((err: Error) => {
logger.error(err.stack);
});
}, 500);

View file

@ -14,14 +14,14 @@ import type {SwizzleAction, SwizzleComponentConfig} from '@docusaurus/types';
const ExitTitle = logger.yellow('[Exit]');
export async function askThemeName(themeNames: string[]): Promise<string> {
const {themeName} = await prompts({
const {themeName} = (await prompts({
type: 'select',
name: 'themeName',
message: 'Select a theme to swizzle:',
choices: themeNames
.map((theme) => ({title: theme, value: theme}))
.concat({title: ExitTitle, value: '[Exit]'}),
});
})) as {themeName?: string};
if (!themeName || themeName === '[Exit]') {
process.exit(0);
}
@ -41,7 +41,7 @@ export async function askComponentName(
})}`;
}
const {componentName} = await prompts({
const {componentName} = (await prompts({
type: 'autocomplete',
name: 'componentName',
message: `
@ -58,12 +58,12 @@ ${PartiallySafeHint} = not safe for all swizzle actions
value: compName,
}))
.concat({title: ExitTitle, value: '[Exit]'}),
async suggest(input, choices) {
async suggest(input: string, choices) {
return choices.filter((choice) =>
choice.title.toLowerCase().includes(input.toLowerCase()),
);
},
});
})) as {componentName?: string};
logger.newLine();
if (!componentName || componentName === '[Exit]') {
@ -74,7 +74,7 @@ ${PartiallySafeHint} = not safe for all swizzle actions
}
export async function askSwizzleDangerousComponent(): Promise<boolean> {
const {switchToDanger} = await prompts({
const {switchToDanger} = (await prompts({
type: 'select',
name: 'switchToDanger',
message: `Do you really want to swizzle this unsafe internal component?`,
@ -86,7 +86,7 @@ export async function askSwizzleDangerousComponent(): Promise<boolean> {
},
{title: ExitTitle, value: '[Exit]'},
],
});
})) as {switchToDanger?: boolean | '[Exit]'};
if (typeof switchToDanger === 'undefined' || switchToDanger === '[Exit]') {
return process.exit(0);
@ -98,7 +98,7 @@ export async function askSwizzleDangerousComponent(): Promise<boolean> {
export async function askSwizzleAction(
componentConfig: SwizzleComponentConfig,
): Promise<SwizzleAction> {
const {action} = await prompts({
const {action} = (await prompts({
type: 'select',
name: 'action',
message: `Which swizzle action do you want to do?`,
@ -117,7 +117,7 @@ export async function askSwizzleAction(
},
{title: ExitTitle, value: '[Exit]'},
],
});
})) as {action?: SwizzleAction | '[Exit]'};
if (typeof action === 'undefined' || action === '[Exit]') {
return process.exit(0);

View file

@ -30,7 +30,7 @@ export async function loadSiteConfig({
const importedConfig = importFresh(siteConfigPath);
const loadedConfig =
const loadedConfig: unknown =
typeof importedConfig === 'function'
? await importedConfig()
: await importedConfig;

View file

@ -144,9 +144,9 @@ const I18N_CONFIG_SCHEMA = Joi.object<I18nConfig>({
.optional()
.default(DEFAULT_I18N_CONFIG);
const SiteUrlSchema = URISchema.required().custom((value, helpers) => {
const SiteUrlSchema = URISchema.required().custom((value: unknown, helpers) => {
try {
const {pathname} = new URL(value);
const {pathname} = new URL(String(value));
if (pathname !== '/') {
helpers.warn('docusaurus.configValidationWarning', {
warningMessage: `the url is not supposed to contain a sub-path like '${pathname}', please use the baseUrl field for sub-paths`,
@ -157,7 +157,7 @@ const SiteUrlSchema = URISchema.required().custom((value, helpers) => {
}, 'siteUrlCustomValidation');
// TODO move to @docusaurus/utils-validation
export const ConfigSchema = Joi.object({
export const ConfigSchema = Joi.object<DocusaurusConfig>({
baseUrl: Joi.string()
.required()
.regex(/\/$/m)
@ -237,9 +237,7 @@ export const ConfigSchema = Joi.object({
});
// TODO move to @docusaurus/utils-validation
export function validateConfig(
config: Partial<DocusaurusConfig>,
): DocusaurusConfig {
export function validateConfig(config: unknown): DocusaurusConfig {
const {error, warning, value} = ConfigSchema.validate(config, {
abortEarly: false,
});

View file

@ -70,7 +70,7 @@ async function choosePort(
}
clearConsole();
const existingProcess = getProcessForPort(defaultPort);
const {shouldChangePort} = await prompts({
const {shouldChangePort} = (await prompts({
type: 'confirm',
name: 'shouldChangePort',
message: logger.yellow(`${logger.bold('[WARNING]')} ${message}${
@ -79,7 +79,7 @@ async function choosePort(
Would you like to run the app on another port instead?`),
initial: true,
});
})) as {shouldChangePort: boolean};
return shouldChangePort ? port : null;
} catch (err) {
logger.error`Could not find an open port at ${host}.`;

View file

@ -20,7 +20,7 @@ async function getPackageJsonVersion(
): Promise<string | undefined> {
if (await fs.pathExists(packageJsonPath)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
return require(packageJsonPath).version;
return (require(packageJsonPath) as {version?: string}).version;
}
return undefined;
}
@ -29,7 +29,7 @@ async function getPackageJsonName(
packageJsonPath: string,
): Promise<string | undefined> {
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
return require(packageJsonPath).name;
return (require(packageJsonPath) as {name?: string}).name;
}
export async function getPluginVersion(

View file

@ -58,7 +58,7 @@ async function readTranslationFileContent(
): Promise<TranslationFileContent | undefined> {
if (await fs.pathExists(filePath)) {
try {
const content = await fs.readJSON(filePath);
const content: unknown = await fs.readJSON(filePath);
ensureTranslationFileContent(content);
return content;
} catch (err) {

View file

@ -347,10 +347,12 @@ ${sourceWarningPart(path.node)}`,
firstArgEvaluated.confident &&
typeof firstArgEvaluated.value === 'object'
) {
const {message, id, description} = firstArgEvaluated.value;
translations[id ?? message] = {
message: message ?? id,
...(description && {description}),
const {message, id, description} = firstArgEvaluated.value as {
[propName: string]: unknown;
};
translations[String(id ?? message)] = {
message: String(message ?? id),
...(Boolean(description) && {description: String(description)}),
};
} else {
warnings.push(

View file

@ -27,6 +27,7 @@
"node": ">=14"
},
"devDependencies": {
"@types/file-loader": "^5.0.1",
"@types/sharp": "^0.30.2"
}
}

View file

@ -38,7 +38,7 @@ export default async function lqipLoader(
} else {
if (!contentIsFileExport) {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const fileLoader = require('file-loader');
const fileLoader = require('file-loader') as typeof import('file-loader');
content = fileLoader.call(this, contentBuffer);
}
source = content.match(

View file

@ -9,8 +9,8 @@ import path from 'path';
import logger from '@docusaurus/logger';
import sharp from 'sharp';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const {version} = require('../package.json');
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
const {version} = require('../package.json') as {version: string};
const ERROR_EXT = `Error: Input file is missing or uses unsupported image format, lqip v${version}`;

View file

@ -27,11 +27,13 @@ export function VersionsProvider({
useEffect(() => {
fetch('https://registry.npmjs.org/@docusaurus/core')
.then((res) => res.json())
.then((data) => {
const name = Object.keys(data.versions).at(-1)!;
const time = data.time[name];
setCanaryVersion({name, time});
});
.then(
(data: {versions: string[]; time: {[versionName: string]: string}}) => {
const name = Object.keys(data.versions).at(-1)!;
const time = data.time[name];
setCanaryVersion({name, time});
},
);
}, []);
return <Context.Provider value={canaryVersion}>{children}</Context.Provider>;
}

View file

@ -3308,6 +3308,13 @@
"@types/qs" "*"
"@types/serve-static" "*"
"@types/file-loader@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@types/file-loader/-/file-loader-5.0.1.tgz#300b7c729e4f0c523783b865b287e3459135bf22"
integrity sha512-FHPPuRb/Ts/25qvNU/mQGwRZUp793nBxYqXd/KwApykxATagqrO4+2EEcGDm/DuXyV/EkOa04umS1DQ8tQSomg==
dependencies:
"@types/webpack" "^4"
"@types/fs-extra@^9.0.13":
version "9.0.13"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45"