From 9e5b19cdd16b10974ca8b95f340a45f7b286bc45 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 26 Oct 2023 16:15:27 -0600 Subject: [PATCH] Add file caching utility function One of the things that this tool will do is to hit the GitHub API and request all of the repositories under the MetaMask organization. We don't want to do this every time the tool is run, though, or else we might get rate limited. To prevent this, we can cache the response data from the API in a file. This `fetchOrPopulateFileCache` function being introduced here (which we will use in a later commit) makes that possible. --- .eslintrc.js | 5 +- jest.config.js | 2 +- package.json | 3 + src/constants.ts | 4 + src/fetch-or-populate-file-cache.test.ts | 261 +++++++++++++++++++++++ src/fetch-or-populate-file-cache.ts | 77 +++++++ src/index.test.ts | 9 - src/index.ts | 9 - src/logging-utils.ts | 5 + tests/helpers.ts | 33 +++ tsconfig.json | 3 + yarn.lock | 193 ++++++++++++++++- 12 files changed, 577 insertions(+), 27 deletions(-) create mode 100644 src/constants.ts create mode 100644 src/fetch-or-populate-file-cache.test.ts create mode 100644 src/fetch-or-populate-file-cache.ts delete mode 100644 src/index.test.ts delete mode 100644 src/index.ts create mode 100644 src/logging-utils.ts create mode 100644 tests/helpers.ts diff --git a/.eslintrc.js b/.eslintrc.js index dc7fd43..497457a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,10 @@ module.exports = { overrides: [ { files: ['*.ts'], - extends: ['@metamask/eslint-config-typescript'], + extends: [ + '@metamask/eslint-config-typescript', + '@metamask/eslint-config-nodejs', + ], }, { diff --git a/jest.config.js b/jest.config.js index 83e1504..8d608e8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -22,7 +22,7 @@ module.exports = { collectCoverage: true, // An array of glob patterns indicating a set of files for which coverage information should be collected - collectCoverageFrom: ['./src/**/*.ts'], + collectCoverageFrom: ['./src/**/*.ts', '!./src/logging-utils.ts'], // The directory where Jest should output its coverage files coverageDirectory: 'coverage', diff --git a/package.json b/package.json index 9b07e70..82bf43b 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,9 @@ "test": "jest && jest-it-up", "test:watch": "jest --watch" }, + "dependencies": { + "@metamask/utils": "^8.2.0" + }, "devDependencies": { "@lavamoat/allow-scripts": "^2.3.1", "@lavamoat/preinstall-always-fail": "^1.0.0", diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..4fd8c38 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,4 @@ +/** + * The number of milliseconds in an hour, used as a cache age. + */ +export const ONE_HOUR = 60 * 60 * 1000; diff --git a/src/fetch-or-populate-file-cache.test.ts b/src/fetch-or-populate-file-cache.test.ts new file mode 100644 index 0000000..e10b8d6 --- /dev/null +++ b/src/fetch-or-populate-file-cache.test.ts @@ -0,0 +1,261 @@ +import { readJsonFile, writeJsonFile } from '@metamask/utils/node'; +import path from 'path'; + +import { fetchOrPopulateFileCache } from './fetch-or-populate-file-cache'; +import { withinSandbox, fakeDateOnly } from '../tests/helpers'; + +describe('fetchOrPopulateFileCache', () => { + beforeEach(() => { + fakeDateOnly(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + describe('if the given file does not already exist', () => { + it('saves the return value of the given function in the file as JSON along with its created time', async () => { + jest.setSystemTime(new Date('2023-01-01T00:00:00Z')); + + await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { + const filePath = path.join(sandboxDirectoryPath, 'cache'); + const data = { foo: 'bar' }; + + await fetchOrPopulateFileCache({ + filePath, + getDataToCache: () => data, + }); + + const cache = await readJsonFile(filePath); + expect(cache).toStrictEqual({ + ctime: '2023-01-01T00:00:00.000Z', + data, + }); + }); + }); + + it('returns the data that was cached', async () => { + await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { + const filePath = path.join(sandboxDirectoryPath, 'cache'); + const dataToCache = { foo: 'bar' }; + + const cachedData = await fetchOrPopulateFileCache({ + filePath, + getDataToCache: () => dataToCache, + }); + + expect(cachedData).toStrictEqual(dataToCache); + }); + }); + }); + + describe('if the given file already exists', () => { + describe('and no explicit max age is given', () => { + describe('and it was created less than an hour ago', () => { + it('does not overwrite the cache', async () => { + jest.setSystemTime(new Date('2023-01-01T00:00:00Z')); + + await withinSandbox( + async ({ directoryPath: sandboxDirectoryPath }) => { + const filePath = path.join(sandboxDirectoryPath, 'cache'); + const data = { foo: 'bar' }; + await writeJsonFile(filePath, { + ctime: new Date('2023-01-01T00:30:00Z').toISOString(), + data, + }); + + await fetchOrPopulateFileCache({ + filePath, + getDataToCache: () => data, + }); + + const cache = await readJsonFile(filePath); + expect(cache).toStrictEqual({ + ctime: '2023-01-01T00:30:00.000Z', + data, + }); + }, + ); + }); + + it('returns the data in the file', async () => { + jest.setSystemTime(new Date('2023-01-01T00:00:00Z')); + + await withinSandbox( + async ({ directoryPath: sandboxDirectoryPath }) => { + const filePath = path.join(sandboxDirectoryPath, 'cache'); + const data = { foo: 'bar' }; + await writeJsonFile(filePath, { + ctime: new Date('2023-01-01T00:30:00Z').toISOString(), + data, + }); + + const cachedData = await fetchOrPopulateFileCache({ + filePath, + getDataToCache: () => data, + }); + + expect(cachedData).toStrictEqual(data); + }, + ); + }); + }); + + describe('and it was created more than an hour ago', () => { + it('overwrites the cache', async () => { + jest.setSystemTime(new Date('2023-01-01T00:00:00Z')); + + await withinSandbox( + async ({ directoryPath: sandboxDirectoryPath }) => { + const filePath = path.join(sandboxDirectoryPath, 'cache'); + const dataToCache = { foo: 'bar' }; + await writeJsonFile(filePath, { + ctime: new Date('2023-01-01T01:00:01Z').toISOString(), + data: dataToCache, + }); + + await fetchOrPopulateFileCache({ + filePath, + getDataToCache: () => dataToCache, + }); + + const cache = await readJsonFile(filePath); + expect(cache).toStrictEqual({ + ctime: '2023-01-01T01:00:01.000Z', + data: dataToCache, + }); + }, + ); + }); + + it('returns the data in the file', async () => { + jest.setSystemTime(new Date('2023-01-01T00:00:00Z')); + + await withinSandbox( + async ({ directoryPath: sandboxDirectoryPath }) => { + const filePath = path.join(sandboxDirectoryPath, 'cache'); + const dataToCache = { foo: 'bar' }; + await writeJsonFile(filePath, { + ctime: new Date('2023-01-01T01:00:01Z').toISOString(), + data: dataToCache, + }); + + const cachedData = await fetchOrPopulateFileCache({ + filePath, + getDataToCache: () => dataToCache, + }); + + expect(cachedData).toStrictEqual(dataToCache); + }, + ); + }); + }); + }); + + describe('and a max age is given', () => { + describe('and it was created less than seconds ago', () => { + it('does not overwrite the cache', async () => { + jest.setSystemTime(new Date('2023-01-01T00:00:00Z')); + + await withinSandbox( + async ({ directoryPath: sandboxDirectoryPath }) => { + const filePath = path.join(sandboxDirectoryPath, 'cache'); + const data = { foo: 'bar' }; + await writeJsonFile(filePath, { + ctime: new Date('2023-01-01T00:00:04Z').toISOString(), + data, + }); + + await fetchOrPopulateFileCache({ + filePath, + getDataToCache: () => data, + maxAge: 5000, + }); + + const cache = await readJsonFile(filePath); + expect(cache).toStrictEqual({ + ctime: '2023-01-01T00:00:04.000Z', + data, + }); + }, + ); + }); + + it('returns the data in the file', async () => { + jest.setSystemTime(new Date('2023-01-01T00:00:00Z')); + + await withinSandbox( + async ({ directoryPath: sandboxDirectoryPath }) => { + const filePath = path.join(sandboxDirectoryPath, 'cache'); + const data = { foo: 'bar' }; + await writeJsonFile(filePath, { + ctime: new Date('2023-01-01T00:00:04Z').toISOString(), + data, + }); + + const cachedData = await fetchOrPopulateFileCache({ + filePath, + getDataToCache: () => data, + maxAge: 5000, + }); + + expect(cachedData).toStrictEqual(data); + }, + ); + }); + }); + + describe('and it was created more than an hour ago', () => { + it('overwrites the cache', async () => { + jest.setSystemTime(new Date('2023-01-01T00:00:00Z')); + + await withinSandbox( + async ({ directoryPath: sandboxDirectoryPath }) => { + const filePath = path.join(sandboxDirectoryPath, 'cache'); + const dataToCache = { foo: 'bar' }; + await writeJsonFile(filePath, { + ctime: new Date('2023-01-01T00:00:06Z').toISOString(), + data: dataToCache, + }); + + await fetchOrPopulateFileCache({ + filePath, + getDataToCache: () => dataToCache, + maxAge: 5000, + }); + + const cache = await readJsonFile(filePath); + expect(cache).toStrictEqual({ + ctime: '2023-01-01T00:00:06.000Z', + data: dataToCache, + }); + }, + ); + }); + + it('returns the data in the file', async () => { + jest.setSystemTime(new Date('2023-01-01T00:00:00Z')); + + await withinSandbox( + async ({ directoryPath: sandboxDirectoryPath }) => { + const filePath = path.join(sandboxDirectoryPath, 'cache'); + const dataToCache = { foo: 'bar' }; + await writeJsonFile(filePath, { + ctime: new Date('2023-01-01T00:00:06Z').toISOString(), + data: dataToCache, + }); + + const cachedData = await fetchOrPopulateFileCache({ + filePath, + getDataToCache: () => dataToCache, + maxAge: 5000, + }); + + expect(cachedData).toStrictEqual(dataToCache); + }, + ); + }); + }); + }); + }); +}); diff --git a/src/fetch-or-populate-file-cache.ts b/src/fetch-or-populate-file-cache.ts new file mode 100644 index 0000000..c9b7ad9 --- /dev/null +++ b/src/fetch-or-populate-file-cache.ts @@ -0,0 +1,77 @@ +import { fileExists, readJsonFile, writeJsonFile } from '@metamask/utils/node'; +import type { Json } from '@metamask/utils/node'; + +import { ONE_HOUR } from './constants'; +import { createModuleLogger, projectLogger } from './logging-utils'; + +const log = createModuleLogger(projectLogger, 'fetch-or-populate-file-cache'); + +/** + * The data stored in the cache file. + */ +type FileCache = { + /** + * When the data was stored. + */ + ctime: string; + /** + * The cached data. + */ + data: Data; +}; + +/** + * How long to cache data retrieved from an API (to prevent rate limiting). + * + * Equal to 1 hour. + */ +const DEFAULT_MAX_AGE = ONE_HOUR; + +/** + * Avoids rate limits when making requests to an API by consulting a file cache. + * + * Reads the given cache file and returns the data within it if it exists and is + * fresh enough; otherwise runs the given function and saves its return value to + * the file. + * + * @param args - The arguments to this function. + * @param args.filePath - The path to the file where the data should be saved. + * @param args.getDataToCache - A function to get the data that should be cached + * if the cache does not exist or is stale. + * @param args.maxAge - The amount of time (in milliseconds) that the cache is + * considered "fresh". Affects subsequent calls: if `fetchOrPopulateFileCache` + * is called again with the same file path within the duration specified here, + * `getDataToCache` will not get called again, otherwise it will. Defaults to 1 + * hour. + */ +export async function fetchOrPopulateFileCache({ + filePath, + maxAge = DEFAULT_MAX_AGE, + getDataToCache, +}: { + filePath: string; + maxAge?: number; + getDataToCache: () => Data | Promise; +}): Promise { + const now = new Date(); + + if (await fileExists(filePath)) { + const cache = await readJsonFile>(filePath); + const createdDate = new Date(cache.ctime); + + if (now.getTime() - createdDate.getTime() <= maxAge) { + log(`Reusing fresh cached data under ${filePath}`); + return cache.data; + } + } + + log( + `Cache does not exist or is stale; preparing data to write to ${filePath}`, + ); + const dataToCache = await getDataToCache(); + await writeJsonFile(filePath, { + ctime: now.toISOString(), + data: dataToCache, + }); + return dataToCache; +} diff --git a/src/index.test.ts b/src/index.test.ts deleted file mode 100644 index bc062d3..0000000 --- a/src/index.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import greeter from '.'; - -describe('Test', () => { - it('greets', () => { - const name = 'Huey'; - const result = greeter(name); - expect(result).toBe('Hello, Huey!'); - }); -}); diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 6972c11..0000000 --- a/src/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Example function that returns a greeting for the given name. - * - * @param name - The name to greet. - * @returns The greeting. - */ -export default function greeter(name: string): string { - return `Hello, ${name}!`; -} diff --git a/src/logging-utils.ts b/src/logging-utils.ts new file mode 100644 index 0000000..f5d3dbe --- /dev/null +++ b/src/logging-utils.ts @@ -0,0 +1,5 @@ +import { createProjectLogger, createModuleLogger } from '@metamask/utils/node'; + +export const projectLogger = createProjectLogger('module-lint'); + +export { createModuleLogger }; diff --git a/tests/helpers.ts b/tests/helpers.ts new file mode 100644 index 0000000..2792ec9 --- /dev/null +++ b/tests/helpers.ts @@ -0,0 +1,33 @@ +import { createSandbox } from '@metamask/utils/node'; + +import { createModuleLogger, projectLogger } from '../src/logging-utils'; + +const { withinSandbox } = createSandbox('module-lint-tests'); + +export const log = createModuleLogger(projectLogger, 'tests'); + +export { withinSandbox }; + +/** + * Uses Jest's fake timers to fake Date only. + */ +export function fakeDateOnly() { + jest.useFakeTimers({ + doNotFake: [ + 'hrtime', + 'nextTick', + 'performance', + 'queueMicrotask', + 'requestAnimationFrame', + 'cancelAnimationFrame', + 'requestIdleCallback', + 'cancelIdleCallback', + 'setImmediate', + 'clearImmediate', + 'setInterval', + 'clearInterval', + 'setTimeout', + 'clearTimeout', + ], + }); +} diff --git a/tsconfig.json b/tsconfig.json index 23c8ceb..eb5ceb7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,9 @@ "noEmit": true, "noErrorTruncation": true, "noUncheckedIndexedAccess": true, + "paths": { + "@metamask/utils/node": ["./node_modules/@metamask/utils/dist/types/node"] + }, "strict": true, "target": "es2020" }, diff --git a/yarn.lock b/yarn.lock index ee76a89..00a0742 100644 --- a/yarn.lock +++ b/yarn.lock @@ -482,6 +482,48 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/common@npm:^3.2.0": + version: 3.2.0 + resolution: "@ethereumjs/common@npm:3.2.0" + dependencies: + "@ethereumjs/util": ^8.1.0 + crc-32: ^1.2.0 + checksum: cb9cc11f5c868cb577ba611cebf55046e509218bbb89b47ccce010776dafe8256d70f8f43fab238aec74cf71f62601cd5842bc03a83261200802de365732a14b + languageName: node + linkType: hard + +"@ethereumjs/rlp@npm:^4.0.1": + version: 4.0.1 + resolution: "@ethereumjs/rlp@npm:4.0.1" + bin: + rlp: bin/rlp + checksum: 30db19c78faa2b6ff27275ab767646929207bb207f903f09eb3e4c273ce2738b45f3c82169ddacd67468b4f063d8d96035f2bf36f02b6b7e4d928eefe2e3ecbc + languageName: node + linkType: hard + +"@ethereumjs/tx@npm:^4.2.0": + version: 4.2.0 + resolution: "@ethereumjs/tx@npm:4.2.0" + dependencies: + "@ethereumjs/common": ^3.2.0 + "@ethereumjs/rlp": ^4.0.1 + "@ethereumjs/util": ^8.1.0 + ethereum-cryptography: ^2.0.0 + checksum: 87a3f5f2452cfbf6712f8847525a80c213210ed453c211c793c5df801fe35ecef28bae17fadd222fcbdd94277478a47e52d2b916a90a6b30cda21f1e0cdaee42 + languageName: node + linkType: hard + +"@ethereumjs/util@npm:^8.1.0": + version: 8.1.0 + resolution: "@ethereumjs/util@npm:8.1.0" + dependencies: + "@ethereumjs/rlp": ^4.0.1 + ethereum-cryptography: ^2.0.0 + micro-ftch: ^0.3.1 + checksum: 9ae5dee8f12b0faf81cd83f06a41560e79b0ba96a48262771d897a510ecae605eb6d84f687da001ab8ccffd50f612ae50f988ef76e6312c752897f462f3ac08d + languageName: node + linkType: hard + "@gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -954,6 +996,7 @@ __metadata: "@metamask/eslint-config-jest": ^12.0.0 "@metamask/eslint-config-nodejs": ^12.0.0 "@metamask/eslint-config-typescript": ^12.0.0 + "@metamask/utils": ^8.2.0 "@swc/cli": ^0.1.62 "@swc/core": ^1.3.66 "@types/jest": ^28.1.6 @@ -981,6 +1024,22 @@ __metadata: languageName: unknown linkType: soft +"@metamask/utils@npm:^8.2.0": + version: 8.2.0 + resolution: "@metamask/utils@npm:8.2.0" + dependencies: + "@ethereumjs/tx": ^4.2.0 + "@noble/hashes": ^1.3.1 + "@scure/base": ^1.1.3 + "@types/debug": ^4.1.7 + debug: ^4.3.4 + pony-cause: ^2.1.10 + semver: ^7.5.4 + superstruct: ^1.0.3 + checksum: 1c70c0f9c375bfa3836c15d48990dbea1c3cadfd3dd69b4867667116c09c3bdeef70a0c7027f1cdea88a9913cb846dc94812ece91be7ec32e65a62e00281b04c + languageName: node + linkType: hard + "@mole-inc/bin-wrapper@npm:^8.0.1": version: 8.0.1 resolution: "@mole-inc/bin-wrapper@npm:8.0.1" @@ -997,6 +1056,29 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.1.0, @noble/curves@npm:~1.1.0": + version: 1.1.0 + resolution: "@noble/curves@npm:1.1.0" + dependencies: + "@noble/hashes": 1.3.1 + checksum: 2658cdd3f84f71079b4e3516c47559d22cf4b55c23ac8ee9d2b1f8e5b72916d9689e59820e0f9d9cb4a46a8423af5b56dc6bb7782405c88be06a015180508db5 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.3.1": + version: 1.3.1 + resolution: "@noble/hashes@npm:1.3.1" + checksum: 7fdefc0f7a0c1ec27acc6ff88841793e3f93ec4ce6b8a6a12bfc0dd70ae6b7c4c82fe305fdfeda1735d5ad4a9eebe761e6693b3d355689c559e91242f4bc95b1 + languageName: node + linkType: hard + +"@noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1": + version: 1.3.2 + resolution: "@noble/hashes@npm:1.3.2" + checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -1073,6 +1155,34 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:^1.1.3, @scure/base@npm:~1.1.0": + version: 1.1.3 + resolution: "@scure/base@npm:1.1.3" + checksum: 1606ab8a4db898cb3a1ada16c15437c3bce4e25854fadc8eb03ae93cbbbac1ed90655af4b0be3da37e12056fef11c0374499f69b9e658c9e5b7b3e06353c630c + languageName: node + linkType: hard + +"@scure/bip32@npm:1.3.1": + version: 1.3.1 + resolution: "@scure/bip32@npm:1.3.1" + dependencies: + "@noble/curves": ~1.1.0 + "@noble/hashes": ~1.3.1 + "@scure/base": ~1.1.0 + checksum: 394d65f77a40651eba21a5096da0f4233c3b50d422864751d373fcf142eeedb94a1149f9ab1dbb078086dab2d0bc27e2b1afec8321bf22d4403c7df2fea5bfe2 + languageName: node + linkType: hard + +"@scure/bip39@npm:1.2.1": + version: 1.2.1 + resolution: "@scure/bip39@npm:1.2.1" + dependencies: + "@noble/hashes": ~1.3.0 + "@scure/base": ~1.1.0 + checksum: c5bd6f1328fdbeae2dcdd891825b1610225310e5e62a4942714db51066866e4f7bef242c7b06a1b9dcc8043a4a13412cf5c5df76d3b10aa9e36b82e9b6e3eeaa + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.24.1": version: 0.24.28 resolution: "@sinclair/typebox@npm:0.24.28" @@ -1354,6 +1464,15 @@ __metadata: languageName: node linkType: hard +"@types/debug@npm:^4.1.7": + version: 4.1.9 + resolution: "@types/debug@npm:4.1.9" + dependencies: + "@types/ms": "*" + checksum: e88ee8b19d106f33eb0d3bc58bacff9702e98d821fd1ebd1de8942e6b97419e19a1ccf39370f1764a1dc66f79fd4619f3412e1be6eeb9f0b76412f5ffe4ead93 + languageName: node + linkType: hard + "@types/glob@npm:^7.1.1": version: 7.1.3 resolution: "@types/glob@npm:7.1.3" @@ -1445,6 +1564,13 @@ __metadata: languageName: node linkType: hard +"@types/ms@npm:*": + version: 0.7.32 + resolution: "@types/ms@npm:0.7.32" + checksum: 610744605c5924aa2657c8a62d307052af4f0e38e2aa015f154ef03391fabb4fd903f9c9baacb41f6e5798b8697e898463c351e5faf638738603ed29137b5254 + languageName: node + linkType: hard + "@types/node@npm:*, @types/node@npm:^16": version: 16.18.31 resolution: "@types/node@npm:16.18.31" @@ -2345,6 +2471,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + "clone-response@npm:^1.0.2": version: 1.0.3 resolution: "clone-response@npm:1.0.3" @@ -2489,6 +2626,15 @@ __metadata: languageName: node linkType: hard +"crc-32@npm:^1.2.0": + version: 1.2.2 + resolution: "crc-32@npm:1.2.2" + bin: + crc32: bin/crc32.njs + checksum: ad2d0ad0cbd465b75dcaeeff0600f8195b686816ab5f3ba4c6e052a07f728c3e70df2e3ca9fd3d4484dc4ba70586e161ca5a2334ec8bf5a41bf022a6103ff243 + languageName: node + linkType: hard + "create-require@npm:^1.1.0": version: 1.1.1 resolution: "create-require@npm:1.1.1" @@ -3197,6 +3343,18 @@ __metadata: languageName: node linkType: hard +"ethereum-cryptography@npm:^2.0.0": + version: 2.1.2 + resolution: "ethereum-cryptography@npm:2.1.2" + dependencies: + "@noble/curves": 1.1.0 + "@noble/hashes": 1.3.1 + "@scure/bip32": 1.3.1 + "@scure/bip39": 1.2.1 + checksum: 2e8f7b8cc90232ae838ab6a8167708e8362621404d26e79b5d9e762c7b53d699f7520aff358d9254de658fcd54d2d0af168ff909943259ed27dc4cef2736410c + languageName: node + linkType: hard + "execa@npm:^0.7.0": version: 0.7.0 resolution: "execa@npm:0.7.0" @@ -5034,6 +5192,13 @@ __metadata: languageName: node linkType: hard +"micro-ftch@npm:^0.3.1": + version: 0.3.1 + resolution: "micro-ftch@npm:0.3.1" + checksum: 0e496547253a36e98a83fb00c628c53c3fb540fa5aaeaf718438873785afd193244988c09d219bb1802984ff227d04938d9571ef90fe82b48bd282262586aaff + languageName: node + linkType: hard + "micromatch@npm:^4.0.4": version: 4.0.4 resolution: "micromatch@npm:4.0.4" @@ -5627,6 +5792,13 @@ __metadata: languageName: node linkType: hard +"pony-cause@npm:^2.1.10": + version: 2.1.10 + resolution: "pony-cause@npm:2.1.10" + checksum: 8b61378f213e61056312dc274a1c79980154e9d864f6ad86e0c8b91a50d3ce900d430995ee24147c9f3caa440dfe7d51c274b488d7f033b65b206522536d7217 + languageName: node + linkType: hard + "postcss@npm:^8.1.10": version: 8.4.31 resolution: "postcss@npm:8.4.31" @@ -6040,7 +6212,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.x, semver@npm:^7.0.0, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": +"semver@npm:7.x, semver@npm:^7.0.0, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.4": version: 7.5.4 resolution: "semver@npm:7.5.4" dependencies: @@ -6429,6 +6601,13 @@ __metadata: languageName: node linkType: hard +"superstruct@npm:^1.0.3": + version: 1.0.3 + resolution: "superstruct@npm:1.0.3" + checksum: 761790bb111e6e21ddd608299c252f3be35df543263a7ebbc004e840d01fcf8046794c274bcb351bdf3eae4600f79d317d085cdbb19ca05803a4361840cc9bb1 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -6985,7 +7164,7 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.0.0, yargs-parser@npm:^21.0.1": +"yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c @@ -7008,17 +7187,17 @@ __metadata: linkType: hard "yargs@npm:^17.0.1, yargs@npm:^17.3.1": - version: 17.5.1 - resolution: "yargs@npm:17.5.1" + version: 17.7.2 + resolution: "yargs@npm:17.7.2" dependencies: - cliui: ^7.0.2 + cliui: ^8.0.1 escalade: ^3.1.1 get-caller-file: ^2.0.5 require-directory: ^2.1.1 string-width: ^4.2.3 y18n: ^5.0.5 - yargs-parser: ^21.0.0 - checksum: 00d58a2c052937fa044834313f07910fd0a115dec5ee35919e857eeee3736b21a4eafa8264535800ba8bac312991ce785ecb8a51f4d2cc8c4676d865af1cfbde + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a languageName: node linkType: hard