diff --git a/.github/.env.test b/.env.test similarity index 100% rename from .github/.env.test rename to .env.test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a212deef2..4d107deff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,30 +46,6 @@ jobs: test-e2e: name: E2E Test runs-on: ubuntu-latest - env: - DB_USER: mastodon - DB_NAME: mastodon - DB_PASS: password - MASTODON_CONTAINER: mastodon - - services: - db: - image: postgres - ports: - - 5432:5432 - env: - POSTGRES_USER: ${{ env.DB_USER }} - POSTGRES_DB: ${{ env.DB_NAME }} - POSTGRES_PASSWORD: ${{ env.DB_PASS }} - POSTGRES_HOST_AUTH_METHOD: trust - options: >- - --health-cmd "pg_isready -U postgres" - redis: - image: redis - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" steps: - uses: actions/checkout@v3 @@ -83,26 +59,7 @@ jobs: cache: yarn - name: Setup Mastodon - run: >- - docker run - -i - --rm - --env-file ./.github/.env.test - docker.io/neetshin/mastodon-dev:latest - bash -c "RAILS_ENV=development bundle exec rails db:setup" - - - name: Run Mastodon - run: >- - docker run - -d - -p 3000:3000 - -p 4000:4000 - --name ${{ env.MASTODON_CONTAINER }} - -e DEEPL_PLAN=${{ secrets.DEEPL_PLAN }} - -e DEEPL_API_KEY=${{ secrets.DEEPL_API_KEY }} - --env-file ./.github/.env.test - docker.io/neetshin/mastodon-dev:latest - bash -c "foreman start" + run: docker compose up -d - name: Install dependencies run: yarn install --frozen-lockfile @@ -110,6 +67,9 @@ jobs: - name: Run tests run: yarn run test:e2e --max-workers=2 + - name: Teardown Mastodon + run: docker compose down + - name: Codecov uses: codecov/codecov-action@v3 with: diff --git a/cspell.json b/cspell.json index e29fc5c0a..97c70e02c 100644 --- a/cspell.json +++ b/cspell.json @@ -1,10 +1,7 @@ { - "version": "0.1", + "version": "0.2", "language": "en,en-GB", - "ignorePaths": [ - "**/node_modules/**", - "**/dist/**" - ], + "ignorePaths": ["**/node_modules/**", "**/dist/**"], "words": [ "AGPL", "asynckit", @@ -44,7 +41,6 @@ "typedoc", "unassign", "unbookmark", - "undici", "unfavourite", "unfetch", "unfollow", diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 000000000..e3bd38b18 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,48 @@ +--- +version: "3" +services: + db: + restart: always + image: postgres + ports: + - "5432:5432" + environment: + - POSTGRES_USER=mastodon + - POSTGRES_PASSWORD=mastodon + - POSTGRES_DB=mastodon + - POSTGRES_HOST_AUTH_METHOD=trust + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + + redis: + restart: always + image: redis + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + + mastodon: + restart: always + image: neetshin/mastodon-dev:latest + ports: + - "3000:3000" + - "4000:4000" + depends_on: + - db + - redis + healthcheck: + test: + [ + "CMD-SHELL", + "wget -q --spider --proxy=off localhost:3000/health || exit 1", + ] + env_file: + - .env.test + environment: + - RAILS_ENV=development + command: > + /bin/bash -c " + bundle exec rails db:setup && + foreman start + " diff --git a/package.json b/package.json index 26899ee5f..99172acee 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "@size-limit/preset-small-lib": "^10.0.1", "@types/jest": "^29.5.6", "@types/node": "^20.8.9", - "@types/proper-lockfile": "^4.1.3", "@types/ws": "^8.5.8", "@typescript-eslint/eslint-plugin": "^6.9.0", "@typescript-eslint/parser": "^6.9.0", @@ -58,11 +57,11 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-unicorn": "^48.0.1", "get-port": "^5.1.1", + "ioredis": "^5.3.2", "iterator-helpers-polyfill": "^2.3.3", "jest": "^29.6.4", "npm-run-all": "^4.1.5", "prettier": "^3.0.3", - "proper-lockfile": "^4.1.2", "rollup": "^4.1.4", "rollup-plugin-auto-external": "^2.0.0", "rollup-plugin-dts": "^6.1.0", @@ -72,8 +71,7 @@ "ts-jest": "^29.1.1", "tslib": "^2.6.2", "typedoc": "^0.25.2", - "typescript": "^5.2.2", - "undici": "^5.27.0" + "typescript": "^5.2.2" }, "files": [ "README.md", diff --git a/src/global.d.ts b/src/global.d.ts deleted file mode 100644 index 84ad617a0..000000000 --- a/src/global.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Fetch API types are not yet supported in Node.js, so we are using this file as a workaround by using types from undici. - * On the userland, these types will be unresolved. So users will have to redeclare types or add `lib: ["dom"]` to their tsconfig. - * - * https://github.com/DefinitelyTyped/DefinitelyTyped/issues/60924 - * https://stackoverflow.com/questions/71294230/how-can-i-use-native-fetch-with-node-in-typescript-node-v17-6 - */ - -/* eslint-disable @typescript-eslint/consistent-type-imports */ -import type * as undici_types from "undici"; - -declare global { - export const { - fetch, - FormData, - Headers, - Request, - Response, - }: typeof import("undici"); - - type FormData = undici_types.FormData; - type Headers = undici_types.Headers; - type HeadersInit = undici_types.HeadersInit; - type BodyInit = undici_types.BodyInit; - type Request = undici_types.Request; - type RequestInit = undici_types.RequestInit; - type RequestInfo = undici_types.RequestInfo; - type RequestMode = undici_types.RequestMode; - type RequestRedirect = undici_types.RequestRedirect; - type RequestCredentials = undici_types.RequestCredentials; - type RequestDestination = undici_types.RequestDestination; - type ReferrerPolicy = undici_types.ReferrerPolicy; - type Response = undici_types.Response; - type ResponseInit = undici_types.ResponseInit; - type ResponseType = undici_types.ResponseType; -} diff --git a/test-utils/jest-environment.ts b/test-utils/jest-environment.ts index cf4025bdb..805b81210 100644 --- a/test-utils/jest-environment.ts +++ b/test-utils/jest-environment.ts @@ -3,6 +3,7 @@ import { existsSync } from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; +import Redis from "ioredis"; import NodeEnvironment from "jest-environment-node"; import { @@ -10,17 +11,30 @@ import { createRestAPIClient, type mastodon, } from "../src"; -import { TokenPoolFsImpl } from "./pools"; -import { TokenFactoryDocker } from "./pools/token-factory-docker"; -import { TokenRepositoryFs } from "./pools/token-repository-fs"; +import { + TokenFactoryDocker, + TokenPoolImpl, + TokenRepositoryRedis, +} from "./pools"; import { createTootctl } from "./tootctl"; class CustomEnvironment extends NodeEnvironment { + redis!: Redis; + override async setup(): Promise { await super.setup(); - const misc = await this.createGlobals(); - this.global.__misc__ = misc; - this.global.Symbol = Symbol; + this.redis = new Redis(); + this.global.__misc__ = await this.createGlobals(); + + /* eslint-disable @typescript-eslint/no-explicit-any */ + (this.global.Symbol as any).dispose ??= Symbol("Symbol.dispose"); + (this.global.Symbol as any).asyncDispose ??= Symbol("Symbol.asyncDispose"); + /* eslint-enable @typescript-eslint/no-explicit-any */ + } + + override async teardown(): Promise { + await super.teardown(); + this.redis.disconnect(); } private async createGlobals(): Promise { @@ -33,15 +47,13 @@ class CustomEnvironment extends NodeEnvironment { } const adminToken = await this.readAdminToken(baseCacheDir); - const repository = new TokenRepositoryFs( - path.join(baseCacheDir, "tokens.json"), - ); + const repository = new TokenRepositoryRedis(this.redis); const container = process.env.MASTODON_CONTAINER ?? "mastodon"; - const tootctl = createTootctl({ container }); + const tootctl = createTootctl({ container, compose: true }); const oauth = createOAuthAPIClient({ url }); const app = await this.readApp(baseCacheDir); const factory = new TokenFactoryDocker(tootctl, oauth, app); - const tokenPool = new TokenPoolFsImpl(repository, factory); + const tokenPool = new TokenPoolImpl(repository, factory); return { url, diff --git a/test-utils/jest-global-setup.ts b/test-utils/jest-global-setup.ts index c84816333..d46fd6f76 100644 --- a/test-utils/jest-global-setup.ts +++ b/test-utils/jest-global-setup.ts @@ -57,16 +57,6 @@ const readOrCreateAdminToken = async ( return token; }; -const initTokens = async (baseCacheDir: string) => { - const tokensFilePath = path.join(baseCacheDir, "tokens.json"); - - if (existsSync(tokensFilePath)) { - return; - } - - await fs.writeFile(path.join(baseCacheDir, "tokens.json"), "[]", "utf8"); -}; - export default async function main(): Promise { const baseCacheDir = path.join(__dirname, "../node_modules/.cache/masto"); if (!existsSync(baseCacheDir)) { @@ -78,5 +68,4 @@ export default async function main(): Promise { const app = await readOrCreateApp(baseCacheDir, masto); await readOrCreateAdminToken(baseCacheDir, oauth, app); - await initTokens(baseCacheDir); } diff --git a/test-utils/pools/fs-exclusive-lock.ts b/test-utils/pools/fs-exclusive-lock.ts deleted file mode 100644 index 0ea5574d9..000000000 --- a/test-utils/pools/fs-exclusive-lock.ts +++ /dev/null @@ -1,34 +0,0 @@ -import lockfile from "proper-lockfile"; - -import { ExponentialBackoff, noop } from "../../src/utils"; - -export class ExclusiveLock { - constructor(readonly path: string) {} - - transaction = async (callback: () => Promise | T): Promise => { - const release = await this.lock(); - - try { - return callback(); - } finally { - await release(); - } - }; - - private lock = async (): Promise<() => Promise> => { - const backoff = new ExponentialBackoff({ - factor: 10, - maxAttempts: 100, - }); - - for await (const _ of backoff) { - try { - return await lockfile.lock(this.path); - } catch { - noop(); - } - } - - throw new Error("Failed to lock"); - }; -} diff --git a/test-utils/pools/index.ts b/test-utils/pools/index.ts index 71e1cbf5b..8340f1485 100644 --- a/test-utils/pools/index.ts +++ b/test-utils/pools/index.ts @@ -1,4 +1,6 @@ export * from "./session-pool"; export * from "./token-pool"; -export * from "./token-pool-fs"; +export * from "./token-pool-impl"; +export * from "./token-repository-redis"; +export * from "./token-factory-docker"; export * from "./base-pool"; diff --git a/test-utils/pools/session-pool.ts b/test-utils/pools/session-pool.ts index cc7d95772..fd8448f72 100644 --- a/test-utils/pools/session-pool.ts +++ b/test-utils/pools/session-pool.ts @@ -13,23 +13,28 @@ export class SessionPoolImpl implements Pool { ) {} acquire = async (): Promise => { - const token = await this.tokens.acquire(); - try { - const session = await createSession( - token, - this.url, - this.instance.urls.streamingApi, - () => this.release(session), - ); - - // eslint-disable-next-line no-console - console.log(`Acquired session ${session.id} (${session.acct})`); - - this.sessionToToken.set(session, token); - return session; + const token = await this.tokens.acquire(); + + try { + const session = await createSession( + token, + this.url, + this.instance.urls.streamingApi, + () => this.release(session), + ); + + this.sessionToToken.set(session, token); + return session; + } catch (error) { + await this.tokens.release(token); + // eslint-disable-next-line no-console + console.error(error); + throw error; + } } catch (error) { - await this.tokens.release(token); + // eslint-disable-next-line no-console + console.error(error); throw error; } }; @@ -45,9 +50,6 @@ export class SessionPoolImpl implements Pool { return; } - // eslint-disable-next-line no-console - console.log(`Released session ${session.id} (${session.acct})`); - await this.tokens.release(token); this.sessionToToken.delete(session); }; diff --git a/test-utils/pools/token-pool-fs.ts b/test-utils/pools/token-pool-impl.ts similarity index 50% rename from test-utils/pools/token-pool-fs.ts rename to test-utils/pools/token-pool-impl.ts index 1225bee02..7a033aa77 100644 --- a/test-utils/pools/token-pool-fs.ts +++ b/test-utils/pools/token-pool-impl.ts @@ -3,39 +3,26 @@ import { type TokenFactory } from "./token-factory"; import { type TokenPool } from "./token-pool"; import { type TokenRepository } from "./token-repository"; -export class TokenPoolFsImpl implements TokenPool { +export class TokenPoolImpl implements TokenPool { constructor( private readonly repository: TokenRepository, private readonly tokenFactory: TokenFactory, ) {} async acquire(): Promise { - const token = await this.repository.transaction(async () => { - const entries = await this.repository.getAll(); - const entry = entries.find((entry) => !entry.inUse); - - if (entry == undefined) { - return; - } + const entry = await this.repository.find({ inUse: false }); + if (entry != undefined) { await this.repository.use(entry.token.accessToken); return entry.token; - }); - - if (token != undefined) { - return token; } const newToken = await this.tokenFactory.obtain(); - await this.repository.transaction(async () => { - await this.repository.add({ token: newToken, inUse: true }); - }); + await this.repository.add({ token: newToken, inUse: true }); return newToken; } async release(token: mastodon.v1.Token): Promise { - return this.repository.transaction(async () => { - await this.repository.release(token.accessToken); - }); + await this.repository.release(token.accessToken); } } diff --git a/test-utils/pools/token-repository-fs.ts b/test-utils/pools/token-repository-fs.ts deleted file mode 100644 index 5b95e3cac..000000000 --- a/test-utils/pools/token-repository-fs.ts +++ /dev/null @@ -1,78 +0,0 @@ -import fs from "node:fs/promises"; - -import { ExponentialBackoff, noop } from "../../src/utils"; -import { ExclusiveLock } from "./fs-exclusive-lock"; -import { type Entry, type TokenRepository } from "./token-repository"; - -export class TokenRepositoryFs implements TokenRepository { - private readonly locker: ExclusiveLock; - readonly transaction: TokenRepository["transaction"]; - - constructor(private readonly path: string) { - this.locker = new ExclusiveLock(path); - this.transaction = this.locker.transaction.bind(this.locker); - } - - async getAll(): Promise { - const backoff = new ExponentialBackoff({ - factor: 10, - maxAttempts: 100, - }); - - for await (const _ of backoff) { - try { - const file = await fs.readFile(this.path, "utf8"); - const entries = JSON.parse(file); - return entries; - } catch { - noop(); - } - } - - throw new Error("Failed to read token repository"); - } - - async add(token: Entry): Promise { - // eslint-disable-next-line no-console - console.log(`Adding token ${token.token.accessToken}`); - const entries = await this.getAll(); - const newEntries = [...entries, token]; - await this.save(newEntries); - } - - async use(token: string): Promise { - const entries = await this.getAll(); - const newEntries = entries.map((entry) => { - if (entry.token.accessToken === token) { - return { - ...entry, - inUse: true, - }; - } - return entry; - }); - await this.save(newEntries); - } - - async release(token: string): Promise { - const entries = await this.getAll(); - const newEntries = entries.map((entry) => { - if (entry.token.accessToken === token) { - return { - ...entry, - inUse: false, - }; - } - return entry; - }); - await this.save(newEntries); - } - - private readonly save = async (entries: Entry[]): Promise => { - await fs.writeFile( - this.path, - JSON.stringify(entries, undefined, 2), - "utf8", - ); - }; -} diff --git a/test-utils/pools/token-repository-redis.ts b/test-utils/pools/token-repository-redis.ts new file mode 100644 index 000000000..42a481714 --- /dev/null +++ b/test-utils/pools/token-repository-redis.ts @@ -0,0 +1,57 @@ +import { type Redis } from "ioredis"; + +import { + type Entry, + type FindParams, + type TokenRepository, +} from "./token-repository"; + +export class TokenRepositoryRedis implements TokenRepository { + constructor(private readonly redis: Redis) {} + + async find(params: FindParams): Promise { + const keys = await this.redis.keys("token:*"); + for (const key of keys) { + const entry = await this.redis.get(key); + if (!entry) { + continue; + } + const parsed = JSON.parse(entry); + if (parsed.inUse === params.inUse) { + return parsed; + } + } + return; + } + + async add(token: Entry): Promise { + await this.redis.set( + `token:${token.token.accessToken}`, + JSON.stringify(token), + ); + } + + async use(token: string): Promise { + const entry = await this.redis.get(`token:${token}`); + if (!entry) { + throw new Error(`Token ${token} not found`); + } + const parsed = JSON.parse(entry); + await this.redis.set( + `token:${token}`, + JSON.stringify({ ...parsed, inUse: true }), + ); + } + + async release(token: string): Promise { + const entry = await this.redis.get(`token:${token}`); + if (!entry) { + throw new Error(`Token ${token} not found`); + } + const parsed = JSON.parse(entry); + await this.redis.set( + `token:${token}`, + JSON.stringify({ ...parsed, inUse: false }), + ); + } +} diff --git a/test-utils/pools/token-repository.ts b/test-utils/pools/token-repository.ts index ae5fc3cb1..74df074f9 100644 --- a/test-utils/pools/token-repository.ts +++ b/test-utils/pools/token-repository.ts @@ -5,10 +5,13 @@ export interface Entry { inUse: boolean; } +export type FindParams = { + readonly inUse: boolean; +}; + export interface TokenRepository { - getAll(): Promise; + find(params: FindParams): Promise; add(token: Entry): Promise; use(token: string): Promise; release(token: string): Promise; - transaction(callback: () => Promise): Promise; } diff --git a/test-utils/tootctl/tootctl-docker.ts b/test-utils/tootctl/tootctl-docker.ts index f7aab7839..855ad6786 100644 --- a/test-utils/tootctl/tootctl-docker.ts +++ b/test-utils/tootctl/tootctl-docker.ts @@ -23,10 +23,11 @@ const stringifyArguments = ( export interface CreateTootctlParams { readonly container: string; + readonly compose: boolean; } export const createTootctl = (params: CreateTootctlParams): Tootctl => { - const { container } = params; + const { container, compose } = params; return { accounts: { @@ -36,9 +37,13 @@ export const createTootctl = (params: CreateTootctlParams): Tootctl => { ): Promise => { const args = stringifyArguments({ ...params }); + const command = compose + ? `docker compose exec ${container}` + : `docker exec ${container}`; + const { stdout } = await exec( [ - `docker exec ${container}`, + command, `bash -c "RAILS_ENV=development bin/tootctl accounts create ${username} ${args}"`, ].join(" "), ); diff --git a/yarn.lock b/yarn.lock index f7690f7b0..3357f2d07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -785,11 +785,6 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.52.0.tgz#78fe5f117840f69dc4a353adf9b9cd926353378c" integrity sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA== -"@fastify/busboy@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" - integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ== - "@humanwhocodes/config-array@^0.11.13": version "0.11.13" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" @@ -809,6 +804,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -1795,18 +1795,6 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz#9b0e3e8533fe5024ad32d6637eb9589988b6fdca" integrity sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A== -"@types/proper-lockfile@^4.1.3": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@types/proper-lockfile/-/proper-lockfile-4.1.3.tgz#6c48bb573de5c4342e77c393d1e38417f7d8a963" - integrity sha512-10msVdc5q6kkZpJFjyz6T245uB6vN9EcbJx6j2hBD6bls0Z8xEpOvLMOyEVpsT1r4uRhfM0+U1Gvs5eC+k7Zuw== - dependencies: - "@types/retry" "*" - -"@types/retry@*": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" - integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== - "@types/semver@^7.5.0": version "7.5.3" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04" @@ -2672,6 +2660,11 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== + cmd-shim@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d" @@ -3113,6 +3106,11 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + deprecation@^2.0.0: version "2.3.1" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" @@ -4141,7 +4139,7 @@ graceful-fs@4.2.10: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -4458,6 +4456,21 @@ into-stream@^7.0.0: from2 "^2.3.0" p-is-promise "^3.0.0" +ioredis@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7" + integrity sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + ip-regex@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" @@ -5535,11 +5548,21 @@ lodash.capitalize@^4.2.1: resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" integrity sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw== +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + lodash.escaperegexp@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw== +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -6748,15 +6771,6 @@ promzard@^1.0.0: dependencies: read "^2.0.0" -proper-lockfile@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" - integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== - dependencies: - graceful-fs "^4.2.4" - retry "^0.12.0" - signal-exit "^3.0.2" - proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -6921,6 +6935,18 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + regexp-tree@^0.1.27: version "0.1.27" resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" @@ -7447,6 +7473,11 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + stream-combiner2@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" @@ -7937,13 +7968,6 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -undici@^5.27.0: - version "5.27.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.27.0.tgz#789f2e40ce982b5507899abc2c2ddeb2712b4554" - integrity sha512-l3ydWhlhOJzMVOYkymLykcRRXqbUaQriERtR70B9LzNkZ4bX52Fc8wbTDneMiwo8T+AemZXvXaTx+9o5ROxrXg== - dependencies: - "@fastify/busboy" "^2.0.0" - unicode-emoji-modifier-base@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459"