diff --git a/docs/Caching.md b/docs/Caching.md
index 9275efe5eb..bfda4f658e 100644
--- a/docs/Caching.md
+++ b/docs/Caching.md
@@ -20,7 +20,7 @@ The main option for configuring the Metro cache is [`cacheStores`](./Configurati
Metro provides a number of built-in cache store implementations for use with the [`cacheStores`](./Configuration.md#cachestores) config option:
* **`FileStore({root: string})`** will store cache entries as files under the directory specified by `root`.
-* **`AutoCleanFileStore()`** is a `FileStore` that periodically cleans up old entries. It accepts the same options as `FileStore` plus the following:
+* **`AutoCleanFileStore()`**
Deprecated
is a `FileStore` that periodically cleans up old entries. It accepts the same options as `FileStore` plus the following:
* **`options.intervalMs: number`** is the time in milliseconds between cleanup attempts. Defaults to 10 minutes.
* **`options.cleanupThresholdMs: number`** is the minimum time in milliseconds since the last modification of an entry before it can be deleted. Defaults to 3 days.
* **`HttpStore(options)`** is a bare-bones remote cache client that reads (`GET`) and writes (`PUT`) compressed cache artifacts over HTTP or HTTPS.
diff --git a/packages/metro-cache/package.json b/packages/metro-cache/package.json
index de252800a7..1f1101dd47 100644
--- a/packages/metro-cache/package.json
+++ b/packages/metro-cache/package.json
@@ -23,7 +23,7 @@
"metro-core": "0.83.1"
},
"devDependencies": {
- "metro-memory-fs": "*"
+ "memfs": "^4.38.2"
},
"license": "MIT",
"engines": {
diff --git a/packages/metro-cache/src/stores/AutoCleanFileStore.js b/packages/metro-cache/src/stores/AutoCleanFileStore.js
index effde7df9b..cf064113ef 100644
--- a/packages/metro-cache/src/stores/AutoCleanFileStore.js
+++ b/packages/metro-cache/src/stores/AutoCleanFileStore.js
@@ -4,8 +4,8 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
+ * @flow strict-local
* @format
- * @flow
*/
import type {Options} from './FileStore';
@@ -14,90 +14,72 @@ import FileStore from './FileStore';
import fs from 'fs';
import path from 'path';
-type CleanOptions = {
+type CleanOptions = $ReadOnly<{
...Options,
intervalMs?: number,
cleanupThresholdMs?: number,
- ...
-};
-
-type FileList = {
- path: string,
- stats: fs.Stats,
- ...
-};
-
-// List all files in a directory in Node.js recursively in a synchronous fashion
-const walkSync = function (
- dir: string,
- filelist: Array,
-): Array {
- const files = fs.readdirSync(dir);
- filelist = filelist || [];
- files.forEach(function (file) {
- const fullPath = path.join(dir, file);
- const stats = fs.statSync(fullPath);
- if (stats.isDirectory()) {
- filelist = walkSync(fullPath + path.sep, filelist);
- } else {
- filelist.push({path: fullPath, stats});
- }
- });
- return filelist;
-};
-
-function get(property: ?T, defaultValue: T): T {
- if (property == null) {
- return defaultValue;
- }
-
- return property;
-}
+}>;
/**
- * A FileStore that cleans itself up in a given interval
+ * A FileStore that, at a given interval, stats the content of the cache root
+ * and deletes any file last modified a set threshold in the past.
+ *
+ * @deprecated This is not efficiently implemented and may cause significant
+ * redundant I/O when caches are large. Prefer your own cleanup scripts, or a
+ * custom Metro cache that uses watches, hooks get/set, and/or implements LRU.
*/
export default class AutoCleanFileStore extends FileStore {
- _intervalMs: number;
- _cleanupThresholdMs: number;
- _root: string;
+ +#intervalMs: number;
+ +#cleanupThresholdMs: number;
+ +#root: string;
constructor(opts: CleanOptions) {
super({root: opts.root});
- this._intervalMs = get(opts.intervalMs, 10 * 60 * 1000); // 10 minutes
- this._cleanupThresholdMs = get(
- opts.cleanupThresholdMs,
- 3 * 24 * 60 * 60 * 1000, // 3 days
- );
+ this.#root = opts.root;
+ this.#intervalMs = opts.intervalMs ?? 10 * 60 * 1000; // 10 minutes
+ this.#cleanupThresholdMs =
+ opts.cleanupThresholdMs ?? 3 * 24 * 60 * 60 * 1000; // 3 days
- this._scheduleCleanup();
+ this.#scheduleCleanup();
}
- _scheduleCleanup() {
- // $FlowFixMe[method-unbinding] added when improving typing for this parameters
- setTimeout(this._doCleanup.bind(this), this._intervalMs);
+ #scheduleCleanup() {
+ setTimeout(() => this.#doCleanup(), this.#intervalMs);
}
- _doCleanup() {
- const files = walkSync(this._root, []);
+ #doCleanup() {
+ const dirents = fs.readdirSync(this.#root, {
+ recursive: true,
+ withFileTypes: true,
+ });
let warned = false;
- files.forEach(file => {
- if (file.stats.mtimeMs < Date.now() - this._cleanupThresholdMs) {
+ const minModifiedTime = Date.now() - this.#cleanupThresholdMs;
+ dirents
+ .filter(dirent => dirent.isFile())
+ .forEach(dirent => {
+ const absolutePath = path.join(
+ // $FlowFixMe[prop-missing] - dirent.parentPath added in Node 20.12
+ dirent.parentPath,
+ dirent.name.toString(),
+ );
try {
- fs.unlinkSync(file.path);
+ if (fs.statSync(absolutePath).mtimeMs < minModifiedTime) {
+ fs.unlinkSync(absolutePath);
+ }
} catch (e) {
if (!warned) {
console.warn(
- 'Problem cleaning up cache for ' + file.path + ': ' + e.message,
+ 'Problem cleaning up cache for ' +
+ absolutePath +
+ ': ' +
+ e.message,
);
warned = true;
}
}
- }
- });
-
- this._scheduleCleanup();
+ });
+ this.#scheduleCleanup();
}
}
diff --git a/packages/metro-cache/src/stores/FileStore.js b/packages/metro-cache/src/stores/FileStore.js
index fc37b558da..18978c7335 100644
--- a/packages/metro-cache/src/stores/FileStore.js
+++ b/packages/metro-cache/src/stores/FileStore.js
@@ -4,8 +4,8 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @format
* @flow
+ * @format
*/
import fs from 'fs';
@@ -14,20 +14,20 @@ import path from 'path';
const NULL_BYTE = 0x00;
const NULL_BYTE_BUFFER = Buffer.from([NULL_BYTE]);
-export type Options = {
+export type Options = $ReadOnly<{
root: string,
-};
+}>;
export default class FileStore {
- _root: string;
+ +#root: string;
constructor(options: Options) {
- this._root = options.root;
+ this.#root = options.root;
}
async get(key: Buffer): Promise {
try {
- const data = await fs.promises.readFile(this._getFilePath(key));
+ const data = await fs.promises.readFile(this.#getFilePath(key));
if (data[0] === NULL_BYTE) {
return (data.slice(1): any);
@@ -44,20 +44,20 @@ export default class FileStore {
}
async set(key: Buffer, value: T): Promise {
- const filePath = this._getFilePath(key);
+ const filePath = this.#getFilePath(key);
try {
- await this._set(filePath, value);
+ await this.#set(filePath, value);
} catch (err) {
if (err.code === 'ENOENT') {
fs.mkdirSync(path.dirname(filePath), {recursive: true});
- await this._set(filePath, value);
+ await this.#set(filePath, value);
} else {
throw err;
}
}
}
- async _set(filePath: string, value: T): Promise {
+ async #set(filePath: string, value: T): Promise {
let content;
if (value instanceof Buffer) {
content = Buffer.concat([NULL_BYTE_BUFFER, value]);
@@ -68,20 +68,20 @@ export default class FileStore {
}
clear() {
- this._removeDirs();
+ this.#removeDirs();
}
- _getFilePath(key: Buffer): string {
+ #getFilePath(key: Buffer): string {
return path.join(
- this._root,
+ this.#root,
key.slice(0, 1).toString('hex'),
key.slice(1).toString('hex'),
);
}
- _removeDirs() {
+ #removeDirs() {
for (let i = 0; i < 256; i++) {
- fs.rmSync(path.join(this._root, ('0' + i.toString(16)).slice(-2)), {
+ fs.rmSync(path.join(this.#root, ('0' + i.toString(16)).slice(-2)), {
force: true,
recursive: true,
});
diff --git a/packages/metro-cache/src/stores/__tests__/AutoCleanFileStore-test.js b/packages/metro-cache/src/stores/__tests__/AutoCleanFileStore-test.js
index af314226b8..c4a9380ee2 100644
--- a/packages/metro-cache/src/stores/__tests__/AutoCleanFileStore-test.js
+++ b/packages/metro-cache/src/stores/__tests__/AutoCleanFileStore-test.js
@@ -9,7 +9,7 @@
* @oncall react_native
*/
-'use strict';
+import {memfs} from 'memfs';
describe('AutoCleanFileStore', () => {
let AutoCleanFileStore;
@@ -19,22 +19,23 @@ describe('AutoCleanFileStore', () => {
jest
.resetModules()
.resetAllMocks()
- .mock('fs', () => new (require('metro-memory-fs'))());
-
+ .mock('fs', () => memfs().fs);
AutoCleanFileStore = require('../AutoCleanFileStore').default;
fs = require('fs');
+ jest.spyOn(fs, 'statSync');
jest.spyOn(fs, 'unlinkSync');
});
test('sets and writes into the cache', async () => {
- // $FlowFixMe[underconstrained-implicit-instantiation]
- const fileStore = new AutoCleanFileStore({
+ const fileStore = new AutoCleanFileStore({
root: '/root',
intervalMs: 49,
- cleanupThresholdMs: 0,
+ cleanupThresholdMs: 90,
});
const cache = Buffer.from([0xfa, 0xce, 0xb0, 0x0c]);
+ expect(fs.statSync).toHaveBeenCalledTimes(0);
+
await fileStore.set(cache, {foo: 42});
expect(await fileStore.get(cache)).toEqual({foo: 42});
@@ -43,17 +44,28 @@ describe('AutoCleanFileStore', () => {
expect(await fileStore.get(cache)).toEqual({foo: 42});
+ // And there should have been no cleanup
+ expect(fs.statSync).not.toHaveBeenCalled();
+
// Run to 50ms so that we've exceeded the 49ms cleanup interval
jest.advanceTimersByTime(20);
- // mtime doesn't work very well in in-memory-store, so we couldn't test that
- // functionality
+ expect(fs.statSync).toHaveBeenCalledTimes(1);
+
+ // At 50ms we should have checked the file, but it's still fresh enough
+ expect(await fileStore.get(cache)).toEqual({foo: 42});
+ expect(fs.unlinkSync).not.toHaveBeenCalled();
+
+ jest.advanceTimersByTime(50);
+
+ // After another 50ms, we should have checked the file again and deleted it
+ expect(fs.statSync).toHaveBeenCalledTimes(2);
+ expect(fs.unlinkSync).toHaveBeenCalledTimes(1);
expect(await fileStore.get(cache)).toEqual(null);
});
test('returns null when reading a non-existing file', async () => {
- // $FlowFixMe[underconstrained-implicit-instantiation]
- const fileStore = new AutoCleanFileStore({root: '/root'});
+ const fileStore = new AutoCleanFileStore({root: '/root'});
const cache = Buffer.from([0xfa, 0xce, 0xb0, 0x0c]);
expect(await fileStore.get(cache)).toEqual(null);
diff --git a/packages/metro-cache/src/stores/__tests__/FileStore-test.js b/packages/metro-cache/src/stores/__tests__/FileStore-test.js
index 027e7d3eb4..5f7a70a420 100644
--- a/packages/metro-cache/src/stores/__tests__/FileStore-test.js
+++ b/packages/metro-cache/src/stores/__tests__/FileStore-test.js
@@ -4,13 +4,12 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
+ * @flow strict-local
* @format
* @oncall react_native
*/
-'use strict';
-
-const {dirname} = require('path');
+import {memfs} from 'memfs';
describe('FileStore', () => {
let FileStore;
@@ -20,7 +19,7 @@ describe('FileStore', () => {
jest
.resetModules()
.resetAllMocks()
- .mock('fs', () => new (require('metro-memory-fs'))());
+ .mock('fs', () => memfs().fs);
FileStore = require('../FileStore').default;
fs = require('fs');
@@ -28,7 +27,7 @@ describe('FileStore', () => {
});
test('sets and writes into the cache', async () => {
- const fileStore = new FileStore({root: '/root'});
+ const fileStore = new FileStore({root: '/root'});
const cache = Buffer.from([0xfa, 0xce, 0xb0, 0x0c]);
await fileStore.set(cache, {foo: 42});
@@ -36,23 +35,22 @@ describe('FileStore', () => {
});
test('returns null when reading a non-existing file', async () => {
- const fileStore = new FileStore({root: '/root'});
+ const fileStore = new FileStore({root: '/root'});
const cache = Buffer.from([0xfa, 0xce, 0xb0, 0x0c]);
expect(await fileStore.get(cache)).toEqual(null);
});
test('returns null when reading a empty file', async () => {
- const fileStore = new FileStore({root: '/root'});
+ const fileStore = new FileStore({root: '/root'});
const cache = Buffer.from([0xfa, 0xce, 0xb0, 0x0c]);
- const filePath = fileStore._getFilePath(cache);
- fs.mkdirSync(dirname(filePath), {recursive: true});
- fs.writeFileSync(filePath, '');
+ jest.spyOn(fs.promises, 'readFile').mockImplementation(async () => '');
expect(await fileStore.get(cache)).toEqual(null);
+ expect(fs.promises.readFile).toHaveBeenCalledWith(expect.any(String));
});
test('writes into cache if folder is missing', async () => {
- const fileStore = new FileStore({root: '/root'});
+ const fileStore = new FileStore({root: '/root'});
const cache = Buffer.from([0xfa, 0xce, 0xb0, 0x0c]);
const data = Buffer.from([0xca, 0xc4, 0xe5]);
@@ -62,7 +60,7 @@ describe('FileStore', () => {
});
test('reads and writes binary data', async () => {
- const fileStore = new FileStore({root: '/root'});
+ const fileStore = new FileStore({root: '/root'});
const cache = Buffer.from([0xfa, 0xce, 0xb0, 0x0c]);
const data = Buffer.from([0xca, 0xc4, 0xe5]);
diff --git a/packages/metro-cache/types/stores/AutoCleanFileStore.d.ts b/packages/metro-cache/types/stores/AutoCleanFileStore.d.ts
index 693b81a6f4..2d78a5c5d7 100644
--- a/packages/metro-cache/types/stores/AutoCleanFileStore.d.ts
+++ b/packages/metro-cache/types/stores/AutoCleanFileStore.d.ts
@@ -10,4 +10,12 @@
import type FileStore from './FileStore';
+/**
+ * A FileStore that, at a given interval, stats the content of the cache root
+ * and deletes any file last modified a set threshold in the past.
+ *
+ * @deprecated This is not efficiently implemented and may cause significant
+ * redundant I/O when caches are large. Prefer your own cleanup scripts, or a
+ * custom Metro cache that uses watches, hooks get/set, and/or implements LRU.
+ */
export default class AutoCleanFileStore extends FileStore {}
diff --git a/scripts/jestFilter.js b/scripts/jestFilter.js
index 1e295fcce7..bd3de0aef9 100644
--- a/scripts/jestFilter.js
+++ b/scripts/jestFilter.js
@@ -26,7 +26,6 @@ const BROKEN_ON_WINDOWS = [
'packages/metro-file-map/src/crawlers/__tests__/node-test.js',
// resolveModulePath failed
- 'packages/metro-cache/src/stores/__tests__/FileStore-test.js',
'packages/metro-resolver/src/__tests__/assets-test.js',
'packages/metro-resolver/src/__tests__/platform-extensions-test.js',
'packages/metro-resolver/src/__tests__/symlinks-test.js',
diff --git a/yarn.lock b/yarn.lock
index 3e7f8dd099..7abf2311d8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1112,6 +1112,50 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
+"@jsonjoy.com/base64@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578"
+ integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==
+
+"@jsonjoy.com/buffers@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-1.0.0.tgz#ade6895b7d3883d70f87b5743efaa12c71dfef7a"
+ integrity sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q==
+
+"@jsonjoy.com/codegen@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz#5c23f796c47675f166d23b948cdb889184b93207"
+ integrity sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==
+
+"@jsonjoy.com/json-pack@^1.11.0":
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.11.0.tgz#3d40d3d8042f5e9eeb005658a76b788e8ca84ac0"
+ integrity sha512-nLqSTAYwpk+5ZQIoVp7pfd/oSKNWlEdvTq2LzVA4r2wtWZg6v+5u0VgBOaDJuUfNOuw/4Ysq6glN5QKSrOCgrA==
+ dependencies:
+ "@jsonjoy.com/base64" "^1.1.2"
+ "@jsonjoy.com/buffers" "^1.0.0"
+ "@jsonjoy.com/codegen" "^1.0.0"
+ "@jsonjoy.com/json-pointer" "^1.0.1"
+ "@jsonjoy.com/util" "^1.9.0"
+ hyperdyperid "^1.2.0"
+ thingies "^2.5.0"
+
+"@jsonjoy.com/json-pointer@^1.0.1":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz#049cb530ac24e84cba08590c5e36b431c4843408"
+ integrity sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==
+ dependencies:
+ "@jsonjoy.com/codegen" "^1.0.0"
+ "@jsonjoy.com/util" "^1.9.0"
+
+"@jsonjoy.com/util@^1.9.0":
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.9.0.tgz#7ee95586aed0a766b746cd8d8363e336c3c47c46"
+ integrity sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==
+ dependencies:
+ "@jsonjoy.com/buffers" "^1.0.0"
+ "@jsonjoy.com/codegen" "^1.0.0"
+
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -2940,6 +2984,11 @@ glob-parent@^6.0.2:
dependencies:
is-glob "^4.0.3"
+glob-to-regex.js@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regex.js/-/glob-to-regex.js-1.0.1.tgz#f71cc9cb8441471a9318626160bc8a35e1306b21"
+ integrity sha512-CG/iEvgQqfzoVsMUbxSJcwbG2JwyZ3naEqPkeltwl0BSS8Bp83k3xlGms+0QdWFUAwV+uvo80wNswKF6FWEkKg==
+
glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@@ -3104,6 +3153,11 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+hyperdyperid@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b"
+ integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==
+
ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
@@ -4194,6 +4248,18 @@ makeerror@1.0.x:
dependencies:
tmpl "1.0.x"
+memfs@^4.38.2:
+ version "4.38.2"
+ resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.38.2.tgz#e3a3a0362032c3ab7093cc7c179bd5fa8abc94c3"
+ integrity sha512-FpWsVHpAkoSh/LfY1BgAl72BVd374ooMRtDi2VqzBycX4XEfvC0XKACCe0C9VRZoYq5viuoyTv6lYXZ/Q7TrLQ==
+ dependencies:
+ "@jsonjoy.com/json-pack" "^1.11.0"
+ "@jsonjoy.com/util" "^1.9.0"
+ glob-to-regex.js "^1.0.1"
+ thingies "^2.5.0"
+ tree-dump "^1.0.3"
+ tslib "^2.0.0"
+
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -5206,6 +5272,11 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
+thingies@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/thingies/-/thingies-2.5.0.tgz#5f7b882c933b85989f8466b528a6247a6881e04f"
+ integrity sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==
+
throat@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
@@ -5238,6 +5309,11 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
+tree-dump@^1.0.3:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.1.0.tgz#ab29129169dc46004414f5a9d4a3c6e89f13e8a4"
+ integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==
+
ts-api-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91"
@@ -5253,6 +5329,11 @@ tsconfig-paths@^3.14.1:
minimist "^1.2.6"
strip-bom "^3.0.0"
+tslib@^2.0.0:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
+ integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
tslib@^2.0.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410"