From 387b3cf263e3ac758bc96f717e21541f7eabe500 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 8 Oct 2024 14:16:58 +0200 Subject: [PATCH 01/54] ensure that flask server doesn't crash locally when finding .DS-Store --- api-server/apiserver.py | 58 +++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/api-server/apiserver.py b/api-server/apiserver.py index c2e1485d8..e6dc6ee66 100644 --- a/api-server/apiserver.py +++ b/api-server/apiserver.py @@ -2,7 +2,7 @@ import binascii from functools import partial -from datadog import initialize as datadog_initialize, statsd +#from datadog import initialize as datadog_initialize, statsd import sentry_sdk from flask import Flask, json, abort, jsonify, request, redirect @@ -16,36 +16,36 @@ NAMESPACE_FILE_MARKER = "__NAMESPACE__" -class Metrics: +# class Metrics: - PREFIX = os.getenv("METRICS_PREFIX", "release_registry") +# PREFIX = os.getenv("METRICS_PREFIX", "release_registry") - def initialize(self, **kwargs): - # DATADOG_API_KEY, DATADOG_APP_KEY can be provided from env - # TODO(michal): Pass statsd_constant_tags from env? - datadog_initialize(**kwargs) - return self +# def initialize(self, **kwargs): +# # DATADOG_API_KEY, DATADOG_APP_KEY can be provided from env +# # TODO(michal): Pass statsd_constant_tags from env? +# #datadog_initialize(**kwargs) +# return self - @staticmethod - def tags_from_request(): - tags = [] - if request.url_rule: - tags.append(f"route:{request.url_rule.rule}") - return tags +# @staticmethod +# def tags_from_request(): +# tags = [] +# if request.url_rule: +# tags.append(f"route:{request.url_rule.rule}") +# return tags - def _metric(self, name): - return f"{self.PREFIX}.{name}" +# def _metric(self, name): +# return f"{self.PREFIX}.{name}" - def increment(self, name, value=1, tags=None, sample_rate=None): - statsd.increment( - self._metric(name), - value=value, - tags=self.tags_from_request() + (tags or []), - sample_rate=sample_rate, - ) +# def increment(self, name, value=1, tags=None, sample_rate=None): +# statsd.increment( +# self._metric(name), +# value=value, +# tags=self.tags_from_request() + (tags or []), +# sample_rate=sample_rate, +# ) -metrics = Metrics().initialize() +# metrics = Metrics().initialize() # SENTRY_DSN will be taken from env sentry_sdk.init(integrations=[FlaskIntegration()]) @@ -149,6 +149,8 @@ def get_package_versions(self, canonical): def iter_packages(self): for package_registry in os.listdir(self._path("packages")): + if not os.path.isdir(self._path("packages", package_registry)): + continue for item in os.listdir(self._path("packages", package_registry)): if os.path.exists( os.path.join( @@ -282,17 +284,17 @@ def return_cached(): if not request.values: response = cache.get(request.path) if response: - metrics.increment("cache_hit") + #metrics.increment("cache_hit") response.headers["X-From-Cache"] = "1" return response - metrics.increment("cache_miss") + #metrics.increment("cache_miss") def cache_response(response): if not request.values: # Make the response picklable response.freeze() - metrics.increment("cache_set") + #metrics.increment("cache_set") cache.set(request.path, response) return response @@ -351,7 +353,7 @@ def get_url_checksums(app_info, url): # Must come before the caching callbacks otherwise it will never be called for # request served by the caching callback. -app.before_request(partial(metrics.increment, "request")) +#app.before_request(partial(metrics.increment, "request")) app.enable_cache = partial(set_cache_enabled, app) app.enable_cache(is_caching_enabled()) From ac8d2a2d6aaac03d7c9ce8a6781c05df187f0c54 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 8 Oct 2024 15:34:15 +0200 Subject: [PATCH 02/54] add packages endpoint --- Dockerfile | 10 + api-node/.eslintrc.js | 25 + api-node/.gitignore | 56 + api-node/.prettierrc | 4 + api-node/README.md | 85 + api-node/nest-cli.json | 8 + api-node/package.json | 69 + api-node/src/app.controller.spec.ts | 22 + api-node/src/app.controller.ts | 12 + api-node/src/app.module.ts | 12 + api-node/src/app.service.ts | 8 + api-node/src/main.ts | 8 + api-node/src/packages/packages.controller.ts | 25 + api-node/src/packages/packages.service.ts | 121 + api-node/test/app.e2e-spec.ts | 21 + api-node/test/jest-e2e.json | 9 + api-node/test/packages.e2e-spec.ts | 68 + api-node/tsconfig.build.json | 4 + api-node/tsconfig.json | 21 + api-node/yarn.lock | 4848 ++++++++++++++++++ 20 files changed, 5436 insertions(+) create mode 100644 api-node/.eslintrc.js create mode 100644 api-node/.gitignore create mode 100644 api-node/.prettierrc create mode 100644 api-node/README.md create mode 100644 api-node/nest-cli.json create mode 100644 api-node/package.json create mode 100644 api-node/src/app.controller.spec.ts create mode 100644 api-node/src/app.controller.ts create mode 100644 api-node/src/app.module.ts create mode 100644 api-node/src/app.service.ts create mode 100644 api-node/src/main.ts create mode 100644 api-node/src/packages/packages.controller.ts create mode 100644 api-node/src/packages/packages.service.ts create mode 100644 api-node/test/app.e2e-spec.ts create mode 100644 api-node/test/jest-e2e.json create mode 100644 api-node/test/packages.e2e-spec.ts create mode 100644 api-node/tsconfig.build.json create mode 100644 api-node/tsconfig.json create mode 100644 api-node/yarn.lock diff --git a/Dockerfile b/Dockerfile index f0a583606..a6c5e651f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,3 +41,13 @@ EXPOSE 5030 ENTRYPOINT ["/docker-entrypoint.sh"] CMD [ "mywsgi", "apiserver:app", "0.0.0.0:5030" ] +# To start this Docker container, use the following commands: + +# Build the Docker image: +# docker build -t registry-api-server . + +# Run the Docker container: +# docker run -p 5030:5030 registry-api-server + +# These commands should be run from the directory containing this Dockerfile. +# The -p 5030:5030 flag maps the container's port 5030 to the host's port 5030. diff --git a/api-node/.eslintrc.js b/api-node/.eslintrc.js new file mode 100644 index 000000000..259de13c7 --- /dev/null +++ b/api-node/.eslintrc.js @@ -0,0 +1,25 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js'], + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, +}; diff --git a/api-node/.gitignore b/api-node/.gitignore new file mode 100644 index 000000000..4b56acfbe --- /dev/null +++ b/api-node/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/api-node/.prettierrc b/api-node/.prettierrc new file mode 100644 index 000000000..dcb72794f --- /dev/null +++ b/api-node/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/api-node/README.md b/api-node/README.md new file mode 100644 index 000000000..63c3e90d9 --- /dev/null +++ b/api-node/README.md @@ -0,0 +1,85 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Coverage +Discord +Backers on Open Collective +Sponsors on Open Collective + Donate us + Support us + Follow us on Twitter +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. + +## Project setup + +```bash +$ yarn install +``` + +## Compile and run the project + +```bash +# development +$ yarn run start + +# watch mode +$ yarn run start:dev + +# production mode +$ yarn run start:prod +``` + +## Run tests + +```bash +# unit tests +$ yarn run test + +# e2e tests +$ yarn run test:e2e + +# test coverage +$ yarn run test:cov +``` + +## Resources + +Check out a few resources that may come in handy when working with NestJS: + +- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. +- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). +- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). +- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). +- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). +- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). +- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myƛliwiec](https://twitter.com/kammysliwiec) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). diff --git a/api-node/nest-cli.json b/api-node/nest-cli.json new file mode 100644 index 000000000..f9aa683b1 --- /dev/null +++ b/api-node/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/api-node/package.json b/api-node/package.json new file mode 100644 index 000000000..e9b816dbf --- /dev/null +++ b/api-node/package.json @@ -0,0 +1,69 @@ +{ + "name": "api-node", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.5.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.1.0", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/api-node/src/app.controller.spec.ts b/api-node/src/app.controller.spec.ts new file mode 100644 index 000000000..d22f3890a --- /dev/null +++ b/api-node/src/app.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +describe('AppController', () => { + let appController: AppController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); + + appController = app.get(AppController); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + expect(appController.getHello()).toBe('Hello World!'); + }); + }); +}); diff --git a/api-node/src/app.controller.ts b/api-node/src/app.controller.ts new file mode 100644 index 000000000..7daccf7a6 --- /dev/null +++ b/api-node/src/app.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get('/healthz') + getHealthCheck(): string { + return 'ok'; + } +} diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts new file mode 100644 index 000000000..b0a98e271 --- /dev/null +++ b/api-node/src/app.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { PackagesController } from './packages/packages.controller'; +import { PackagesService } from './packages/packages.service'; + +@Module({ + imports: [], + controllers: [AppController, PackagesController], + providers: [AppService, PackagesService], +}) +export class AppModule {} diff --git a/api-node/src/app.service.ts b/api-node/src/app.service.ts new file mode 100644 index 000000000..927d7cca0 --- /dev/null +++ b/api-node/src/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + getHello(): string { + return 'Hello World!'; + } +} diff --git a/api-node/src/main.ts b/api-node/src/main.ts new file mode 100644 index 000000000..13cad38cf --- /dev/null +++ b/api-node/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(3000); +} +bootstrap(); diff --git a/api-node/src/packages/packages.controller.ts b/api-node/src/packages/packages.controller.ts new file mode 100644 index 000000000..e1056a28b --- /dev/null +++ b/api-node/src/packages/packages.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { PackagesService } from './packages.service'; + +@Controller('packages') +export class PackagesController { + constructor(private packagesService: PackagesService) {} + + @Get() + getPackages() { + return this.packagesService.getPackages(); + } + + @Get('/:package(*)/versions') + getPackageVersions(@Param('package') pgkName: string) { + return this.packagesService.getPackageVersions(pgkName); + } + + @Get('/:package(*)/:version') + getPackageByVersion( + @Param('package') pkgName: string, + @Param('version') version: string, + ) { + return this.packagesService.getPackageByVersion(pkgName, version); + } +} diff --git a/api-node/src/packages/packages.service.ts b/api-node/src/packages/packages.service.ts new file mode 100644 index 000000000..df8ee3a32 --- /dev/null +++ b/api-node/src/packages/packages.service.ts @@ -0,0 +1,121 @@ +import { Injectable } from '@nestjs/common'; +import * as fs from 'fs'; +import * as path from 'path'; + +@Injectable() +export class PackagesService { + #packages: string[]; + + constructor() { + this.#packages = Array.from(iterPackages()); + } + + getPackages() { + return this.#packages.reduce((acc, canonical) => { + const packageDir = getPackageDir(canonical); + const latestFilePath = path.join(packageDir, 'latest.json'); + + try { + const packageInfo = JSON.parse( + fs.readFileSync(latestFilePath).toString(), + ); + return { + ...acc, + [packageInfo.canonical]: packageInfo, + }; + } catch (e) { + console.error(`Failed to read package: ${canonical}`); + console.error(e); + } + }, {}); + } + + getPackageVersions(packageName: string): { latest: any; versions: string[] } { + const packageDir = getPackageDir(packageName); + try { + const versions = fs + .readdirSync(packageDir) + .filter((file) => file.endsWith('.json') && file !== 'latest.json') + .map((f) => { + const versionFile = JSON.parse( + fs.readFileSync(path.join(packageDir, f)).toString(), + ); + return versionFile.version; + }); + + const dedupedVersions = Array.from(new Set(versions)); + + const latest = JSON.parse( + fs.readFileSync(path.join(packageDir, 'latest.json')).toString(), + ); + + return { versions: dedupedVersions, latest }; + } catch (e) { + console.error(`Failed to read package versions: ${packageName}`); + console.error(e); + } + } + + getPackageByVersion(packageName: string, version: string): string { + const packageDir = getPackageDir(packageName); + const versionFilePath = path.join(packageDir, `${version}.json`); + + try { + return JSON.parse(fs.readFileSync(versionFilePath).toString()); + } catch (e) { + console.error(`Failed to read package by version: ${packageName}`); + console.error(e); + } + } +} + +const NAMESPACE_FILE_MARKER = '__NAMESPACE__'; + +function getPackageDir(canonicalPackageName: string) { + const [registry, name] = canonicalPackageName.split(':', 2); + const pkgPath = name.replace(':', path.sep).split(path.sep); + return path.resolve(path.join('..', 'packages', registry, ...pkgPath)); +} + +function* iterPackages() { + const packagesPath = '../packages'; + + // Loop through each package registry + const packageRegistries = fs.readdirSync(packagesPath); + for (const packageRegistry of packageRegistries) { + const registryPath = path.join(packagesPath, packageRegistry); + + // bail if registry path is not a dir + if (!fs.lstatSync(registryPath).isDirectory()) { + continue; + } + + // Loop through each item in the registry + const items = fs.readdirSync(registryPath); + for (const item of items) { + const namespaceFilePath = path.join( + packagesPath, + packageRegistry, + item, + NAMESPACE_FILE_MARKER, + ); + + // Check if the NAMESPACE_FILE_MARKER exists + if (fs.existsSync(namespaceFilePath)) { + const subItems = fs.readdirSync( + path.join(packagesPath, packageRegistry, item), + ); + + // Yield subitems, excluding NAMESPACE_FILE_MARKER + for (const subitem of subItems) { + if (subitem !== NAMESPACE_FILE_MARKER) { + yield `${packageRegistry}:${item}/${subitem}`; + } + } + } else { + // Yield item if NAMESPACE_FILE_MARKER does not exist + yield `${packageRegistry}:${item}`; + } + } + } +} diff --git a/api-node/test/app.e2e-spec.ts b/api-node/test/app.e2e-spec.ts new file mode 100644 index 000000000..83986d826 --- /dev/null +++ b/api-node/test/app.e2e-spec.ts @@ -0,0 +1,21 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from './../src/app.module'; + +describe('AppController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => { + return request(app.getHttpServer()).get('/').expect(404); + }); +}); diff --git a/api-node/test/jest-e2e.json b/api-node/test/jest-e2e.json new file mode 100644 index 000000000..e9d912f3e --- /dev/null +++ b/api-node/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/api-node/test/packages.e2e-spec.ts b/api-node/test/packages.e2e-spec.ts new file mode 100644 index 000000000..ad01a792e --- /dev/null +++ b/api-node/test/packages.e2e-spec.ts @@ -0,0 +1,68 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from './../src/app.module'; + +const PYTHON_API_PORT = 5031; +const PYTHON_API_URL = `http://localhost:${PYTHON_API_PORT}`; + +describe('PackagesController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/packages (GET)', async () => { + const pythonApiResponse = await fetch(`${PYTHON_API_URL}/packages`); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get('/packages') + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); + + it('/packages/:packageName/versions (GET)', async () => { + const packageName = 'npm:@sentry/angular'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/packages/${packageName}/versions`, + ); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get(`/packages/${packageName}/versions`) + .expect((r) => { + expect(r.status).toEqual(200); + const { versions, latest } = r.body; + expect(versions.length).toEqual(pythonApiData.versions.length); + expect(versions.sort()).toEqual(pythonApiData.versions.sort()); + expect(latest).toEqual(pythonApiData.latest); + }); + }); + + it('/packages/:packageName/:version (GET)', async () => { + const packageName = 'npm:@sentry/angular'; + const version = '8.0.0'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/packages/${packageName}/${version}`, + ); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get(`/packages/${packageName}/${version}`) + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); +}); diff --git a/api-node/tsconfig.build.json b/api-node/tsconfig.build.json new file mode 100644 index 000000000..64f86c6bd --- /dev/null +++ b/api-node/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/api-node/tsconfig.json b/api-node/tsconfig.json new file mode 100644 index 000000000..95f5641cf --- /dev/null +++ b/api-node/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/api-node/yarn.lock b/api-node/yarn.lock new file mode 100644 index 000000000..d087e5846 --- /dev/null +++ b/api-node/yarn.lock @@ -0,0 +1,4848 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@angular-devkit/core@17.3.8": + version "17.3.8" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.3.8.tgz#8679cacf84cf79764f027811020e235ab32016d2" + integrity sha512-Q8q0voCGudbdCgJ7lXdnyaxKHbNQBARH68zPQV72WT8NWy+Gw/tys870i6L58NWbBaCJEUcIj/kb6KoakSRu+Q== + dependencies: + ajv "8.12.0" + ajv-formats "2.1.1" + jsonc-parser "3.2.1" + picomatch "4.0.1" + rxjs "7.8.1" + source-map "0.7.4" + +"@angular-devkit/schematics-cli@17.3.8": + version "17.3.8" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-17.3.8.tgz#26eeb9b581309be474868d01d9f87555760557c3" + integrity sha512-TjmiwWJarX7oqvNiRAroQ5/LeKUatxBOCNEuKXO/PV8e7pn/Hr/BqfFm+UcYrQoFdZplmtNAfqmbqgVziKvCpA== + dependencies: + "@angular-devkit/core" "17.3.8" + "@angular-devkit/schematics" "17.3.8" + ansi-colors "4.1.3" + inquirer "9.2.15" + symbol-observable "4.0.0" + yargs-parser "21.1.1" + +"@angular-devkit/schematics@17.3.8": + version "17.3.8" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-17.3.8.tgz#f853eb21682aadfb6667e090b5b509fc95ce8442" + integrity sha512-QRVEYpIfgkprNHc916JlPuNbLzOgrm9DZalHasnLUz4P6g7pR21olb8YCyM2OTJjombNhya9ZpckcADU5Qyvlg== + dependencies: + "@angular-devkit/core" "17.3.8" + jsonc-parser "3.2.1" + magic-string "0.30.8" + ora "5.4.1" + rxjs "7.8.1" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + +"@babel/compat-data@^7.25.2": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" + integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" + integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.25.0", "@babel/generator@^7.25.6", "@babel/generator@^7.7.2": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c" + integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw== + dependencies: + "@babel/types" "^7.25.6" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" + integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== + dependencies: + "@babel/compat-data" "^7.25.2" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" + integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" + integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.2" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.8.0": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" + integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== + +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" + integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== + +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + +"@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== + +"@babel/helpers@^7.25.0": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60" + integrity sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q== + dependencies: + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.6" + +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" + integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== + dependencies: + "@babel/types" "^7.25.6" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz#6d4c78f042db0e82fd6436cd65fec5dc78ad2bde" + integrity sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" + integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz#04db9ce5a9043d9c635e75ae7969a2cd50ca97ff" + integrity sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/template@^7.25.0", "@babel/template@^7.3.3": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" + +"@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" + integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.6" + "@babel/parser" "^7.25.6" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6", "@babel/types@^7.3.3": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" + integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.11.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f" + integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@ljharb/through@^2.3.12": + version "2.3.13" + resolved "https://registry.yarnpkg.com/@ljharb/through/-/through-2.3.13.tgz#b7e4766e0b65aa82e529be945ab078de79874edc" + integrity sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ== + dependencies: + call-bind "^1.0.7" + +"@lukeed/csprng@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" + integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== + +"@nestjs/cli@^10.0.0": + version "10.4.5" + resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-10.4.5.tgz#d6563b87e8ca1d0f256c19a7847dbcc96c76a88e" + integrity sha512-FP7Rh13u8aJbHe+zZ7hM0CC4785g9Pw4lz4r2TTgRtf0zTxSWMkJaPEwyjX8SK9oWK2GsYxl+fKpwVZNbmnj9A== + dependencies: + "@angular-devkit/core" "17.3.8" + "@angular-devkit/schematics" "17.3.8" + "@angular-devkit/schematics-cli" "17.3.8" + "@nestjs/schematics" "^10.0.1" + chalk "4.1.2" + chokidar "3.6.0" + cli-table3 "0.6.5" + commander "4.1.1" + fork-ts-checker-webpack-plugin "9.0.2" + glob "10.4.2" + inquirer "8.2.6" + node-emoji "1.11.0" + ora "5.4.1" + tree-kill "1.2.2" + tsconfig-paths "4.2.0" + tsconfig-paths-webpack-plugin "4.1.0" + typescript "5.3.3" + webpack "5.94.0" + webpack-node-externals "3.0.0" + +"@nestjs/common@^10.0.0": + version "10.4.4" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.4.4.tgz#673d0eca273e1ab3a4d3ec9e212114b9f4fbf6e8" + integrity sha512-0j2/zqRw9nvHV1GKTktER8B/hIC/Z8CYFjN/ZqUuvwayCH+jZZBhCR2oRyuvLTXdnlSmtCAg2xvQ0ULqQvzqhA== + dependencies: + uid "2.0.2" + iterare "1.2.1" + tslib "2.7.0" + +"@nestjs/core@^10.0.0": + version "10.4.4" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.4.4.tgz#12cb1110da6d76e12ceccf0e92f6f5220fe27525" + integrity sha512-y9tjmAzU6LTh1cC/lWrRsCcOd80khSR0qAHAqwY2svbW+AhsR/XCzgpZrAAKJrm/dDfjLCZKyxJSayeirGcW5Q== + dependencies: + uid "2.0.2" + "@nuxtjs/opencollective" "0.3.2" + fast-safe-stringify "2.1.1" + iterare "1.2.1" + path-to-regexp "3.3.0" + tslib "2.7.0" + +"@nestjs/platform-express@^10.0.0": + version "10.4.4" + resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.4.4.tgz#582d375272207f8d1528f77ff470ba815d9d9846" + integrity sha512-y52q1MxhbHaT3vAgWd08RgiYon0lJgtTa8U6g6gV0KI0IygwZhDQFJVxnrRDUdxQGIP5CKHmfQu3sk9gTNFoEA== + dependencies: + body-parser "1.20.3" + cors "2.8.5" + express "4.21.0" + multer "1.4.4-lts.1" + tslib "2.7.0" + +"@nestjs/schematics@^10.0.0", "@nestjs/schematics@^10.0.1": + version "10.1.4" + resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-10.1.4.tgz#e445b856eefce9bd338c5fc1cf2c95f0985849cf" + integrity sha512-QpY8ez9cTvXXPr3/KBrtSgXQHMSV6BkOUYy2c2TTe6cBqriEdGnCYqGl8cnfrQl3632q3lveQPaZ/c127dHsEw== + dependencies: + "@angular-devkit/core" "17.3.8" + "@angular-devkit/schematics" "17.3.8" + comment-json "4.2.3" + jsonc-parser "3.3.1" + pluralize "8.0.0" + +"@nestjs/testing@^10.0.0": + version "10.4.4" + resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.4.4.tgz#1f73f4b6c8d7a996a267ec498679e53763936963" + integrity sha512-qRGFj51A5RM7JqA8pcyEwSLA3Y0dle/PAZ8oxP0suimoCusRY3Tk7wYqutZdCNj1ATb678SDaUZDHk2pwSv9/g== + dependencies: + tslib "2.7.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" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@nuxtjs/opencollective@0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz#620ce1044f7ac77185e825e1936115bb38e2681c" + integrity sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA== + dependencies: + chalk "^4.1.0" + consola "^2.15.0" + node-fetch "^2.6.1" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@pkgr/core@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cookiejar@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" + integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== + +"@types/estree@^1.0.5": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/express-serve-static-core@^4.17.33": + version "4.19.6" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" + integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.17": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.2": + version "29.5.13" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.13.tgz#8bc571659f401e6a719a7bf0dbcb8b78c71a8adc" + integrity sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.8": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/methods@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" + integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/node@*": + version "22.7.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.3.tgz#7ddf1ddf13078692b4cfadb835852b2a718ee1ef" + integrity sha512-qXKfhXXqGTyBskvWEzJZPUxSslAiLaB6JGP1ic/XTH9ctGgzdgYguuLP1C601aRTSDNlLb0jbKqXjZ48GNraSA== + dependencies: + undici-types "~6.19.2" + +"@types/node@^20.3.1": + version "20.16.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.9.tgz#1217c6cc77c4f3aaf4a6c76fb56b790e81e48120" + integrity sha512-rkvIVJxsOfBejxK7I0FO5sa2WxFmJCzoDwcd88+fq/CUfynNywTo/1/T6hyFz22CyztsnLS9nVlHOnTI36RH5w== + dependencies: + undici-types "~6.19.2" + +"@types/qs@*": + version "6.9.16" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.16.tgz#52bba125a07c0482d26747d5d4947a64daf8f794" + integrity sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/superagent@^8.1.0": + version "8.1.9" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.9.tgz#28bfe4658e469838ed0bf66d898354bcab21f49f" + integrity sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ== + dependencies: + "@types/cookiejar" "^2.1.5" + "@types/methods" "^1.1.4" + "@types/node" "*" + form-data "^4.0.0" + +"@types/supertest@^6.0.0": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-6.0.2.tgz#2af1c466456aaf82c7c6106c6b5cbd73a5e86588" + integrity sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg== + dependencies: + "@types/methods" "^1.1.4" + "@types/superagent" "^8.1.0" + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^8.0.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz#d0070f206daad26253bf00ca5b80f9b54f9e2dd0" + integrity sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.7.0" + "@typescript-eslint/type-utils" "8.7.0" + "@typescript-eslint/utils" "8.7.0" + "@typescript-eslint/visitor-keys" "8.7.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^8.0.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.7.0.tgz#a567b0890d13db72c7348e1d88442ea8ab4e9173" + integrity sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ== + dependencies: + "@typescript-eslint/scope-manager" "8.7.0" + "@typescript-eslint/types" "8.7.0" + "@typescript-eslint/typescript-estree" "8.7.0" + "@typescript-eslint/visitor-keys" "8.7.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz#90ee7bf9bc982b9260b93347c01a8bc2b595e0b8" + integrity sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg== + dependencies: + "@typescript-eslint/types" "8.7.0" + "@typescript-eslint/visitor-keys" "8.7.0" + +"@typescript-eslint/type-utils@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz#d56b104183bdcffcc434a23d1ce26cde5e42df93" + integrity sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ== + dependencies: + "@typescript-eslint/typescript-estree" "8.7.0" + "@typescript-eslint/utils" "8.7.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.7.0.tgz#21d987201c07b69ce7ddc03451d7196e5445ad19" + integrity sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w== + +"@typescript-eslint/typescript-estree@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz#6c7db6baa4380b937fa81466c546d052f362d0e8" + integrity sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg== + dependencies: + "@typescript-eslint/types" "8.7.0" + "@typescript-eslint/visitor-keys" "8.7.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.7.0.tgz#cef3f70708b5b5fd7ed8672fc14714472bd8a011" + integrity sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.7.0" + "@typescript-eslint/types" "8.7.0" + "@typescript-eslint/typescript-estree" "8.7.0" + +"@typescript-eslint/visitor-keys@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz#5e46f1777f9d69360a883c1a56ac3c511c9659a8" + integrity sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ== + dependencies: + "@typescript-eslint/types" "8.7.0" + eslint-visitor-keys "^3.4.3" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" + +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + +ajv-formats@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@8.12.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-colors@4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-timsort@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-timsort/-/array-timsort-1.0.3.tgz#3c9e4199e54fb2b9c3fe5976396a21614ef0d926" + integrity sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ== + +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.13.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.21.10, browserslist@^4.23.1: + version "4.24.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" + integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== + dependencies: + caniuse-lite "^1.0.30001663" + electron-to-chromium "^1.5.28" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +busboy@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001663: + version "1.0.30001664" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz#d588d75c9682d3301956b05a3749652a80677df4" + integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g== + +chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chokidar@3.6.0, chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0: + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + +cli-table3@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +comment-json@4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.2.3.tgz#50b487ebbf43abe44431f575ebda07d30d015365" + integrity sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw== + dependencies: + array-timsort "^1.0.3" + core-util-is "^1.0.3" + esprima "^4.0.1" + has-own-prop "^2.0.0" + repeat-string "^1.6.1" + +component-emitter@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" + integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +consola@^2.15.0: + version "2.15.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" + integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +cookiejar@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== + +core-util-is@^1.0.3, core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cors@2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +cosmiconfig@^8.2.0: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +dezalgo@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.28: + version "1.5.29" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz#aa592a3caa95d07cc26a66563accf99fa573a1ee" + integrity sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.2.1: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== + +escalade@^3.1.1, escalade@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" + integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== + +eslint-plugin-prettier@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz#d1c8f972d8f60e414c25465c163d16f209411f95" + integrity sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.9.1" + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.42.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +express@4.21.0: + version "4.21.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" + integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.3" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.3.1" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.3" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.10" + proxy-addr "~2.0.7" + qs "6.13.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.19.0" + serve-static "1.16.2" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +external-editor@^3.0.3, external-editor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fast-uri@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.2.tgz#d78b298cf70fd3b752fd951175a3da6a7b48f024" + integrity sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +figures@^3.0.0, figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== + dependencies: + debug "2.6.9" + encodeurl "~2.0.0" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +fork-ts-checker-webpack-plugin@9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz#c12c590957837eb02b02916902dcf3e675fd2b1e" + integrity sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg== + dependencies: + "@babel/code-frame" "^7.16.7" + chalk "^4.1.2" + chokidar "^3.5.3" + cosmiconfig "^8.2.0" + deepmerge "^4.2.2" + fs-extra "^10.0.0" + memfs "^3.4.1" + minimatch "^3.0.4" + node-abort-controller "^3.0.1" + schema-utils "^3.1.1" + semver "^7.3.5" + tapable "^2.2.1" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formidable@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.1.tgz#9360a23a656f261207868b1484624c4c8d06ee1a" + integrity sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og== + dependencies: + dezalgo "^1.0.4" + hexoid "^1.0.0" + once "^1.4.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-monkey@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" + integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@10.4.2: + version "10.4.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.2.tgz#bed6b95dade5c1f80b4434daced233aee76160e5" + integrity sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +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.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== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-own-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-own-prop/-/has-own-prop-2.0.0.tgz#f0f95d58f65804f5d218db32563bb85b8e0417af" + integrity sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hasown@^2.0.0, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hexoid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" + integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24, iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1, import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@8.2.6: + version "8.2.6" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.6.tgz#733b74888195d8d400a67ac332011b5fae5ea562" + integrity sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^6.0.1" + +inquirer@9.2.15: + version "9.2.15" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.2.15.tgz#2135a36190a6e5c92f5d205e0af1fea36b9d3492" + integrity sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg== + dependencies: + "@ljharb/through" "^2.3.12" + ansi-escapes "^4.3.2" + chalk "^5.3.0" + cli-cursor "^3.1.0" + cli-width "^4.1.0" + external-editor "^3.1.0" + figures "^3.2.0" + lodash "^4.17.21" + mute-stream "1.0.0" + ora "^5.4.1" + run-async "^3.0.0" + rxjs "^7.8.1" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.13.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +iterare@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" + integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.5.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc-parser@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== + +jsonc-parser@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" + integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +magic-string@0.30.8: + version "0.30.8" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.8.tgz#14e8624246d2bedba70d5462aa99ac9681844613" + integrity sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^3.4.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== + dependencies: + fs-monkey "^1.0.4" + +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@^1.1.2, methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.0, micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mkdirp@^0.5.4: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multer@1.4.4-lts.1: + version "1.4.4-lts.1" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.4-lts.1.tgz#24100f701a4611211cfae94ae16ea39bb314e04d" + integrity sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg== + dependencies: + append-field "^1.0.0" + busboy "^1.0.0" + concat-stream "^1.5.2" + mkdirp "^0.5.4" + object-assign "^4.1.1" + type-is "^1.6.4" + xtend "^4.0.0" + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +mute-stream@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-abort-controller@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" + integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== + +node-emoji@1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== + dependencies: + lodash "^4.17.21" + +node-fetch@^2.6.1: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +ora@5.4.1, ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== + +path-to-regexp@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" + integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + +picomatch@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.1.tgz#68c26c8837399e5819edce48590412ea07f17a07" + integrity sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pluralize@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.0.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +qs@6.13.0, qs@^6.11.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +readable-stream@^2.2.2: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reflect-metadata@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + +resolve@^1.20.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-async@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad" + integrity sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@7.8.1, rxjs@^7.5.5, rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.4, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.19.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@^0.5.21, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@0.7.4, source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +superagent@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-9.0.2.tgz#a18799473fc57557289d6b63960610e358bdebc1" + integrity sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w== + dependencies: + component-emitter "^1.3.0" + cookiejar "^2.1.4" + debug "^4.3.4" + fast-safe-stringify "^2.1.1" + form-data "^4.0.0" + formidable "^3.5.1" + methods "^1.1.2" + mime "2.6.0" + qs "^6.11.0" + +supertest@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-7.0.0.tgz#cac53b3d6872a0b317980b2b0cfa820f09cd7634" + integrity sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA== + dependencies: + methods "^1.1.2" + superagent "^9.0.1" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +symbol-observable@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" + integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== + +synckit@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.1.tgz#febbfbb6649979450131f64735aa3f6c14575c88" + integrity sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A== + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + +tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.20" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.26.0" + +terser@^5.26.0: + version "5.34.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.34.0.tgz#62f2496542290bc6d8bf886edaee7fac158e37e4" + integrity sha512-y5NUX+U9HhVsK/zihZwoq4r9dICLyV2jXGOriDAVOeKhq3LKVjgJbGO90FisozXLlJfvjHqgckGmJFBb9KYoWQ== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +tree-kill@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +ts-jest@^29.1.0: + version "29.2.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" + +ts-loader@^9.4.3: + version "9.5.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.1.tgz#63d5912a86312f1fbe32cef0859fb8b2193d9b89" + integrity sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + source-map "^0.7.4" + +ts-node@^10.9.1: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsconfig-paths-webpack-plugin@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz#3c6892c5e7319c146eee1e7302ed9e6f2be4f763" + integrity sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.7.0" + tsconfig-paths "^4.1.2" + +tsconfig-paths@4.2.0, tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@2.7.0, tslib@^2.1.0, tslib@^2.6.2: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-is@^1.6.4, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + +typescript@5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + +typescript@^5.1.3: + version "5.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== + +uid@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/uid/-/uid-2.0.2.tgz#4b5782abf0f2feeefc00fa88006b2b3b7af3e3b9" + integrity sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g== + dependencies: + "@lukeed/csprng" "^1.0.0" + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +webpack-node-externals@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917" + integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ== + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@5.94.0: + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== + dependencies: + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" + acorn "^8.7.1" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.1" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@21.1.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + 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.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 2795c77648b02c813795d9014bf0e54aa5ecd22b Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 8 Oct 2024 17:14:28 +0200 Subject: [PATCH 03/54] add healthz controller --- api-node/src/app.controller.ts | 12 -------- api-node/src/app.module.ts | 7 ++--- api-node/src/app.service.ts | 8 ------ api-node/src/health/healthCheck.controller.ts | 11 ++++++++ api-node/test/healthCheck.e2e-spec.ts | 28 +++++++++++++++++++ api-node/test/packages.e2e-spec.ts | 3 +- api-node/test/utils.ts | 2 ++ 7 files changed, 45 insertions(+), 26 deletions(-) delete mode 100644 api-node/src/app.controller.ts delete mode 100644 api-node/src/app.service.ts create mode 100644 api-node/src/health/healthCheck.controller.ts create mode 100644 api-node/test/healthCheck.e2e-spec.ts create mode 100644 api-node/test/utils.ts diff --git a/api-node/src/app.controller.ts b/api-node/src/app.controller.ts deleted file mode 100644 index 7daccf7a6..000000000 --- a/api-node/src/app.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { AppService } from './app.service'; - -@Controller() -export class AppController { - constructor(private readonly appService: AppService) {} - - @Get('/healthz') - getHealthCheck(): string { - return 'ok'; - } -} diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index b0a98e271..53060b23d 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -1,12 +1,11 @@ import { Module } from '@nestjs/common'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; import { PackagesController } from './packages/packages.controller'; import { PackagesService } from './packages/packages.service'; +import { HealthCheckController } from './health/healthCheck.controller'; @Module({ imports: [], - controllers: [AppController, PackagesController], - providers: [AppService, PackagesService], + controllers: [HealthCheckController, PackagesController], + providers: [PackagesService], }) export class AppModule {} diff --git a/api-node/src/app.service.ts b/api-node/src/app.service.ts deleted file mode 100644 index 927d7cca0..000000000 --- a/api-node/src/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AppService { - getHello(): string { - return 'Hello World!'; - } -} diff --git a/api-node/src/health/healthCheck.controller.ts b/api-node/src/health/healthCheck.controller.ts new file mode 100644 index 000000000..938dd3348 --- /dev/null +++ b/api-node/src/health/healthCheck.controller.ts @@ -0,0 +1,11 @@ +import { Controller, Get } from '@nestjs/common'; + +@Controller('/healthz') +export class HealthCheckController { + constructor() {} + + @Get() + getHealthCheck(): string { + return 'ok\n'; + } +} diff --git a/api-node/test/healthCheck.e2e-spec.ts b/api-node/test/healthCheck.e2e-spec.ts new file mode 100644 index 000000000..60013cf3b --- /dev/null +++ b/api-node/test/healthCheck.e2e-spec.ts @@ -0,0 +1,28 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from './../src/app.module'; +import { PYTHON_API_URL } from './utils'; + +describe('PackagesController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/healthz (GET)', async () => { + const pythonApiResponse = await fetch(`${PYTHON_API_URL}/healthz`); + const pythonApiData = await pythonApiResponse.text(); + + return request(app.getHttpServer()) + .get('/healthz') + .expect(200) + .expect(pythonApiData); + }); +}); diff --git a/api-node/test/packages.e2e-spec.ts b/api-node/test/packages.e2e-spec.ts index ad01a792e..a37e35ff9 100644 --- a/api-node/test/packages.e2e-spec.ts +++ b/api-node/test/packages.e2e-spec.ts @@ -3,8 +3,7 @@ import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; -const PYTHON_API_PORT = 5031; -const PYTHON_API_URL = `http://localhost:${PYTHON_API_PORT}`; +import { PYTHON_API_URL } from './utils'; describe('PackagesController (e2e)', () => { let app: INestApplication; diff --git a/api-node/test/utils.ts b/api-node/test/utils.ts new file mode 100644 index 000000000..e8eab428a --- /dev/null +++ b/api-node/test/utils.ts @@ -0,0 +1,2 @@ +const PYTHON_API_PORT = 5031; +export const PYTHON_API_URL = `http://localhost:${PYTHON_API_PORT}`; From 2ab1ab0f58a50859142cab88fb3fa0253a8596ce Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 8 Oct 2024 17:38:54 +0200 Subject: [PATCH 04/54] add marketing-slug endpoint --- api-node/src/app.module.ts | 6 ++-- .../src/marketing/marketing.controller.ts | 20 +++++++++++++ api-node/src/marketing/marketing.service.ts | 19 ++++++++++++ api-node/src/marketing/types.ts | 9 ++++++ api-node/test/healthCheck.e2e-spec.ts | 2 +- api-node/test/marketing.e2e-spec.ts | 30 +++++++++++++++++++ 6 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 api-node/src/marketing/marketing.controller.ts create mode 100644 api-node/src/marketing/marketing.service.ts create mode 100644 api-node/src/marketing/types.ts create mode 100644 api-node/test/marketing.e2e-spec.ts diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index 53060b23d..8d6a62a78 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -2,10 +2,12 @@ import { Module } from '@nestjs/common'; import { PackagesController } from './packages/packages.controller'; import { PackagesService } from './packages/packages.service'; import { HealthCheckController } from './health/healthCheck.controller'; +import { MarketingController } from './marketing/marketing.controller'; +import { MarketingService } from './marketing/marketing.service'; @Module({ imports: [], - controllers: [HealthCheckController, PackagesController], - providers: [PackagesService], + controllers: [HealthCheckController, PackagesController, MarketingController], + providers: [PackagesService, MarketingService], }) export class AppModule {} diff --git a/api-node/src/marketing/marketing.controller.ts b/api-node/src/marketing/marketing.controller.ts new file mode 100644 index 000000000..b5348193d --- /dev/null +++ b/api-node/src/marketing/marketing.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { MarketingService } from './marketing.service'; +import { MarketingSlugResponse } from './types'; + +@Controller('marketing-slugs') +export class MarketingController { + constructor(private readonly marketingService: MarketingService) {} + + @Get() + getMarketingSlugs(): MarketingSlugResponse { + return this.marketingService.getMarketingSlugs(); + } + + @Get(':slug') + resolveMarketingSlug(@Param('slug') slug: string): string { + // TODO: This needs to be implemented but we need other functionality first + // return this.marketingService.resolveMarketingSlug(slug); + return 'ok'; + } +} diff --git a/api-node/src/marketing/marketing.service.ts b/api-node/src/marketing/marketing.service.ts new file mode 100644 index 000000000..785ede73c --- /dev/null +++ b/api-node/src/marketing/marketing.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import * as fs from 'fs'; +import * as path from 'path'; +import { MarketingSlugResponse } from './types'; + +@Injectable() +export class MarketingService { + #slugs: Record; + + constructor() { + this.#slugs = JSON.parse( + fs.readFileSync(path.join('..', 'misc', 'marketing-slugs.json'), 'utf8'), + ); + } + + getMarketingSlugs(): MarketingSlugResponse { + return { slugs: Object.keys(this.#slugs) }; + } +} diff --git a/api-node/src/marketing/types.ts b/api-node/src/marketing/types.ts new file mode 100644 index 000000000..aea45bcf0 --- /dev/null +++ b/api-node/src/marketing/types.ts @@ -0,0 +1,9 @@ +export interface MarketingSlugEntry { + type: string; + target: string; + integration: string; +} + +export interface MarketingSlugResponse { + slugs: string[]; +} diff --git a/api-node/test/healthCheck.e2e-spec.ts b/api-node/test/healthCheck.e2e-spec.ts index 60013cf3b..98e32b053 100644 --- a/api-node/test/healthCheck.e2e-spec.ts +++ b/api-node/test/healthCheck.e2e-spec.ts @@ -4,7 +4,7 @@ import * as request from 'supertest'; import { AppModule } from './../src/app.module'; import { PYTHON_API_URL } from './utils'; -describe('PackagesController (e2e)', () => { +describe('HealthCheckController (e2e)', () => { let app: INestApplication; beforeEach(async () => { diff --git a/api-node/test/marketing.e2e-spec.ts b/api-node/test/marketing.e2e-spec.ts new file mode 100644 index 000000000..630df250d --- /dev/null +++ b/api-node/test/marketing.e2e-spec.ts @@ -0,0 +1,30 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from './../src/app.module'; +import { PYTHON_API_URL } from './utils'; + +describe('MarketingController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/marketing-slugs (GET)', async () => { + const pythonApiResponse = await fetch(`${PYTHON_API_URL}/marketing-slugs`); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get('/marketing-slugs') + .expect((res) => { + expect(res.status).toBe(200); + expect(res.body.slugs.sort()).toEqual(pythonApiData.slugs.sort()); + }); + }); +}); From 4d74a6e268e6cdeef50427cbf89eafde74dac922 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 8 Oct 2024 17:39:31 +0200 Subject: [PATCH 05/54] cleanup --- api-node/src/app.controller.spec.ts | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 api-node/src/app.controller.spec.ts diff --git a/api-node/src/app.controller.spec.ts b/api-node/src/app.controller.spec.ts deleted file mode 100644 index d22f3890a..000000000 --- a/api-node/src/app.controller.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; - -describe('AppController', () => { - let appController: AppController; - - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); - - appController = app.get(AppController); - }); - - describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); - }); - }); -}); From 58f76c407e71c87e47c03bb876b19fefd148b56e Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 9 Oct 2024 13:57:53 +0200 Subject: [PATCH 06/54] add /apps/:appId/:version endpoint w/ extended functionality --- api-node/package.json | 2 +- api-node/src/app.module.ts | 11 +- api-node/src/apps/apps.controller.ts | 64 +++++++++ api-node/src/apps/apps.service.ts | 94 +++++++++++++ api-node/src/apps/types.ts | 12 ++ api-node/src/packages/packages.service.ts | 14 +- api-node/test/app.e2e-spec.ts | 21 --- api-node/test/apps.e2e-spec.ts | 154 ++++++++++++++++++++++ api-node/yarn.lock | 19 +-- 9 files changed, 351 insertions(+), 40 deletions(-) create mode 100644 api-node/src/apps/apps.controller.ts create mode 100644 api-node/src/apps/apps.service.ts create mode 100644 api-node/src/apps/types.ts delete mode 100644 api-node/test/app.e2e-spec.ts create mode 100644 api-node/test/apps.e2e-spec.ts diff --git a/api-node/package.json b/api-node/package.json index e9b816dbf..806bc0885 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -30,7 +30,7 @@ "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", - "@types/express": "^4.17.17", + "@types/express": "^5.0.0", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^6.0.0", diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index 8d6a62a78..7bfe4eb3b 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -4,10 +4,17 @@ import { PackagesService } from './packages/packages.service'; import { HealthCheckController } from './health/healthCheck.controller'; import { MarketingController } from './marketing/marketing.controller'; import { MarketingService } from './marketing/marketing.service'; +import { AppsController } from './apps/apps.controller'; +import { AppsService } from './apps/apps.service'; @Module({ imports: [], - controllers: [HealthCheckController, PackagesController, MarketingController], - providers: [PackagesService, MarketingService], + controllers: [ + HealthCheckController, + PackagesController, + MarketingController, + AppsController, + ], + providers: [PackagesService, MarketingService, AppsService], }) export class AppModule {} diff --git a/api-node/src/apps/apps.controller.ts b/api-node/src/apps/apps.controller.ts new file mode 100644 index 000000000..b1e630003 --- /dev/null +++ b/api-node/src/apps/apps.controller.ts @@ -0,0 +1,64 @@ +import { Controller, Get, Param, Query, Res } from '@nestjs/common'; + +import { AppsService } from './apps.service'; +import { AppsResponse } from './types'; + +import type { Response } from 'express'; + +@Controller('apps') +export class AppsController { + constructor(private appsService: AppsService) {} + + @Get() + getApps(): AppsResponse { + return this.appsService.getApps(); + } + + @Get(':appId/:version') + getAppVersion( + @Res() res: Response, + @Param('appId') appId: string, + @Param('version') version: string, + @Query('response') response?: string, + @Query('arch') arch?: string, + @Query('platform') platform?: string, + @Query('package') pkgName?: string, + ): void { + const appInfo = this.appsService.getApp(appId, version); + + if (!appInfo) { + res.status(404).send('App not found'); + return; + } + + if (response === 'download') { + if (!arch || !platform || !pkgName) { + res.status(400).send('Missing required query parameters'); + return; + } + + const url = this.appsService.findDownloadUrl( + appInfo, + pkgName, + arch, + platform, + ); + if (!url) { + res.status(404).send('Download URL not found'); + return; + } + + const checksums = this.appsService.getUrlChecksums(appInfo, url); + const digest = this.appsService.makeDigest(checksums); + + res.setHeader('Location', url); + if (digest) { + res.setHeader('Digest', digest); + } + res.status(302).send(); + return; + } + + res.json(appInfo); + } +} diff --git a/api-node/src/apps/apps.service.ts b/api-node/src/apps/apps.service.ts new file mode 100644 index 000000000..cb2ed90b5 --- /dev/null +++ b/api-node/src/apps/apps.service.ts @@ -0,0 +1,94 @@ +import { Injectable } from '@nestjs/common'; +import * as fs from 'fs'; +import * as path from 'path'; +import { AppEntry, AppsResponse } from './types'; + +const APPS_PATH = path.join('..', 'apps'); + +@Injectable() +export class AppsService { + getApps(): AppsResponse { + try { + const apps = fs.readdirSync(APPS_PATH); + return apps.reduce((acc, appName) => { + try { + const app = this.getApp(appName); + if (app) { + acc[appName] = app; + } + } catch { + // Continue to next iteration if there's an error + } + return acc; + }, {}); + } catch (error) { + // Handle error (e.g., log it or throw a custom exception) + console.error('Error reading apps directory:', error); + } + } + + getApp(appId: string, version: string = 'latest'): AppEntry | null { + try { + const filePath = path.join(APPS_PATH, appId, `${version}.json`); + const fileContent = fs.readFileSync(filePath, 'utf8'); + return JSON.parse(fileContent); + } catch { + return null; + } + } + + findDownloadUrl( + appInfo: AppEntry, + pkgName: string, + arch: string, + platform: string, + ): string | null { + const normalizedPackage = pkgName + .replace(/_/g, '-') + .toLowerCase() + .split('-'); + for (const [_, url] of Object.entries(appInfo.file_urls)) { + let normalizedUrl = url.toLowerCase(); + if (normalizedUrl.endsWith('.exe')) { + normalizedUrl = normalizedUrl.slice(0, -4); + } + const parts = normalizedUrl.split('/').pop()!.split('-'); + if ( + parts.length > 2 && + parts.slice(0, -2).join('-') === normalizedPackage.join('-') && + parts[parts.length - 1].replace(/_/g, '-') === + arch.toLowerCase().replace(/_/g, '-') && + parts[parts.length - 2] === platform.toLowerCase() + ) { + return url; + } + } + return null; + } + + getUrlChecksums( + appInfo: AppEntry, + url: string, + ): Record | null { + for (const fileInfo of Object.values(appInfo.files)) { + if (fileInfo.url === url) { + return (fileInfo as any).checksums || null; + } + } + return null; + } + + makeDigest(checksums: Record | null): string { + if (!checksums) return ''; + const digestParts: string[] = []; + for (const [algo, value] of Object.entries(checksums)) { + if (algo.endsWith('-hex')) { + const base64Value = Buffer.from(value, 'hex').toString('base64'); + digestParts.push(`${algo.slice(0, -4)}=${base64Value}`); + } else if (algo.endsWith('-base64')) { + digestParts.push(`${algo.slice(0, -7)}=${value}`); + } + } + return digestParts.join(','); + } +} diff --git a/api-node/src/apps/types.ts b/api-node/src/apps/types.ts new file mode 100644 index 000000000..b9485e6c2 --- /dev/null +++ b/api-node/src/apps/types.ts @@ -0,0 +1,12 @@ +export interface AppEntry { + name: string; + canonical: string; + version: string; + repo_url: string; + main_docs_url: string; + file_urls: Record; + created_at: string; + files: Record }>; +} + +export type AppsResponse = Record; diff --git a/api-node/src/packages/packages.service.ts b/api-node/src/packages/packages.service.ts index df8ee3a32..2a3bac8e9 100644 --- a/api-node/src/packages/packages.service.ts +++ b/api-node/src/packages/packages.service.ts @@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; +const PACKAGES_PATH = path.join('..', 'packages'); + @Injectable() export class PackagesService { #packages: string[]; @@ -74,16 +76,14 @@ const NAMESPACE_FILE_MARKER = '__NAMESPACE__'; function getPackageDir(canonicalPackageName: string) { const [registry, name] = canonicalPackageName.split(':', 2); const pkgPath = name.replace(':', path.sep).split(path.sep); - return path.resolve(path.join('..', 'packages', registry, ...pkgPath)); + return path.resolve(path.join(PACKAGES_PATH, registry, ...pkgPath)); } function* iterPackages() { - const packagesPath = '../packages'; - // Loop through each package registry - const packageRegistries = fs.readdirSync(packagesPath); + const packageRegistries = fs.readdirSync(PACKAGES_PATH); for (const packageRegistry of packageRegistries) { - const registryPath = path.join(packagesPath, packageRegistry); + const registryPath = path.join(PACKAGES_PATH, packageRegistry); // bail if registry path is not a dir if (!fs.lstatSync(registryPath).isDirectory()) { @@ -94,7 +94,7 @@ function* iterPackages() { const items = fs.readdirSync(registryPath); for (const item of items) { const namespaceFilePath = path.join( - packagesPath, + PACKAGES_PATH, packageRegistry, item, NAMESPACE_FILE_MARKER, @@ -103,7 +103,7 @@ function* iterPackages() { // Check if the NAMESPACE_FILE_MARKER exists if (fs.existsSync(namespaceFilePath)) { const subItems = fs.readdirSync( - path.join(packagesPath, packageRegistry, item), + path.join(PACKAGES_PATH, packageRegistry, item), ); // Yield subitems, excluding NAMESPACE_FILE_MARKER diff --git a/api-node/test/app.e2e-spec.ts b/api-node/test/app.e2e-spec.ts deleted file mode 100644 index 83986d826..000000000 --- a/api-node/test/app.e2e-spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; - -describe('AppController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it('/ (GET)', () => { - return request(app.getHttpServer()).get('/').expect(404); - }); -}); diff --git a/api-node/test/apps.e2e-spec.ts b/api-node/test/apps.e2e-spec.ts new file mode 100644 index 000000000..d90e3bfb5 --- /dev/null +++ b/api-node/test/apps.e2e-spec.ts @@ -0,0 +1,154 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from './../src/app.module'; +import { PYTHON_API_URL } from './utils'; + +describe('AppsController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/apps (GET)', async () => { + const pythonApiResponse = await fetch(`${PYTHON_API_URL}/apps`); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get('/apps') + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); + + describe('/apps/:appId/:version (GET)', () => { + it('no query params, with latest version', async () => { + const appId = 'sentry-cli'; + const version = 'latest'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/apps/${appId}/${version}`, + ); + + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get(`/apps/${appId}/${version}`) + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); + + it('no query params, with fixed version', async () => { + const appId = 'sentry-cli'; + const version = '2.0.0'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/apps/${appId}/${version}`, + ); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get(`/apps/${appId}/${version}`) + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); + + it('with response=download', async () => { + const appId = 'sentry-cli'; + const version = 'latest'; + const arch = 'x86_64'; + const platform = 'linux'; + const pkgName = 'sentry-cli'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/apps/${appId}/${version}?response=download&arch=${arch}&platform=${platform}&package=${pkgName}`, + { redirect: 'manual' }, + ); + + expect(pythonApiResponse.status).toEqual(302); + + return request(app.getHttpServer()) + .get(`/apps/${appId}/${version}`) + .query({ response: 'download', arch, platform, package: pkgName }) + .expect((r) => { + expect(r.status).toEqual(302); + expect(r.header.location).toEqual( + pythonApiResponse.headers.get('location'), + ); + expect(r.header.digest).toEqual( + pythonApiResponse.headers.get('digest'), + ); + }); + }); + + it('with invalid appId', async () => { + const appId = 'invalid-app'; + const version = 'latest'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/apps/${appId}/${version}`, + ); + + expect(pythonApiResponse.status).toEqual(404); + + return request(app.getHttpServer()) + .get(`/apps/${appId}/${version}`) + .expect((r) => { + expect(r.status).toEqual(404); + expect(r.text).toEqual('App not found'); + }); + }); + + it('with response=download and missing parameters', async () => { + const appId = 'sentry-cli'; + const version = 'latest'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/apps/${appId}/${version}?response=download`, + ); + + expect(pythonApiResponse.status).toEqual(400); + + return request(app.getHttpServer()) + .get(`/apps/${appId}/${version}`) + .query({ response: 'download' }) + .expect((r) => { + expect(r.status).toEqual(400); + expect(r.text).toEqual('Missing required query parameters'); + }); + }); + + it('with response=download and invalid parameters', async () => { + const appId = 'sentry-cli'; + const version = 'latest'; + const arch = 'invalid-arch'; + const platform = 'invalid-platform'; + const pkgName = 'invalid-package'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/apps/${appId}/${version}?response=download&arch=${arch}&platform=${platform}&package=${pkgName}`, + ); + + expect(pythonApiResponse.status).toEqual(404); + + return request(app.getHttpServer()) + .get(`/apps/${appId}/${version}`) + .query({ response: 'download', arch, platform, package: pkgName }) + .expect((r) => { + expect(r.status).toEqual(404); + expect(r.text).toEqual('Download URL not found'); + }); + }); + }); +}); diff --git a/api-node/yarn.lock b/api-node/yarn.lock index d087e5846..bc95a8a51 100644 --- a/api-node/yarn.lock +++ b/api-node/yarn.lock @@ -881,23 +881,23 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== -"@types/express-serve-static-core@^4.17.33": - version "4.19.6" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" - integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== +"@types/express-serve-static-core@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz#91f06cda1049e8f17eeab364798ed79c97488a1c" + integrity sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" "@types/send" "*" -"@types/express@^4.17.17": - version "4.17.21" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" - integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== +"@types/express@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.0.tgz#13a7d1f75295e90d19ed6e74cab3678488eaa96c" + integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ== dependencies: "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" + "@types/express-serve-static-core" "^5.0.0" "@types/qs" "*" "@types/serve-static" "*" @@ -4765,6 +4765,7 @@ word-wrap@^1.2.5: integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From dadc043d6c5527c9cacaeca38621437fa74452d7 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 9 Oct 2024 14:01:18 +0200 Subject: [PATCH 07/54] improve tests --- api-node/test/apps.e2e-spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api-node/test/apps.e2e-spec.ts b/api-node/test/apps.e2e-spec.ts index d90e3bfb5..cbf901928 100644 --- a/api-node/test/apps.e2e-spec.ts +++ b/api-node/test/apps.e2e-spec.ts @@ -89,6 +89,15 @@ describe('AppsController (e2e)', () => { expect(r.header.digest).toEqual( pythonApiResponse.headers.get('digest'), ); + expect(r.header.location).toEqual( + pythonApiResponse.headers.get('location'), + ); + expect(r.header.location).toEqual( + 'https://downloads.sentry-cdn.com/sentry-cli/2.36.3/sentry-cli-Linux-x86_64', + ); + expect(r.header.digest).toEqual( + 'sha256=8cs/OTYjDCCuSrIIAmFPoggGPWI599KeBquwkXqTQRg=', + ); }); }); From 3e6061181fe27f086b87abf74cb935141bfd7133 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 14 Oct 2024 17:56:50 +0200 Subject: [PATCH 08/54] add sdks endpoints --- api-node/package.json | 3 +- api-node/src/app.module.ts | 5 +- api-node/src/common/packageUtils.ts | 16 +++ api-node/src/packages/packages.service.ts | 22 ++-- api-node/src/sdks/sdks.controller.ts | 28 +++++ api-node/src/sdks/sdks.service.ts | 61 +++++++++++ api-node/src/sdks/types.ts | 17 +++ api-node/test/sdks.e2e-spec.ts | 120 ++++++++++++++++++++++ 8 files changed, 256 insertions(+), 16 deletions(-) create mode 100644 api-node/src/common/packageUtils.ts create mode 100644 api-node/src/sdks/sdks.controller.ts create mode 100644 api-node/src/sdks/sdks.service.ts create mode 100644 api-node/src/sdks/types.ts create mode 100644 api-node/test/sdks.e2e-spec.ts diff --git a/api-node/package.json b/api-node/package.json index 806bc0885..33204b76f 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -17,7 +17,8 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "start:python-api": "docker build -t registry-api-server ../ && docker run -p 5031:5030 registry-api-server" }, "dependencies": { "@nestjs/common": "^10.0.0", diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index 7bfe4eb3b..26c181c41 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -6,6 +6,8 @@ import { MarketingController } from './marketing/marketing.controller'; import { MarketingService } from './marketing/marketing.service'; import { AppsController } from './apps/apps.controller'; import { AppsService } from './apps/apps.service'; +import { SdksController } from './sdks/sdks.controller'; +import { SdksService } from './sdks/sdks.service'; @Module({ imports: [], @@ -14,7 +16,8 @@ import { AppsService } from './apps/apps.service'; PackagesController, MarketingController, AppsController, + SdksController, ], - providers: [PackagesService, MarketingService, AppsService], + providers: [PackagesService, MarketingService, AppsService, SdksService], }) export class AppModule {} diff --git a/api-node/src/common/packageUtils.ts b/api-node/src/common/packageUtils.ts new file mode 100644 index 000000000..3e2ec60ef --- /dev/null +++ b/api-node/src/common/packageUtils.ts @@ -0,0 +1,16 @@ +import * as path from 'path'; +import * as fs from 'fs'; + +export const PACKAGES_PATH = path.join('..', 'packages'); + +export function getPackage(packageName: string, version: string = 'latest') { + const packageDir = getPackageDirFromCanonical(packageName); + const versionFilePath = path.join(packageDir, `${version}.json`); + return JSON.parse(fs.readFileSync(versionFilePath).toString()); +} + +export function getPackageDirFromCanonical(canonicalPackageName: string) { + const [registry, name] = canonicalPackageName.split(/:(.*)/s); + const pkgPath = name.replaceAll(':', path.sep).split(path.sep); + return path.resolve(path.join(PACKAGES_PATH, registry, ...pkgPath)); +} diff --git a/api-node/src/packages/packages.service.ts b/api-node/src/packages/packages.service.ts index 2a3bac8e9..c76065d23 100644 --- a/api-node/src/packages/packages.service.ts +++ b/api-node/src/packages/packages.service.ts @@ -1,8 +1,11 @@ import { Injectable } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; - -const PACKAGES_PATH = path.join('..', 'packages'); +import { + getPackage, + getPackageDirFromCanonical, + PACKAGES_PATH, +} from '../common/packageUtils'; @Injectable() export class PackagesService { @@ -14,7 +17,7 @@ export class PackagesService { getPackages() { return this.#packages.reduce((acc, canonical) => { - const packageDir = getPackageDir(canonical); + const packageDir = getPackageDirFromCanonical(canonical); const latestFilePath = path.join(packageDir, 'latest.json'); try { @@ -33,7 +36,7 @@ export class PackagesService { } getPackageVersions(packageName: string): { latest: any; versions: string[] } { - const packageDir = getPackageDir(packageName); + const packageDir = getPackageDirFromCanonical(packageName); try { const versions = fs .readdirSync(packageDir) @@ -59,11 +62,8 @@ export class PackagesService { } getPackageByVersion(packageName: string, version: string): string { - const packageDir = getPackageDir(packageName); - const versionFilePath = path.join(packageDir, `${version}.json`); - try { - return JSON.parse(fs.readFileSync(versionFilePath).toString()); + return getPackage(packageName, version); } catch (e) { console.error(`Failed to read package by version: ${packageName}`); console.error(e); @@ -73,12 +73,6 @@ export class PackagesService { const NAMESPACE_FILE_MARKER = '__NAMESPACE__'; -function getPackageDir(canonicalPackageName: string) { - const [registry, name] = canonicalPackageName.split(':', 2); - const pkgPath = name.replace(':', path.sep).split(path.sep); - return path.resolve(path.join(PACKAGES_PATH, registry, ...pkgPath)); -} - function* iterPackages() { // Loop through each package registry const packageRegistries = fs.readdirSync(PACKAGES_PATH); diff --git a/api-node/src/sdks/sdks.controller.ts b/api-node/src/sdks/sdks.controller.ts new file mode 100644 index 000000000..bff2bce59 --- /dev/null +++ b/api-node/src/sdks/sdks.controller.ts @@ -0,0 +1,28 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { SdksService } from './sdks.service'; +import { SdkEntry, SdksResponse, SdkVersionsResponse } from './types'; + +@Controller('sdks') +export class SdksController { + constructor(private sdksService: SdksService) {} + + @Get() + getSdks(@Query('strict') strict?: string): SdksResponse { + const isStrict = + strict?.toLowerCase() === 'true' || strict === '1' || strict === 'yes'; + return this.sdksService.getSdks(isStrict); + } + + @Get('/:sdkId/versions') + getSdkVersions(@Param('sdkId') sdkId: string): SdkVersionsResponse { + return this.sdksService.getSdkVersions(sdkId); + } + + @Get('/:sdkId/:version') + getSdk( + @Param('sdkId') sdkId: string, + @Param('version') version?: string, + ): SdkEntry { + return this.sdksService.getSdk(sdkId, version); + } +} diff --git a/api-node/src/sdks/sdks.service.ts b/api-node/src/sdks/sdks.service.ts new file mode 100644 index 000000000..308b60496 --- /dev/null +++ b/api-node/src/sdks/sdks.service.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@nestjs/common'; +import * as fs from 'fs'; +import * as path from 'path'; +import { SdkEntry, SdksResponse } from './types'; +import { getPackage } from '../common/packageUtils'; +import { PackagesService } from '../packages/packages.service'; + +const SDKS_PATH = path.join('..', 'sdks'); + +@Injectable() +export class SdksService { + constructor(private packagesService: PackagesService) {} + + getSdks(strict: boolean = false): SdksResponse { + const sdks: SdksResponse = {}; + try { + const sdkLinks = fs.readdirSync(SDKS_PATH); + for (const link of sdkLinks) { + try { + const latestJsonPath = path.join(SDKS_PATH, link, 'latest.json'); + const latestJsonContent = fs.readFileSync(latestJsonPath, 'utf8'); + const { canonical } = JSON.parse(latestJsonContent); + const pkg = getPackage(canonical); + if (pkg) { + sdks[link] = pkg; + } else if (strict) { + throw new Error( + `Package ${link}, canonical cannot be resolved: ${canonical}`, + ); + } + } catch (error) { + if (strict) { + throw error; + } + // If not strict, continue to the next SDK + } + } + } catch (error) { + console.error('Error reading SDKs directory:', error); + } + return sdks; + } + + getSdk(sdkId: string, version: string = 'latest'): SdkEntry { + const sdkFilePath = path.join(SDKS_PATH, sdkId, `${version}.json`); + try { + const { canonical } = JSON.parse(fs.readFileSync(sdkFilePath, 'utf8')); + return getPackage(canonical, version); + } catch (error) { + console.error('Error reading SDK file:', error); + } + } + + getSdkVersions(sdkId: string): { latest: any; versions: string[] } { + const latest = this.getSdk(sdkId); + const { versions } = this.packagesService.getPackageVersions( + latest.canonical as string, + ); + return { latest, versions }; + } +} diff --git a/api-node/src/sdks/types.ts b/api-node/src/sdks/types.ts new file mode 100644 index 000000000..5ab1a7c40 --- /dev/null +++ b/api-node/src/sdks/types.ts @@ -0,0 +1,17 @@ +export interface SdkEntry { + name: string; + canonical: string; + version: string; + // Add other fields as needed +} + +export type SdksResponse = Record; + +export interface SdkEntry { + canonical: string; +} + +export interface SdkVersionsResponse { + latest: SdkEntry; + versions: string[]; +} diff --git a/api-node/test/sdks.e2e-spec.ts b/api-node/test/sdks.e2e-spec.ts new file mode 100644 index 000000000..59d9c7172 --- /dev/null +++ b/api-node/test/sdks.e2e-spec.ts @@ -0,0 +1,120 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from './../src/app.module'; +import { PYTHON_API_URL } from './utils'; + +describe('SdksController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + describe('/sdks (GET)', () => { + it('get without strict', async () => { + const pythonApiResponse = await fetch(`${PYTHON_API_URL}/sdks`); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get('/sdks') + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); + + it.each(['true', '1', 'yes'])('get with strict=%s', async (strict) => { + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/sdks?strict=${strict}`, + ); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get('/sdks') + .query({ strict }) + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); + }); + + describe('/sdks/:sdkId/:version (GET)', () => { + it('latest', async () => { + const sdkId = 'sentry.python'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/sdks/${sdkId}/latest`, + ); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get(`/sdks/${sdkId}/latest`) + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); + + it('specific version', async () => { + const sdkId = 'sentry.python'; + const version = '2.0.0'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/sdks/${sdkId}/${version}`, + ); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get(`/sdks/${sdkId}/${version}`) + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); + }); + + describe('/sdks/:sdkId/versions (GET)', () => { + it('python', async () => { + const sdkId = 'sentry.python'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/sdks/${sdkId}/versions`, + ); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get(`/sdks/${sdkId}/versions`) + .expect((r) => { + expect(r.status).toEqual(200); + const { versions, latest } = r.body; + expect(versions.sort()).toEqual(pythonApiData.versions.sort()); + expect(latest).toEqual(pythonApiData.latest); + }); + }); + + it('NextJS', async () => { + const sdkId = 'sentry.javascript.nextjs'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/sdks/${sdkId}/versions`, + ); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get(`/sdks/${sdkId}/versions`) + .expect((r) => { + expect(r.status).toEqual(200); + const { versions, latest } = r.body; + expect(versions.sort()).toEqual(pythonApiData.versions.sort()); + expect(latest).toEqual(pythonApiData.latest); + }); + }); + }); +}); From fa176a328e7eab77584be7f382b40757210af3c9 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 15 Oct 2024 16:44:17 +0200 Subject: [PATCH 09/54] add /aws-lambda-layers endpoint --- api-node/src/app.module.ts | 5 ++- .../aws-lambda-layers.controller.ts | 12 +++++++ api-node/src/registry/registry.service.ts | 33 +++++++++++++++++++ api-node/test/awsLambdaLayers.e2e-spec.ts | 32 ++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts create mode 100644 api-node/src/registry/registry.service.ts create mode 100644 api-node/test/awsLambdaLayers.e2e-spec.ts diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index 26c181c41..076c73c5c 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -8,6 +8,8 @@ import { AppsController } from './apps/apps.controller'; import { AppsService } from './apps/apps.service'; import { SdksController } from './sdks/sdks.controller'; import { SdksService } from './sdks/sdks.service'; +import { AwsLambdaLayersController } from './aws-lambda-layers/aws-lambda-layers.controller'; +import { RegistryService } from './registry/registry.service'; @Module({ imports: [], @@ -17,7 +19,8 @@ import { SdksService } from './sdks/sdks.service'; MarketingController, AppsController, SdksController, + AwsLambdaLayersController, ], - providers: [PackagesService, MarketingService, AppsService, SdksService], + providers: [PackagesService, MarketingService, AppsService, SdksService, RegistryService], }) export class AppModule {} diff --git a/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts b/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts new file mode 100644 index 000000000..f78788d4f --- /dev/null +++ b/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { RegistryService } from '../registry/registry.service'; + +@Controller('aws-lambda-layers') +export class AwsLambdaLayersController { + constructor(private readonly registryService: RegistryService) {} + + @Get() + async getLayers() { + return this.registryService.getAwsLambdaLayers(); + } +} diff --git a/api-node/src/registry/registry.service.ts b/api-node/src/registry/registry.service.ts new file mode 100644 index 000000000..e2a8779ce --- /dev/null +++ b/api-node/src/registry/registry.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import * as path from 'path'; +import * as fs from 'fs'; + +const AWS_LAMBDA_LAYERS_PATH = path.join('..', 'aws-lambda-layers'); + +@Injectable() +export class RegistryService { + async getAwsLambdaLayers() { + const layers: Record = {}; + const lambdaLayersDir = path.resolve(AWS_LAMBDA_LAYERS_PATH); + const runtimeDirs = fs.readdirSync(AWS_LAMBDA_LAYERS_PATH); + + try { + for (const runtime of runtimeDirs) { + if (fs.lstatSync(path.join(lambdaLayersDir, runtime)).isDirectory()) { + const latestLayerFile = path.join( + AWS_LAMBDA_LAYERS_PATH, + runtime, + 'latest.json', + ); + const content = fs.readFileSync(latestLayerFile, 'utf-8'); + const data = JSON.parse(content); + layers[data.canonical] = data; + } + } + } catch (error) { + console.error('Error reading AWS Lambda Layers directory:', error); + } + + return layers; + } +} diff --git a/api-node/test/awsLambdaLayers.e2e-spec.ts b/api-node/test/awsLambdaLayers.e2e-spec.ts new file mode 100644 index 000000000..d34a0919c --- /dev/null +++ b/api-node/test/awsLambdaLayers.e2e-spec.ts @@ -0,0 +1,32 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from './../src/app.module'; +import { PYTHON_API_URL } from './utils'; + +describe('AwsLambdaLayersController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/aws-lambda-layers (GET)', async () => { + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/aws-lambda-layers`, + ); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get('/aws-lambda-layers') + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); +}); From 026b78ec71900f82e8f8411c0ce0161291cfcf55 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 15 Oct 2024 17:04:34 +0200 Subject: [PATCH 10/54] add last /marketing-slugs/:slug endpoint --- .../src/marketing/marketing.controller.ts | 16 ++++--- api-node/src/marketing/marketing.service.ts | 47 +++++++++++++++++-- api-node/src/marketing/types.ts | 17 ++++++- api-node/test/marketing.e2e-spec.ts | 35 ++++++++++++++ 4 files changed, 104 insertions(+), 11 deletions(-) diff --git a/api-node/src/marketing/marketing.controller.ts b/api-node/src/marketing/marketing.controller.ts index b5348193d..87508fb81 100644 --- a/api-node/src/marketing/marketing.controller.ts +++ b/api-node/src/marketing/marketing.controller.ts @@ -1,6 +1,6 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Controller, Get, Param, NotFoundException } from '@nestjs/common'; import { MarketingService } from './marketing.service'; -import { MarketingSlugResponse } from './types'; +import { MarketingSlugResponse, MarketingSlugResolveResponse } from './types'; @Controller('marketing-slugs') export class MarketingController { @@ -12,9 +12,13 @@ export class MarketingController { } @Get(':slug') - resolveMarketingSlug(@Param('slug') slug: string): string { - // TODO: This needs to be implemented but we need other functionality first - // return this.marketingService.resolveMarketingSlug(slug); - return 'ok'; + resolveMarketingSlug( + @Param('slug') slug: string, + ): MarketingSlugResolveResponse { + const result = this.marketingService.resolveMarketingSlug(slug); + if (!result) { + throw new NotFoundException(); + } + return result; } } diff --git a/api-node/src/marketing/marketing.service.ts b/api-node/src/marketing/marketing.service.ts index 785ede73c..575bcadd2 100644 --- a/api-node/src/marketing/marketing.service.ts +++ b/api-node/src/marketing/marketing.service.ts @@ -1,13 +1,22 @@ import { Injectable } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; -import { MarketingSlugResponse } from './types'; +import { + MarketingSlugEntry, + MarketingSlugResolveResponse, + MarketingSlugResponse, +} from './types'; +import { SdksService } from '../sdks/sdks.service'; +import { PackagesService } from '../packages/packages.service'; @Injectable() export class MarketingService { - #slugs: Record; + #slugs: Record; - constructor() { + constructor( + private readonly sdksService: SdksService, + private readonly packagesService: PackagesService, + ) { this.#slugs = JSON.parse( fs.readFileSync(path.join('..', 'misc', 'marketing-slugs.json'), 'utf8'), ); @@ -16,4 +25,36 @@ export class MarketingService { getMarketingSlugs(): MarketingSlugResponse { return { slugs: Object.keys(this.#slugs) }; } + + resolveMarketingSlug(slug: string): MarketingSlugResolveResponse | null { + const data = this.#slugs[slug]; + if (!data) { + return null; + } + + let target = null; + if (data.type === 'sdk') { + target = this.sdksService.getSdk(data.target); + } else if (data.type === 'package') { + target = this.packagesService.getPackageByVersion(data.target, 'latest'); + } else if (data.type === 'integration') { + let pkg = null; + if (data.sdk) { + pkg = this.sdksService.getSdk(data.sdk); + } else if (data.package) { + pkg = this.packagesService.getPackageByVersion(data.package, 'latest'); + } + if (pkg) { + target = { + package: pkg, + integration: data.integration, + }; + } + } + + return { + definition: data, + target, + }; + } } diff --git a/api-node/src/marketing/types.ts b/api-node/src/marketing/types.ts index aea45bcf0..32644c0b5 100644 --- a/api-node/src/marketing/types.ts +++ b/api-node/src/marketing/types.ts @@ -1,9 +1,22 @@ export interface MarketingSlugEntry { type: string; - target: string; - integration: string; + target?: string; + integration?: string; + sdk?: string; + package?: string; } export interface MarketingSlugResponse { slugs: string[]; } + +export interface MarketingSlugResolveResponse { + definition: { + type: string; + target?: string; + integration?: string; + sdk?: string; + package?: string; + }; + target: any; +} diff --git a/api-node/test/marketing.e2e-spec.ts b/api-node/test/marketing.e2e-spec.ts index 630df250d..985e7096a 100644 --- a/api-node/test/marketing.e2e-spec.ts +++ b/api-node/test/marketing.e2e-spec.ts @@ -27,4 +27,39 @@ describe('MarketingController (e2e)', () => { expect(res.body.slugs.sort()).toEqual(pythonApiData.slugs.sort()); }); }); + + describe('/marketing-slugs/:slug (GET)', () => { + it.each(['python', 'javascript', 'browser', 'flask', 'django', 'rust'])( + 'valid slug %s', + async (slug) => { + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/marketing-slugs/${slug}`, + ); + const pythonApiData = await pythonApiResponse.json(); + + console.log(pythonApiData); + + return request(app.getHttpServer()) + .get(`/marketing-slugs/${slug}`) + .expect((res) => { + expect(res.status).toBe(200); + expect(res.body).toEqual(pythonApiData); + }); + }, + ); + + it('invalid slug', async () => { + const slug = 'invalid-slug'; + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/marketing-slugs/${slug}`, + ); + + return request(app.getHttpServer()) + .get(`/marketing-slugs/${slug}`) + .expect((res) => { + expect(res.status).toBe(pythonApiResponse.status); + expect(res.status).toBe(404); + }); + }); + }); }); From 9435503d397dd1fc8c8daf05ed592912b4e2847a Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 11:09:11 +0200 Subject: [PATCH 11/54] consolidate packages and sdks service to registry service --- api-node/src/app.module.ts | 4 +- api-node/src/marketing/marketing.service.ts | 16 +- api-node/src/packages/packages.controller.ts | 10 +- api-node/src/packages/packages.service.ts | 115 ------------- api-node/src/registry/registry.service.ts | 161 +++++++++++++++++++ api-node/src/sdks/sdks.controller.ts | 10 +- api-node/src/sdks/sdks.service.ts | 61 ------- 7 files changed, 178 insertions(+), 199 deletions(-) delete mode 100644 api-node/src/packages/packages.service.ts delete mode 100644 api-node/src/sdks/sdks.service.ts diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index 076c73c5c..a3e9d59fc 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -1,13 +1,11 @@ import { Module } from '@nestjs/common'; import { PackagesController } from './packages/packages.controller'; -import { PackagesService } from './packages/packages.service'; import { HealthCheckController } from './health/healthCheck.controller'; import { MarketingController } from './marketing/marketing.controller'; import { MarketingService } from './marketing/marketing.service'; import { AppsController } from './apps/apps.controller'; import { AppsService } from './apps/apps.service'; import { SdksController } from './sdks/sdks.controller'; -import { SdksService } from './sdks/sdks.service'; import { AwsLambdaLayersController } from './aws-lambda-layers/aws-lambda-layers.controller'; import { RegistryService } from './registry/registry.service'; @@ -21,6 +19,6 @@ import { RegistryService } from './registry/registry.service'; SdksController, AwsLambdaLayersController, ], - providers: [PackagesService, MarketingService, AppsService, SdksService, RegistryService], + providers: [MarketingService, AppsService, RegistryService], }) export class AppModule {} diff --git a/api-node/src/marketing/marketing.service.ts b/api-node/src/marketing/marketing.service.ts index 575bcadd2..1144fc116 100644 --- a/api-node/src/marketing/marketing.service.ts +++ b/api-node/src/marketing/marketing.service.ts @@ -6,17 +6,13 @@ import { MarketingSlugResolveResponse, MarketingSlugResponse, } from './types'; -import { SdksService } from '../sdks/sdks.service'; -import { PackagesService } from '../packages/packages.service'; +import { RegistryService } from '../registry/registry.service'; @Injectable() export class MarketingService { #slugs: Record; - constructor( - private readonly sdksService: SdksService, - private readonly packagesService: PackagesService, - ) { + constructor(private readonly registryService: RegistryService) { this.#slugs = JSON.parse( fs.readFileSync(path.join('..', 'misc', 'marketing-slugs.json'), 'utf8'), ); @@ -34,15 +30,15 @@ export class MarketingService { let target = null; if (data.type === 'sdk') { - target = this.sdksService.getSdk(data.target); + target = this.registryService.getSdk(data.target); } else if (data.type === 'package') { - target = this.packagesService.getPackageByVersion(data.target, 'latest'); + target = this.registryService.getPackageByVersion(data.target, 'latest'); } else if (data.type === 'integration') { let pkg = null; if (data.sdk) { - pkg = this.sdksService.getSdk(data.sdk); + pkg = this.registryService.getSdk(data.sdk); } else if (data.package) { - pkg = this.packagesService.getPackageByVersion(data.package, 'latest'); + pkg = this.registryService.getPackageByVersion(data.package, 'latest'); } if (pkg) { target = { diff --git a/api-node/src/packages/packages.controller.ts b/api-node/src/packages/packages.controller.ts index e1056a28b..dafb695e5 100644 --- a/api-node/src/packages/packages.controller.ts +++ b/api-node/src/packages/packages.controller.ts @@ -1,18 +1,18 @@ import { Controller, Get, Param } from '@nestjs/common'; -import { PackagesService } from './packages.service'; +import { RegistryService } from '../registry/registry.service'; @Controller('packages') export class PackagesController { - constructor(private packagesService: PackagesService) {} + constructor(private registryService: RegistryService) {} @Get() getPackages() { - return this.packagesService.getPackages(); + return this.registryService.getPackages(); } @Get('/:package(*)/versions') getPackageVersions(@Param('package') pgkName: string) { - return this.packagesService.getPackageVersions(pgkName); + return this.registryService.getPackageVersions(pgkName); } @Get('/:package(*)/:version') @@ -20,6 +20,6 @@ export class PackagesController { @Param('package') pkgName: string, @Param('version') version: string, ) { - return this.packagesService.getPackageByVersion(pkgName, version); + return this.registryService.getPackageByVersion(pkgName, version); } } diff --git a/api-node/src/packages/packages.service.ts b/api-node/src/packages/packages.service.ts deleted file mode 100644 index c76065d23..000000000 --- a/api-node/src/packages/packages.service.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import * as fs from 'fs'; -import * as path from 'path'; -import { - getPackage, - getPackageDirFromCanonical, - PACKAGES_PATH, -} from '../common/packageUtils'; - -@Injectable() -export class PackagesService { - #packages: string[]; - - constructor() { - this.#packages = Array.from(iterPackages()); - } - - getPackages() { - return this.#packages.reduce((acc, canonical) => { - const packageDir = getPackageDirFromCanonical(canonical); - const latestFilePath = path.join(packageDir, 'latest.json'); - - try { - const packageInfo = JSON.parse( - fs.readFileSync(latestFilePath).toString(), - ); - return { - ...acc, - [packageInfo.canonical]: packageInfo, - }; - } catch (e) { - console.error(`Failed to read package: ${canonical}`); - console.error(e); - } - }, {}); - } - - getPackageVersions(packageName: string): { latest: any; versions: string[] } { - const packageDir = getPackageDirFromCanonical(packageName); - try { - const versions = fs - .readdirSync(packageDir) - .filter((file) => file.endsWith('.json') && file !== 'latest.json') - .map((f) => { - const versionFile = JSON.parse( - fs.readFileSync(path.join(packageDir, f)).toString(), - ); - return versionFile.version; - }); - - const dedupedVersions = Array.from(new Set(versions)); - - const latest = JSON.parse( - fs.readFileSync(path.join(packageDir, 'latest.json')).toString(), - ); - - return { versions: dedupedVersions, latest }; - } catch (e) { - console.error(`Failed to read package versions: ${packageName}`); - console.error(e); - } - } - - getPackageByVersion(packageName: string, version: string): string { - try { - return getPackage(packageName, version); - } catch (e) { - console.error(`Failed to read package by version: ${packageName}`); - console.error(e); - } - } -} - -const NAMESPACE_FILE_MARKER = '__NAMESPACE__'; - -function* iterPackages() { - // Loop through each package registry - const packageRegistries = fs.readdirSync(PACKAGES_PATH); - for (const packageRegistry of packageRegistries) { - const registryPath = path.join(PACKAGES_PATH, packageRegistry); - - // bail if registry path is not a dir - if (!fs.lstatSync(registryPath).isDirectory()) { - continue; - } - - // Loop through each item in the registry - const items = fs.readdirSync(registryPath); - for (const item of items) { - const namespaceFilePath = path.join( - PACKAGES_PATH, - packageRegistry, - item, - NAMESPACE_FILE_MARKER, - ); - - // Check if the NAMESPACE_FILE_MARKER exists - if (fs.existsSync(namespaceFilePath)) { - const subItems = fs.readdirSync( - path.join(PACKAGES_PATH, packageRegistry, item), - ); - - // Yield subitems, excluding NAMESPACE_FILE_MARKER - for (const subitem of subItems) { - if (subitem !== NAMESPACE_FILE_MARKER) { - yield `${packageRegistry}:${item}/${subitem}`; - } - } - } else { - // Yield item if NAMESPACE_FILE_MARKER does not exist - yield `${packageRegistry}:${item}`; - } - } - } -} diff --git a/api-node/src/registry/registry.service.ts b/api-node/src/registry/registry.service.ts index e2a8779ce..e1f12abd1 100644 --- a/api-node/src/registry/registry.service.ts +++ b/api-node/src/registry/registry.service.ts @@ -1,11 +1,129 @@ import { Injectable } from '@nestjs/common'; import * as path from 'path'; import * as fs from 'fs'; +import type { SdkEntry, SdksResponse } from '../sdks/types'; +import { getPackage, getPackageDirFromCanonical } from '../common/packageUtils'; +const SDKS_PATH = path.join('..', 'sdks'); +const PACKAGES_PATH = path.join('..', 'packages'); const AWS_LAMBDA_LAYERS_PATH = path.join('..', 'aws-lambda-layers'); @Injectable() export class RegistryService { + // TODO: package types + #packages; + + constructor() { + this.#packages = Array.from(iterPackages()); + } + + // SDKs + getSdks(strict: boolean = false): SdksResponse { + const sdks: SdksResponse = {}; + try { + const sdkLinks = fs.readdirSync(SDKS_PATH); + for (const link of sdkLinks) { + try { + const latestJsonPath = path.join(SDKS_PATH, link, 'latest.json'); + const latestJsonContent = fs.readFileSync(latestJsonPath, 'utf8'); + const { canonical } = JSON.parse(latestJsonContent); + const pkg = getPackage(canonical); + if (pkg) { + sdks[link] = pkg; + } else if (strict) { + throw new Error( + `Package ${link}, canonical cannot be resolved: ${canonical}`, + ); + } + } catch (error) { + if (strict) { + throw error; + } + // If not strict, continue to the next SDK + } + } + } catch (error) { + console.error('Error reading SDKs directory:', error); + } + return sdks; + } + + getSdk(sdkId: string, version: string = 'latest'): SdkEntry { + const sdkFilePath = path.join(SDKS_PATH, sdkId, `${version}.json`); + try { + const { canonical } = JSON.parse(fs.readFileSync(sdkFilePath, 'utf8')); + return getPackage(canonical, version); + } catch (error) { + console.error('Error reading SDK file:', error); + } + } + + getSdkVersions(sdkId: string): { latest: any; versions: string[] } { + const latest = this.getSdk(sdkId); + const { versions } = this.getPackageVersions(latest.canonical as string); + return { latest, versions }; + } + + // Packages + + getPackages() { + return this.#packages.reduce((acc, canonical) => { + const packageDir = getPackageDirFromCanonical(canonical); + const latestFilePath = path.join(packageDir, 'latest.json'); + + try { + const packageInfo = JSON.parse( + fs.readFileSync(latestFilePath).toString(), + ); + return { + ...acc, + [packageInfo.canonical]: packageInfo, + }; + } catch (e) { + console.error(`Failed to read package: ${canonical}`); + console.error(e); + } + }, {}); + } + + getPackageVersions(packageName: string): { latest: any; versions: string[] } { + const packageDir = getPackageDirFromCanonical(packageName); + try { + const versions = fs + .readdirSync(packageDir) + .filter((file) => file.endsWith('.json') && file !== 'latest.json') + .map((f) => { + const versionFile = JSON.parse( + fs.readFileSync(path.join(packageDir, f)).toString(), + ); + return versionFile.version; + }); + + const dedupedVersions = Array.from(new Set(versions)); + + const latest = JSON.parse( + fs.readFileSync(path.join(packageDir, 'latest.json')).toString(), + ); + + return { versions: dedupedVersions, latest }; + } catch (e) { + console.error(`Failed to read package versions: ${packageName}`); + console.error(e); + } + } + + // TODO: rename to getPackage + getPackageByVersion(packageName: string, version: string): string { + try { + return getPackage(packageName, version); + } catch (e) { + console.error(`Failed to read package by version: ${packageName}`); + console.error(e); + } + } + + // AWS Lambda Layers + async getAwsLambdaLayers() { const layers: Record = {}; const lambdaLayersDir = path.resolve(AWS_LAMBDA_LAYERS_PATH); @@ -31,3 +149,46 @@ export class RegistryService { return layers; } } + +const NAMESPACE_FILE_MARKER = '__NAMESPACE__'; + +function* iterPackages() { + // Loop through each package registry + const packageRegistries = fs.readdirSync(PACKAGES_PATH); + for (const packageRegistry of packageRegistries) { + const registryPath = path.join(PACKAGES_PATH, packageRegistry); + + // bail if registry path is not a dir + if (!fs.lstatSync(registryPath).isDirectory()) { + continue; + } + + // Loop through each item in the registry + const items = fs.readdirSync(registryPath); + for (const item of items) { + const namespaceFilePath = path.join( + PACKAGES_PATH, + packageRegistry, + item, + NAMESPACE_FILE_MARKER, + ); + + // Check if the NAMESPACE_FILE_MARKER exists + if (fs.existsSync(namespaceFilePath)) { + const subItems = fs.readdirSync( + path.join(PACKAGES_PATH, packageRegistry, item), + ); + + // Yield subitems, excluding NAMESPACE_FILE_MARKER + for (const subitem of subItems) { + if (subitem !== NAMESPACE_FILE_MARKER) { + yield `${packageRegistry}:${item}/${subitem}`; + } + } + } else { + // Yield item if NAMESPACE_FILE_MARKER does not exist + yield `${packageRegistry}:${item}`; + } + } + } +} diff --git a/api-node/src/sdks/sdks.controller.ts b/api-node/src/sdks/sdks.controller.ts index bff2bce59..23d2d213b 100644 --- a/api-node/src/sdks/sdks.controller.ts +++ b/api-node/src/sdks/sdks.controller.ts @@ -1,21 +1,21 @@ import { Controller, Get, Param, Query } from '@nestjs/common'; -import { SdksService } from './sdks.service'; import { SdkEntry, SdksResponse, SdkVersionsResponse } from './types'; +import { RegistryService } from '../registry/registry.service'; @Controller('sdks') export class SdksController { - constructor(private sdksService: SdksService) {} + constructor(private registryService: RegistryService) {} @Get() getSdks(@Query('strict') strict?: string): SdksResponse { const isStrict = strict?.toLowerCase() === 'true' || strict === '1' || strict === 'yes'; - return this.sdksService.getSdks(isStrict); + return this.registryService.getSdks(isStrict); } @Get('/:sdkId/versions') getSdkVersions(@Param('sdkId') sdkId: string): SdkVersionsResponse { - return this.sdksService.getSdkVersions(sdkId); + return this.registryService.getSdkVersions(sdkId); } @Get('/:sdkId/:version') @@ -23,6 +23,6 @@ export class SdksController { @Param('sdkId') sdkId: string, @Param('version') version?: string, ): SdkEntry { - return this.sdksService.getSdk(sdkId, version); + return this.registryService.getSdk(sdkId, version); } } diff --git a/api-node/src/sdks/sdks.service.ts b/api-node/src/sdks/sdks.service.ts deleted file mode 100644 index 308b60496..000000000 --- a/api-node/src/sdks/sdks.service.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import * as fs from 'fs'; -import * as path from 'path'; -import { SdkEntry, SdksResponse } from './types'; -import { getPackage } from '../common/packageUtils'; -import { PackagesService } from '../packages/packages.service'; - -const SDKS_PATH = path.join('..', 'sdks'); - -@Injectable() -export class SdksService { - constructor(private packagesService: PackagesService) {} - - getSdks(strict: boolean = false): SdksResponse { - const sdks: SdksResponse = {}; - try { - const sdkLinks = fs.readdirSync(SDKS_PATH); - for (const link of sdkLinks) { - try { - const latestJsonPath = path.join(SDKS_PATH, link, 'latest.json'); - const latestJsonContent = fs.readFileSync(latestJsonPath, 'utf8'); - const { canonical } = JSON.parse(latestJsonContent); - const pkg = getPackage(canonical); - if (pkg) { - sdks[link] = pkg; - } else if (strict) { - throw new Error( - `Package ${link}, canonical cannot be resolved: ${canonical}`, - ); - } - } catch (error) { - if (strict) { - throw error; - } - // If not strict, continue to the next SDK - } - } - } catch (error) { - console.error('Error reading SDKs directory:', error); - } - return sdks; - } - - getSdk(sdkId: string, version: string = 'latest'): SdkEntry { - const sdkFilePath = path.join(SDKS_PATH, sdkId, `${version}.json`); - try { - const { canonical } = JSON.parse(fs.readFileSync(sdkFilePath, 'utf8')); - return getPackage(canonical, version); - } catch (error) { - console.error('Error reading SDK file:', error); - } - } - - getSdkVersions(sdkId: string): { latest: any; versions: string[] } { - const latest = this.getSdk(sdkId); - const { versions } = this.packagesService.getPackageVersions( - latest.canonical as string, - ); - return { latest, versions }; - } -} From fcd043d3fc4a6e6f537596cfefb1c06d979b4b59 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 11:12:26 +0200 Subject: [PATCH 12/54] consolidate marketing service --- api-node/src/app.module.ts | 3 +- .../src/marketing/marketing.controller.ts | 8 +-- api-node/src/marketing/marketing.service.ts | 56 ------------------- api-node/src/registry/registry.service.ts | 47 ++++++++++++++++ 4 files changed, 52 insertions(+), 62 deletions(-) delete mode 100644 api-node/src/marketing/marketing.service.ts diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index a3e9d59fc..0e680f2f0 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -2,7 +2,6 @@ import { Module } from '@nestjs/common'; import { PackagesController } from './packages/packages.controller'; import { HealthCheckController } from './health/healthCheck.controller'; import { MarketingController } from './marketing/marketing.controller'; -import { MarketingService } from './marketing/marketing.service'; import { AppsController } from './apps/apps.controller'; import { AppsService } from './apps/apps.service'; import { SdksController } from './sdks/sdks.controller'; @@ -19,6 +18,6 @@ import { RegistryService } from './registry/registry.service'; SdksController, AwsLambdaLayersController, ], - providers: [MarketingService, AppsService, RegistryService], + providers: [AppsService, RegistryService], }) export class AppModule {} diff --git a/api-node/src/marketing/marketing.controller.ts b/api-node/src/marketing/marketing.controller.ts index 87508fb81..1c2f5a62d 100644 --- a/api-node/src/marketing/marketing.controller.ts +++ b/api-node/src/marketing/marketing.controller.ts @@ -1,21 +1,21 @@ import { Controller, Get, Param, NotFoundException } from '@nestjs/common'; -import { MarketingService } from './marketing.service'; import { MarketingSlugResponse, MarketingSlugResolveResponse } from './types'; +import { RegistryService } from '../registry/registry.service'; @Controller('marketing-slugs') export class MarketingController { - constructor(private readonly marketingService: MarketingService) {} + constructor(private readonly registryService: RegistryService) {} @Get() getMarketingSlugs(): MarketingSlugResponse { - return this.marketingService.getMarketingSlugs(); + return this.registryService.getMarketingSlugs(); } @Get(':slug') resolveMarketingSlug( @Param('slug') slug: string, ): MarketingSlugResolveResponse { - const result = this.marketingService.resolveMarketingSlug(slug); + const result = this.registryService.resolveMarketingSlug(slug); if (!result) { throw new NotFoundException(); } diff --git a/api-node/src/marketing/marketing.service.ts b/api-node/src/marketing/marketing.service.ts deleted file mode 100644 index 1144fc116..000000000 --- a/api-node/src/marketing/marketing.service.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import * as fs from 'fs'; -import * as path from 'path'; -import { - MarketingSlugEntry, - MarketingSlugResolveResponse, - MarketingSlugResponse, -} from './types'; -import { RegistryService } from '../registry/registry.service'; - -@Injectable() -export class MarketingService { - #slugs: Record; - - constructor(private readonly registryService: RegistryService) { - this.#slugs = JSON.parse( - fs.readFileSync(path.join('..', 'misc', 'marketing-slugs.json'), 'utf8'), - ); - } - - getMarketingSlugs(): MarketingSlugResponse { - return { slugs: Object.keys(this.#slugs) }; - } - - resolveMarketingSlug(slug: string): MarketingSlugResolveResponse | null { - const data = this.#slugs[slug]; - if (!data) { - return null; - } - - let target = null; - if (data.type === 'sdk') { - target = this.registryService.getSdk(data.target); - } else if (data.type === 'package') { - target = this.registryService.getPackageByVersion(data.target, 'latest'); - } else if (data.type === 'integration') { - let pkg = null; - if (data.sdk) { - pkg = this.registryService.getSdk(data.sdk); - } else if (data.package) { - pkg = this.registryService.getPackageByVersion(data.package, 'latest'); - } - if (pkg) { - target = { - package: pkg, - integration: data.integration, - }; - } - } - - return { - definition: data, - target, - }; - } -} diff --git a/api-node/src/registry/registry.service.ts b/api-node/src/registry/registry.service.ts index e1f12abd1..d1bccc48b 100644 --- a/api-node/src/registry/registry.service.ts +++ b/api-node/src/registry/registry.service.ts @@ -3,6 +3,11 @@ import * as path from 'path'; import * as fs from 'fs'; import type { SdkEntry, SdksResponse } from '../sdks/types'; import { getPackage, getPackageDirFromCanonical } from '../common/packageUtils'; +import { + MarketingSlugEntry, + MarketingSlugResolveResponse, + MarketingSlugResponse, +} from 'src/marketing/types'; const SDKS_PATH = path.join('..', 'sdks'); const PACKAGES_PATH = path.join('..', 'packages'); @@ -12,9 +17,13 @@ const AWS_LAMBDA_LAYERS_PATH = path.join('..', 'aws-lambda-layers'); export class RegistryService { // TODO: package types #packages; + #slugs: Record; constructor() { this.#packages = Array.from(iterPackages()); + this.#slugs = JSON.parse( + fs.readFileSync(path.join('..', 'misc', 'marketing-slugs.json'), 'utf8'), + ); } // SDKs @@ -122,6 +131,44 @@ export class RegistryService { } } + // Marketing + + getMarketingSlugs(): MarketingSlugResponse { + return { slugs: Object.keys(this.#slugs) }; + } + + resolveMarketingSlug(slug: string): MarketingSlugResolveResponse | null { + const data = this.#slugs[slug]; + if (!data) { + return null; + } + + let target = null; + if (data.type === 'sdk') { + target = this.getSdk(data.target); + } else if (data.type === 'package') { + target = this.getPackageByVersion(data.target, 'latest'); + } else if (data.type === 'integration') { + let pkg = null; + if (data.sdk) { + pkg = this.getSdk(data.sdk); + } else if (data.package) { + pkg = this.getPackageByVersion(data.package, 'latest'); + } + if (pkg) { + target = { + package: pkg, + integration: data.integration, + }; + } + } + + return { + definition: data, + target, + }; + } + // AWS Lambda Layers async getAwsLambdaLayers() { From 7d600de8d3f09ae5170407f72af8f555ee6f99b7 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 11:16:28 +0200 Subject: [PATCH 13/54] consolidate apps service --- api-node/src/app.module.ts | 3 +- api-node/src/apps/apps.controller.ts | 14 +-- api-node/src/apps/apps.service.ts | 94 ------------------- .../aws-lambda-layers.controller.ts | 2 + api-node/src/registry/registry.service.ts | 89 ++++++++++++++++++ 5 files changed, 99 insertions(+), 103 deletions(-) delete mode 100644 api-node/src/apps/apps.service.ts diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index 0e680f2f0..8db0f1503 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -3,7 +3,6 @@ import { PackagesController } from './packages/packages.controller'; import { HealthCheckController } from './health/healthCheck.controller'; import { MarketingController } from './marketing/marketing.controller'; import { AppsController } from './apps/apps.controller'; -import { AppsService } from './apps/apps.service'; import { SdksController } from './sdks/sdks.controller'; import { AwsLambdaLayersController } from './aws-lambda-layers/aws-lambda-layers.controller'; import { RegistryService } from './registry/registry.service'; @@ -18,6 +17,6 @@ import { RegistryService } from './registry/registry.service'; SdksController, AwsLambdaLayersController, ], - providers: [AppsService, RegistryService], + providers: [RegistryService], }) export class AppModule {} diff --git a/api-node/src/apps/apps.controller.ts b/api-node/src/apps/apps.controller.ts index b1e630003..48ca1bf1f 100644 --- a/api-node/src/apps/apps.controller.ts +++ b/api-node/src/apps/apps.controller.ts @@ -1,17 +1,17 @@ import { Controller, Get, Param, Query, Res } from '@nestjs/common'; -import { AppsService } from './apps.service'; import { AppsResponse } from './types'; import type { Response } from 'express'; +import { RegistryService } from '../registry/registry.service'; @Controller('apps') export class AppsController { - constructor(private appsService: AppsService) {} + constructor(private readonly registryService: RegistryService) {} @Get() getApps(): AppsResponse { - return this.appsService.getApps(); + return this.registryService.getApps(); } @Get(':appId/:version') @@ -24,7 +24,7 @@ export class AppsController { @Query('platform') platform?: string, @Query('package') pkgName?: string, ): void { - const appInfo = this.appsService.getApp(appId, version); + const appInfo = this.registryService.getApp(appId, version); if (!appInfo) { res.status(404).send('App not found'); @@ -37,7 +37,7 @@ export class AppsController { return; } - const url = this.appsService.findDownloadUrl( + const url = this.registryService.findDownloadUrl( appInfo, pkgName, arch, @@ -48,8 +48,8 @@ export class AppsController { return; } - const checksums = this.appsService.getUrlChecksums(appInfo, url); - const digest = this.appsService.makeDigest(checksums); + const checksums = this.registryService.getUrlChecksums(appInfo, url); + const digest = this.registryService.makeDigest(checksums); res.setHeader('Location', url); if (digest) { diff --git a/api-node/src/apps/apps.service.ts b/api-node/src/apps/apps.service.ts deleted file mode 100644 index cb2ed90b5..000000000 --- a/api-node/src/apps/apps.service.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import * as fs from 'fs'; -import * as path from 'path'; -import { AppEntry, AppsResponse } from './types'; - -const APPS_PATH = path.join('..', 'apps'); - -@Injectable() -export class AppsService { - getApps(): AppsResponse { - try { - const apps = fs.readdirSync(APPS_PATH); - return apps.reduce((acc, appName) => { - try { - const app = this.getApp(appName); - if (app) { - acc[appName] = app; - } - } catch { - // Continue to next iteration if there's an error - } - return acc; - }, {}); - } catch (error) { - // Handle error (e.g., log it or throw a custom exception) - console.error('Error reading apps directory:', error); - } - } - - getApp(appId: string, version: string = 'latest'): AppEntry | null { - try { - const filePath = path.join(APPS_PATH, appId, `${version}.json`); - const fileContent = fs.readFileSync(filePath, 'utf8'); - return JSON.parse(fileContent); - } catch { - return null; - } - } - - findDownloadUrl( - appInfo: AppEntry, - pkgName: string, - arch: string, - platform: string, - ): string | null { - const normalizedPackage = pkgName - .replace(/_/g, '-') - .toLowerCase() - .split('-'); - for (const [_, url] of Object.entries(appInfo.file_urls)) { - let normalizedUrl = url.toLowerCase(); - if (normalizedUrl.endsWith('.exe')) { - normalizedUrl = normalizedUrl.slice(0, -4); - } - const parts = normalizedUrl.split('/').pop()!.split('-'); - if ( - parts.length > 2 && - parts.slice(0, -2).join('-') === normalizedPackage.join('-') && - parts[parts.length - 1].replace(/_/g, '-') === - arch.toLowerCase().replace(/_/g, '-') && - parts[parts.length - 2] === platform.toLowerCase() - ) { - return url; - } - } - return null; - } - - getUrlChecksums( - appInfo: AppEntry, - url: string, - ): Record | null { - for (const fileInfo of Object.values(appInfo.files)) { - if (fileInfo.url === url) { - return (fileInfo as any).checksums || null; - } - } - return null; - } - - makeDigest(checksums: Record | null): string { - if (!checksums) return ''; - const digestParts: string[] = []; - for (const [algo, value] of Object.entries(checksums)) { - if (algo.endsWith('-hex')) { - const base64Value = Buffer.from(value, 'hex').toString('base64'); - digestParts.push(`${algo.slice(0, -4)}=${base64Value}`); - } else if (algo.endsWith('-base64')) { - digestParts.push(`${algo.slice(0, -7)}=${value}`); - } - } - return digestParts.join(','); - } -} diff --git a/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts b/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts index f78788d4f..af421bfcc 100644 --- a/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts +++ b/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts @@ -3,6 +3,8 @@ import { RegistryService } from '../registry/registry.service'; @Controller('aws-lambda-layers') export class AwsLambdaLayersController { + // TODO: types + constructor(private readonly registryService: RegistryService) {} @Get() diff --git a/api-node/src/registry/registry.service.ts b/api-node/src/registry/registry.service.ts index d1bccc48b..1b2120eb1 100644 --- a/api-node/src/registry/registry.service.ts +++ b/api-node/src/registry/registry.service.ts @@ -8,8 +8,10 @@ import { MarketingSlugResolveResponse, MarketingSlugResponse, } from 'src/marketing/types'; +import { AppEntry, AppsResponse } from 'src/apps/types'; const SDKS_PATH = path.join('..', 'sdks'); +const APPS_PATH = path.join('..', 'apps'); const PACKAGES_PATH = path.join('..', 'packages'); const AWS_LAMBDA_LAYERS_PATH = path.join('..', 'aws-lambda-layers'); @@ -131,6 +133,93 @@ export class RegistryService { } } + // Apps + + getApps(): AppsResponse { + try { + const apps = fs.readdirSync(APPS_PATH); + return apps.reduce((acc, appName) => { + try { + const app = this.getApp(appName); + if (app) { + acc[appName] = app; + } + } catch { + // Continue to next iteration if there's an error + } + return acc; + }, {}); + } catch (error) { + // Handle error (e.g., log it or throw a custom exception) + console.error('Error reading apps directory:', error); + } + } + + getApp(appId: string, version: string = 'latest'): AppEntry | null { + try { + const filePath = path.join(APPS_PATH, appId, `${version}.json`); + const fileContent = fs.readFileSync(filePath, 'utf8'); + return JSON.parse(fileContent); + } catch { + return null; + } + } + + findDownloadUrl( + appInfo: AppEntry, + pkgName: string, + arch: string, + platform: string, + ): string | null { + const normalizedPackage = pkgName + .replace(/_/g, '-') + .toLowerCase() + .split('-'); + for (const [_, url] of Object.entries(appInfo.file_urls)) { + let normalizedUrl = url.toLowerCase(); + if (normalizedUrl.endsWith('.exe')) { + normalizedUrl = normalizedUrl.slice(0, -4); + } + const parts = normalizedUrl.split('/').pop()!.split('-'); + if ( + parts.length > 2 && + parts.slice(0, -2).join('-') === normalizedPackage.join('-') && + parts[parts.length - 1].replace(/_/g, '-') === + arch.toLowerCase().replace(/_/g, '-') && + parts[parts.length - 2] === platform.toLowerCase() + ) { + return url; + } + } + return null; + } + + getUrlChecksums( + appInfo: AppEntry, + url: string, + ): Record | null { + for (const fileInfo of Object.values(appInfo.files)) { + if (fileInfo.url === url) { + return (fileInfo as any).checksums || null; + } + } + return null; + } + + makeDigest(checksums: Record | null): string { + if (!checksums) return ''; + const digestParts: string[] = []; + for (const [algo, value] of Object.entries(checksums)) { + if (algo.endsWith('-hex')) { + const base64Value = Buffer.from(value, 'hex').toString('base64'); + digestParts.push(`${algo.slice(0, -4)}=${base64Value}`); + } else if (algo.endsWith('-base64')) { + digestParts.push(`${algo.slice(0, -7)}=${value}`); + } + } + return digestParts.join(','); + } + // Marketing getMarketingSlugs(): MarketingSlugResponse { From a742ff2a3370d9ce7e82bd8a36731c726c805a46 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 11:18:16 +0200 Subject: [PATCH 14/54] move registry service to common --- api-node/src/app.module.ts | 2 +- api-node/src/apps/apps.controller.ts | 2 +- api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts | 2 +- api-node/src/{registry => common}/registry.service.ts | 0 api-node/src/marketing/marketing.controller.ts | 2 +- api-node/src/packages/packages.controller.ts | 2 +- api-node/src/sdks/sdks.controller.ts | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename api-node/src/{registry => common}/registry.service.ts (100%) diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index 8db0f1503..d82b63fdb 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -5,7 +5,7 @@ import { MarketingController } from './marketing/marketing.controller'; import { AppsController } from './apps/apps.controller'; import { SdksController } from './sdks/sdks.controller'; import { AwsLambdaLayersController } from './aws-lambda-layers/aws-lambda-layers.controller'; -import { RegistryService } from './registry/registry.service'; +import { RegistryService } from './common/registry.service'; @Module({ imports: [], diff --git a/api-node/src/apps/apps.controller.ts b/api-node/src/apps/apps.controller.ts index 48ca1bf1f..118cdda84 100644 --- a/api-node/src/apps/apps.controller.ts +++ b/api-node/src/apps/apps.controller.ts @@ -3,7 +3,7 @@ import { Controller, Get, Param, Query, Res } from '@nestjs/common'; import { AppsResponse } from './types'; import type { Response } from 'express'; -import { RegistryService } from '../registry/registry.service'; +import { RegistryService } from '../common/registry.service'; @Controller('apps') export class AppsController { diff --git a/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts b/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts index af421bfcc..336b2093c 100644 --- a/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts +++ b/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts @@ -1,5 +1,5 @@ import { Controller, Get } from '@nestjs/common'; -import { RegistryService } from '../registry/registry.service'; +import { RegistryService } from '../common/registry.service'; @Controller('aws-lambda-layers') export class AwsLambdaLayersController { diff --git a/api-node/src/registry/registry.service.ts b/api-node/src/common/registry.service.ts similarity index 100% rename from api-node/src/registry/registry.service.ts rename to api-node/src/common/registry.service.ts diff --git a/api-node/src/marketing/marketing.controller.ts b/api-node/src/marketing/marketing.controller.ts index 1c2f5a62d..45ae88279 100644 --- a/api-node/src/marketing/marketing.controller.ts +++ b/api-node/src/marketing/marketing.controller.ts @@ -1,6 +1,6 @@ import { Controller, Get, Param, NotFoundException } from '@nestjs/common'; import { MarketingSlugResponse, MarketingSlugResolveResponse } from './types'; -import { RegistryService } from '../registry/registry.service'; +import { RegistryService } from '../common/registry.service'; @Controller('marketing-slugs') export class MarketingController { diff --git a/api-node/src/packages/packages.controller.ts b/api-node/src/packages/packages.controller.ts index dafb695e5..87158fa8e 100644 --- a/api-node/src/packages/packages.controller.ts +++ b/api-node/src/packages/packages.controller.ts @@ -1,5 +1,5 @@ import { Controller, Get, Param } from '@nestjs/common'; -import { RegistryService } from '../registry/registry.service'; +import { RegistryService } from '../common/registry.service'; @Controller('packages') export class PackagesController { diff --git a/api-node/src/sdks/sdks.controller.ts b/api-node/src/sdks/sdks.controller.ts index 23d2d213b..526d18b65 100644 --- a/api-node/src/sdks/sdks.controller.ts +++ b/api-node/src/sdks/sdks.controller.ts @@ -1,6 +1,6 @@ import { Controller, Get, Param, Query } from '@nestjs/common'; import { SdkEntry, SdksResponse, SdkVersionsResponse } from './types'; -import { RegistryService } from '../registry/registry.service'; +import { RegistryService } from '../common/registry.service'; @Controller('sdks') export class SdksController { From 40a92fba8e0479efa3eed6fda0f414d6227c7081 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 11:28:59 +0200 Subject: [PATCH 15/54] cleanup utils --- api-node/src/apps/apps.controller.ts | 12 +-- api-node/src/apps/utils.ts | 55 +++++++++++++ api-node/src/common/packageUtils.ts | 16 ---- api-node/src/common/registry.service.ts | 82 +++++--------------- api-node/src/packages/packages.controller.ts | 2 +- 5 files changed, 78 insertions(+), 89 deletions(-) create mode 100644 api-node/src/apps/utils.ts delete mode 100644 api-node/src/common/packageUtils.ts diff --git a/api-node/src/apps/apps.controller.ts b/api-node/src/apps/apps.controller.ts index 118cdda84..5592ea197 100644 --- a/api-node/src/apps/apps.controller.ts +++ b/api-node/src/apps/apps.controller.ts @@ -4,6 +4,7 @@ import { AppsResponse } from './types'; import type { Response } from 'express'; import { RegistryService } from '../common/registry.service'; +import { findDownloadUrl, getUrlChecksums, makeDigest } from './utils'; @Controller('apps') export class AppsController { @@ -37,19 +38,14 @@ export class AppsController { return; } - const url = this.registryService.findDownloadUrl( - appInfo, - pkgName, - arch, - platform, - ); + const url = findDownloadUrl(appInfo, pkgName, arch, platform); if (!url) { res.status(404).send('Download URL not found'); return; } - const checksums = this.registryService.getUrlChecksums(appInfo, url); - const digest = this.registryService.makeDigest(checksums); + const checksums = getUrlChecksums(appInfo, url); + const digest = makeDigest(checksums); res.setHeader('Location', url); if (digest) { diff --git a/api-node/src/apps/utils.ts b/api-node/src/apps/utils.ts new file mode 100644 index 000000000..a374170e2 --- /dev/null +++ b/api-node/src/apps/utils.ts @@ -0,0 +1,55 @@ +import { AppEntry } from 'src/apps/types'; + +export function findDownloadUrl( + appInfo: AppEntry, + pkgName: string, + arch: string, + platform: string, +): string | null { + const normalizedPackage = pkgName.replace(/_/g, '-').toLowerCase().split('-'); + for (const [_, url] of Object.entries(appInfo.file_urls)) { + let normalizedUrl = url.toLowerCase(); + if (normalizedUrl.endsWith('.exe')) { + normalizedUrl = normalizedUrl.slice(0, -4); + } + const parts = normalizedUrl.split('/').pop()!.split('-'); + if ( + parts.length > 2 && + parts.slice(0, -2).join('-') === normalizedPackage.join('-') && + parts[parts.length - 1].replace(/_/g, '-') === + arch.toLowerCase().replace(/_/g, '-') && + parts[parts.length - 2] === platform.toLowerCase() + ) { + return url; + } + } + return null; +} + +export function getUrlChecksums( + appInfo: AppEntry, + url: string, +): Record | null { + for (const fileInfo of Object.values(appInfo.files)) { + if (fileInfo.url === url) { + return (fileInfo as any).checksums || null; + } + } + return null; +} + +export function makeDigest(checksums: Record | null): string { + if (!checksums) { + return ''; + } + const digestParts: string[] = []; + for (const [algo, value] of Object.entries(checksums)) { + if (algo.endsWith('-hex')) { + const base64Value = Buffer.from(value, 'hex').toString('base64'); + digestParts.push(`${algo.slice(0, -4)}=${base64Value}`); + } else if (algo.endsWith('-base64')) { + digestParts.push(`${algo.slice(0, -7)}=${value}`); + } + } + return digestParts.join(','); +} diff --git a/api-node/src/common/packageUtils.ts b/api-node/src/common/packageUtils.ts deleted file mode 100644 index 3e2ec60ef..000000000 --- a/api-node/src/common/packageUtils.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as path from 'path'; -import * as fs from 'fs'; - -export const PACKAGES_PATH = path.join('..', 'packages'); - -export function getPackage(packageName: string, version: string = 'latest') { - const packageDir = getPackageDirFromCanonical(packageName); - const versionFilePath = path.join(packageDir, `${version}.json`); - return JSON.parse(fs.readFileSync(versionFilePath).toString()); -} - -export function getPackageDirFromCanonical(canonicalPackageName: string) { - const [registry, name] = canonicalPackageName.split(/:(.*)/s); - const pkgPath = name.replaceAll(':', path.sep).split(path.sep); - return path.resolve(path.join(PACKAGES_PATH, registry, ...pkgPath)); -} diff --git a/api-node/src/common/registry.service.ts b/api-node/src/common/registry.service.ts index 1b2120eb1..93f57cd65 100644 --- a/api-node/src/common/registry.service.ts +++ b/api-node/src/common/registry.service.ts @@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common'; import * as path from 'path'; import * as fs from 'fs'; import type { SdkEntry, SdksResponse } from '../sdks/types'; -import { getPackage, getPackageDirFromCanonical } from '../common/packageUtils'; import { MarketingSlugEntry, MarketingSlugResolveResponse, @@ -15,6 +14,9 @@ const APPS_PATH = path.join('..', 'apps'); const PACKAGES_PATH = path.join('..', 'packages'); const AWS_LAMBDA_LAYERS_PATH = path.join('..', 'aws-lambda-layers'); +// Some packages have a __NAMESPACE__ file that contains the package name +const NAMESPACE_FILE_MARKER = '__NAMESPACE__'; + @Injectable() export class RegistryService { // TODO: package types @@ -38,7 +40,7 @@ export class RegistryService { const latestJsonPath = path.join(SDKS_PATH, link, 'latest.json'); const latestJsonContent = fs.readFileSync(latestJsonPath, 'utf8'); const { canonical } = JSON.parse(latestJsonContent); - const pkg = getPackage(canonical); + const pkg = this.getPackage(canonical); if (pkg) { sdks[link] = pkg; } else if (strict) { @@ -63,7 +65,7 @@ export class RegistryService { const sdkFilePath = path.join(SDKS_PATH, sdkId, `${version}.json`); try { const { canonical } = JSON.parse(fs.readFileSync(sdkFilePath, 'utf8')); - return getPackage(canonical, version); + return this.getPackage(canonical, version); } catch (error) { console.error('Error reading SDK file:', error); } @@ -124,9 +126,11 @@ export class RegistryService { } // TODO: rename to getPackage - getPackageByVersion(packageName: string, version: string): string { + getPackage(packageName: string, version: string = 'latest') { try { - return getPackage(packageName, version); + const packageDir = getPackageDirFromCanonical(packageName); + const versionFilePath = path.join(packageDir, `${version}.json`); + return JSON.parse(fs.readFileSync(versionFilePath).toString()); } catch (e) { console.error(`Failed to read package by version: ${packageName}`); console.error(e); @@ -165,61 +169,6 @@ export class RegistryService { } } - findDownloadUrl( - appInfo: AppEntry, - pkgName: string, - arch: string, - platform: string, - ): string | null { - const normalizedPackage = pkgName - .replace(/_/g, '-') - .toLowerCase() - .split('-'); - for (const [_, url] of Object.entries(appInfo.file_urls)) { - let normalizedUrl = url.toLowerCase(); - if (normalizedUrl.endsWith('.exe')) { - normalizedUrl = normalizedUrl.slice(0, -4); - } - const parts = normalizedUrl.split('/').pop()!.split('-'); - if ( - parts.length > 2 && - parts.slice(0, -2).join('-') === normalizedPackage.join('-') && - parts[parts.length - 1].replace(/_/g, '-') === - arch.toLowerCase().replace(/_/g, '-') && - parts[parts.length - 2] === platform.toLowerCase() - ) { - return url; - } - } - return null; - } - - getUrlChecksums( - appInfo: AppEntry, - url: string, - ): Record | null { - for (const fileInfo of Object.values(appInfo.files)) { - if (fileInfo.url === url) { - return (fileInfo as any).checksums || null; - } - } - return null; - } - - makeDigest(checksums: Record | null): string { - if (!checksums) return ''; - const digestParts: string[] = []; - for (const [algo, value] of Object.entries(checksums)) { - if (algo.endsWith('-hex')) { - const base64Value = Buffer.from(value, 'hex').toString('base64'); - digestParts.push(`${algo.slice(0, -4)}=${base64Value}`); - } else if (algo.endsWith('-base64')) { - digestParts.push(`${algo.slice(0, -7)}=${value}`); - } - } - return digestParts.join(','); - } - // Marketing getMarketingSlugs(): MarketingSlugResponse { @@ -236,13 +185,13 @@ export class RegistryService { if (data.type === 'sdk') { target = this.getSdk(data.target); } else if (data.type === 'package') { - target = this.getPackageByVersion(data.target, 'latest'); + target = this.getPackage(data.target, 'latest'); } else if (data.type === 'integration') { let pkg = null; if (data.sdk) { pkg = this.getSdk(data.sdk); } else if (data.package) { - pkg = this.getPackageByVersion(data.package, 'latest'); + pkg = this.getPackage(data.package, 'latest'); } if (pkg) { target = { @@ -286,8 +235,6 @@ export class RegistryService { } } -const NAMESPACE_FILE_MARKER = '__NAMESPACE__'; - function* iterPackages() { // Loop through each package registry const packageRegistries = fs.readdirSync(PACKAGES_PATH); @@ -328,3 +275,10 @@ function* iterPackages() { } } } + +function getPackageDirFromCanonical(canonicalPackageName: string) { + const pkgPath = canonicalPackageName + .replaceAll(':', path.sep) + .split(path.sep); + return path.resolve(path.join(PACKAGES_PATH, ...pkgPath)); +} diff --git a/api-node/src/packages/packages.controller.ts b/api-node/src/packages/packages.controller.ts index 87158fa8e..db377e6e2 100644 --- a/api-node/src/packages/packages.controller.ts +++ b/api-node/src/packages/packages.controller.ts @@ -20,6 +20,6 @@ export class PackagesController { @Param('package') pkgName: string, @Param('version') version: string, ) { - return this.registryService.getPackageByVersion(pkgName, version); + return this.registryService.getPackage(pkgName, version); } } From a5f01109b78859945b573c1a13d10ead319092c4 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 16:11:03 +0200 Subject: [PATCH 16/54] cleanup, typing, linting --- api-node/.eslintrc.js | 4 +- api-node/package.json | 6 +- api-node/src/apps/apps.controller.ts | 5 +- api-node/src/apps/types.ts | 2 +- api-node/src/apps/utils.ts | 4 +- .../aws-lambda-layers.controller.ts | 5 +- api-node/src/aws-lambda-layers/types.ts | 13 +++ api-node/src/common/registry.service.ts | 53 +++++----- api-node/src/main.ts | 3 +- .../src/marketing/marketing.controller.ts | 8 +- api-node/src/marketing/types.ts | 9 +- api-node/src/packages/packages.controller.ts | 7 +- api-node/src/packages/types.ts | 28 +++++ api-node/src/sdks/sdks.controller.ts | 6 +- api-node/src/sdks/types.ts | 4 +- api-node/test/marketing.e2e-spec.ts | 2 - api-node/yarn.lock | 100 +++++++++--------- aws-lambda-layers/node/8.32.0.json | 34 +++++- 18 files changed, 185 insertions(+), 108 deletions(-) create mode 100644 api-node/src/aws-lambda-layers/types.ts create mode 100644 api-node/src/packages/types.ts diff --git a/api-node/.eslintrc.js b/api-node/.eslintrc.js index 259de13c7..e686e4531 100644 --- a/api-node/.eslintrc.js +++ b/api-node/.eslintrc.js @@ -18,8 +18,8 @@ module.exports = { ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-function-return-type': 'error', '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-explicit-any': 'error', }, }; diff --git a/api-node/package.json b/api-node/package.json index 33204b76f..9ab5261d3 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -35,8 +35,8 @@ "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^6.0.0", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", + "@typescript-eslint/eslint-plugin": "^8.9.0", + "@typescript-eslint/parser": "^8.9.0", "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", @@ -48,7 +48,7 @@ "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" + "typescript": "^5.6.2" }, "jest": { "moduleFileExtensions": [ diff --git a/api-node/src/apps/apps.controller.ts b/api-node/src/apps/apps.controller.ts index 5592ea197..c2fd2b199 100644 --- a/api-node/src/apps/apps.controller.ts +++ b/api-node/src/apps/apps.controller.ts @@ -1,17 +1,16 @@ import { Controller, Get, Param, Query, Res } from '@nestjs/common'; -import { AppsResponse } from './types'; - import type { Response } from 'express'; import { RegistryService } from '../common/registry.service'; import { findDownloadUrl, getUrlChecksums, makeDigest } from './utils'; +import type { Apps } from './types'; @Controller('apps') export class AppsController { constructor(private readonly registryService: RegistryService) {} @Get() - getApps(): AppsResponse { + getApps(): Apps { return this.registryService.getApps(); } diff --git a/api-node/src/apps/types.ts b/api-node/src/apps/types.ts index b9485e6c2..6a26d26ad 100644 --- a/api-node/src/apps/types.ts +++ b/api-node/src/apps/types.ts @@ -9,4 +9,4 @@ export interface AppEntry { files: Record }>; } -export type AppsResponse = Record; +export type Apps = Record; diff --git a/api-node/src/apps/utils.ts b/api-node/src/apps/utils.ts index a374170e2..5a2ca0c01 100644 --- a/api-node/src/apps/utils.ts +++ b/api-node/src/apps/utils.ts @@ -7,7 +7,7 @@ export function findDownloadUrl( platform: string, ): string | null { const normalizedPackage = pkgName.replace(/_/g, '-').toLowerCase().split('-'); - for (const [_, url] of Object.entries(appInfo.file_urls)) { + for (const [, url] of Object.entries(appInfo.file_urls)) { let normalizedUrl = url.toLowerCase(); if (normalizedUrl.endsWith('.exe')) { normalizedUrl = normalizedUrl.slice(0, -4); @@ -32,7 +32,7 @@ export function getUrlChecksums( ): Record | null { for (const fileInfo of Object.values(appInfo.files)) { if (fileInfo.url === url) { - return (fileInfo as any).checksums || null; + return fileInfo.checksums || null; } } return null; diff --git a/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts b/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts index 336b2093c..ae8ae2227 100644 --- a/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts +++ b/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts @@ -1,14 +1,13 @@ import { Controller, Get } from '@nestjs/common'; import { RegistryService } from '../common/registry.service'; +import { AwsLambdaLayers } from './types'; @Controller('aws-lambda-layers') export class AwsLambdaLayersController { - // TODO: types - constructor(private readonly registryService: RegistryService) {} @Get() - async getLayers() { + getLayers(): AwsLambdaLayers { return this.registryService.getAwsLambdaLayers(); } } diff --git a/api-node/src/aws-lambda-layers/types.ts b/api-node/src/aws-lambda-layers/types.ts new file mode 100644 index 000000000..6b278ab6f --- /dev/null +++ b/api-node/src/aws-lambda-layers/types.ts @@ -0,0 +1,13 @@ +export interface AwsLambdaLayerEntry { + name: string; + repo_url: string; + main_docs_url: string; + created_at?: string; + canonical: string; + sdk_version: string; + account_number: string; + layer_name: string; + regions: { region: string; version: string }[]; +} + +export type AwsLambdaLayers = Record; diff --git a/api-node/src/common/registry.service.ts b/api-node/src/common/registry.service.ts index 93f57cd65..a72b6eef2 100644 --- a/api-node/src/common/registry.service.ts +++ b/api-node/src/common/registry.service.ts @@ -1,26 +1,32 @@ import { Injectable } from '@nestjs/common'; import * as path from 'path'; import * as fs from 'fs'; -import type { SdkEntry, SdksResponse } from '../sdks/types'; -import { +import type { SdkEntry, Sdks, SdkVersions } from '../sdks/types'; +import type { MarketingSlugEntry, - MarketingSlugResolveResponse, - MarketingSlugResponse, -} from 'src/marketing/types'; -import { AppEntry, AppsResponse } from 'src/apps/types'; + ResolvedMarketingSlug, + MarketingSlugs, +} from '../marketing/types'; +import type { AppEntry, Apps } from '../apps/types'; +import type { + PackageEntry, + Packages, + PackageVersions, +} from '../packages/types'; +import type { AwsLambdaLayers } from '../aws-lambda-layers/types'; const SDKS_PATH = path.join('..', 'sdks'); const APPS_PATH = path.join('..', 'apps'); const PACKAGES_PATH = path.join('..', 'packages'); const AWS_LAMBDA_LAYERS_PATH = path.join('..', 'aws-lambda-layers'); -// Some packages have a __NAMESPACE__ file that contains the package name +// Some /packages sub-directories have a __NAMESPACE__ file that designates +// the directory path as a package namespace (e.g. @sentry of @sentry/browser) const NAMESPACE_FILE_MARKER = '__NAMESPACE__'; @Injectable() export class RegistryService { - // TODO: package types - #packages; + #packages: string[]; #slugs: Record; constructor() { @@ -31,8 +37,8 @@ export class RegistryService { } // SDKs - getSdks(strict: boolean = false): SdksResponse { - const sdks: SdksResponse = {}; + getSdks(strict: boolean = false): Sdks { + const sdks: Sdks = {}; try { const sdkLinks = fs.readdirSync(SDKS_PATH); for (const link of sdkLinks) { @@ -71,15 +77,15 @@ export class RegistryService { } } - getSdkVersions(sdkId: string): { latest: any; versions: string[] } { + getSdkVersions(sdkId: string): SdkVersions { const latest = this.getSdk(sdkId); - const { versions } = this.getPackageVersions(latest.canonical as string); + const { versions } = this.getPackageVersions(latest.canonical); return { latest, versions }; } // Packages - getPackages() { + getPackages(): Packages { return this.#packages.reduce((acc, canonical) => { const packageDir = getPackageDirFromCanonical(canonical); const latestFilePath = path.join(packageDir, 'latest.json'); @@ -99,7 +105,7 @@ export class RegistryService { }, {}); } - getPackageVersions(packageName: string): { latest: any; versions: string[] } { + getPackageVersions(packageName: string): PackageVersions { const packageDir = getPackageDirFromCanonical(packageName); try { const versions = fs @@ -125,8 +131,7 @@ export class RegistryService { } } - // TODO: rename to getPackage - getPackage(packageName: string, version: string = 'latest') { + getPackage(packageName: string, version: string = 'latest'): PackageEntry { try { const packageDir = getPackageDirFromCanonical(packageName); const versionFilePath = path.join(packageDir, `${version}.json`); @@ -139,7 +144,7 @@ export class RegistryService { // Apps - getApps(): AppsResponse { + getApps(): Apps { try { const apps = fs.readdirSync(APPS_PATH); return apps.reduce((acc, appName) => { @@ -171,11 +176,11 @@ export class RegistryService { // Marketing - getMarketingSlugs(): MarketingSlugResponse { + getMarketingSlugs(): MarketingSlugs { return { slugs: Object.keys(this.#slugs) }; } - resolveMarketingSlug(slug: string): MarketingSlugResolveResponse | null { + resolveMarketingSlug(slug: string): ResolvedMarketingSlug | null { const data = this.#slugs[slug]; if (!data) { return null; @@ -209,8 +214,8 @@ export class RegistryService { // AWS Lambda Layers - async getAwsLambdaLayers() { - const layers: Record = {}; + getAwsLambdaLayers(): AwsLambdaLayers { + const layers: AwsLambdaLayers = {}; const lambdaLayersDir = path.resolve(AWS_LAMBDA_LAYERS_PATH); const runtimeDirs = fs.readdirSync(AWS_LAMBDA_LAYERS_PATH); @@ -235,7 +240,7 @@ export class RegistryService { } } -function* iterPackages() { +function* iterPackages(): Generator { // Loop through each package registry const packageRegistries = fs.readdirSync(PACKAGES_PATH); for (const packageRegistry of packageRegistries) { @@ -276,7 +281,7 @@ function* iterPackages() { } } -function getPackageDirFromCanonical(canonicalPackageName: string) { +function getPackageDirFromCanonical(canonicalPackageName: string): string { const pkgPath = canonicalPackageName .replaceAll(':', path.sep) .split(path.sep); diff --git a/api-node/src/main.ts b/api-node/src/main.ts index 13cad38cf..545babdb7 100644 --- a/api-node/src/main.ts +++ b/api-node/src/main.ts @@ -1,8 +1,9 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; -async function bootstrap() { +async function bootstrap(): Promise { const app = await NestFactory.create(AppModule); await app.listen(3000); } + bootstrap(); diff --git a/api-node/src/marketing/marketing.controller.ts b/api-node/src/marketing/marketing.controller.ts index 45ae88279..20bec3ba8 100644 --- a/api-node/src/marketing/marketing.controller.ts +++ b/api-node/src/marketing/marketing.controller.ts @@ -1,5 +1,5 @@ import { Controller, Get, Param, NotFoundException } from '@nestjs/common'; -import { MarketingSlugResponse, MarketingSlugResolveResponse } from './types'; +import { MarketingSlugs, ResolvedMarketingSlug } from './types'; import { RegistryService } from '../common/registry.service'; @Controller('marketing-slugs') @@ -7,14 +7,12 @@ export class MarketingController { constructor(private readonly registryService: RegistryService) {} @Get() - getMarketingSlugs(): MarketingSlugResponse { + getMarketingSlugs(): MarketingSlugs { return this.registryService.getMarketingSlugs(); } @Get(':slug') - resolveMarketingSlug( - @Param('slug') slug: string, - ): MarketingSlugResolveResponse { + resolveMarketingSlug(@Param('slug') slug: string): ResolvedMarketingSlug { const result = this.registryService.resolveMarketingSlug(slug); if (!result) { throw new NotFoundException(); diff --git a/api-node/src/marketing/types.ts b/api-node/src/marketing/types.ts index 32644c0b5..3c023e5e3 100644 --- a/api-node/src/marketing/types.ts +++ b/api-node/src/marketing/types.ts @@ -1,3 +1,6 @@ +import { PackageEntry } from 'src/packages/types'; +import { SdkEntry } from 'src/sdks/types'; + export interface MarketingSlugEntry { type: string; target?: string; @@ -6,11 +9,11 @@ export interface MarketingSlugEntry { package?: string; } -export interface MarketingSlugResponse { +export interface MarketingSlugs { slugs: string[]; } -export interface MarketingSlugResolveResponse { +export interface ResolvedMarketingSlug { definition: { type: string; target?: string; @@ -18,5 +21,5 @@ export interface MarketingSlugResolveResponse { sdk?: string; package?: string; }; - target: any; + target: SdkEntry | PackageEntry | null; } diff --git a/api-node/src/packages/packages.controller.ts b/api-node/src/packages/packages.controller.ts index db377e6e2..176e75dd1 100644 --- a/api-node/src/packages/packages.controller.ts +++ b/api-node/src/packages/packages.controller.ts @@ -1,17 +1,18 @@ import { Controller, Get, Param } from '@nestjs/common'; import { RegistryService } from '../common/registry.service'; +import { PackageEntry, Packages, PackageVersions } from './types'; @Controller('packages') export class PackagesController { constructor(private registryService: RegistryService) {} @Get() - getPackages() { + getPackages(): Packages { return this.registryService.getPackages(); } @Get('/:package(*)/versions') - getPackageVersions(@Param('package') pgkName: string) { + getPackageVersions(@Param('package') pgkName: string): PackageVersions { return this.registryService.getPackageVersions(pgkName); } @@ -19,7 +20,7 @@ export class PackagesController { getPackageByVersion( @Param('package') pkgName: string, @Param('version') version: string, - ) { + ): PackageEntry { return this.registryService.getPackage(pkgName, version); } } diff --git a/api-node/src/packages/types.ts b/api-node/src/packages/types.ts new file mode 100644 index 000000000..574a84af9 --- /dev/null +++ b/api-node/src/packages/types.ts @@ -0,0 +1,28 @@ +export interface PackageEntry { + name: string; + canonical: string; + version: string; + repo_url: string; + main_docs_url: string; + created_at: string; + package_url?: string; + files?: Record; + categories?: string[]; + features?: Record; +} + +interface FileEntry { + checksums: Record; +} + +interface FeatureEntry { + availability: string | null; + 'available-with-version': string | null; +} + +export interface PackageVersions { + latest: PackageEntry; + versions: string[]; +} + +export type Packages = Record; diff --git a/api-node/src/sdks/sdks.controller.ts b/api-node/src/sdks/sdks.controller.ts index 526d18b65..a2e624a07 100644 --- a/api-node/src/sdks/sdks.controller.ts +++ b/api-node/src/sdks/sdks.controller.ts @@ -1,5 +1,5 @@ import { Controller, Get, Param, Query } from '@nestjs/common'; -import { SdkEntry, SdksResponse, SdkVersionsResponse } from './types'; +import { SdkEntry, Sdks, SdkVersions } from './types'; import { RegistryService } from '../common/registry.service'; @Controller('sdks') @@ -7,14 +7,14 @@ export class SdksController { constructor(private registryService: RegistryService) {} @Get() - getSdks(@Query('strict') strict?: string): SdksResponse { + getSdks(@Query('strict') strict?: string): Sdks { const isStrict = strict?.toLowerCase() === 'true' || strict === '1' || strict === 'yes'; return this.registryService.getSdks(isStrict); } @Get('/:sdkId/versions') - getSdkVersions(@Param('sdkId') sdkId: string): SdkVersionsResponse { + getSdkVersions(@Param('sdkId') sdkId: string): SdkVersions { return this.registryService.getSdkVersions(sdkId); } diff --git a/api-node/src/sdks/types.ts b/api-node/src/sdks/types.ts index 5ab1a7c40..8c8df564f 100644 --- a/api-node/src/sdks/types.ts +++ b/api-node/src/sdks/types.ts @@ -5,13 +5,13 @@ export interface SdkEntry { // Add other fields as needed } -export type SdksResponse = Record; +export type Sdks = Record; export interface SdkEntry { canonical: string; } -export interface SdkVersionsResponse { +export interface SdkVersions { latest: SdkEntry; versions: string[]; } diff --git a/api-node/test/marketing.e2e-spec.ts b/api-node/test/marketing.e2e-spec.ts index 985e7096a..4ab90783e 100644 --- a/api-node/test/marketing.e2e-spec.ts +++ b/api-node/test/marketing.e2e-spec.ts @@ -37,8 +37,6 @@ describe('MarketingController (e2e)', () => { ); const pythonApiData = await pythonApiResponse.json(); - console.log(pythonApiData); - return request(app.getHttpServer()) .get(`/marketing-slugs/${slug}`) .expect((res) => { diff --git a/api-node/yarn.lock b/api-node/yarn.lock index bc95a8a51..4a1f0bfbb 100644 --- a/api-node/yarn.lock +++ b/api-node/yarn.lock @@ -1031,62 +1031,62 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^8.0.0": - version "8.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz#d0070f206daad26253bf00ca5b80f9b54f9e2dd0" - integrity sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A== +"@typescript-eslint/eslint-plugin@^8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.9.0.tgz#bf0b25305b0bf014b4b194a6919103d7ac2a7907" + integrity sha512-Y1n621OCy4m7/vTXNlCbMVp87zSd7NH0L9cXD8aIpOaNlzeWxIK4+Q19A68gSmTNRZn92UjocVUWDthGxtqHFg== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.7.0" - "@typescript-eslint/type-utils" "8.7.0" - "@typescript-eslint/utils" "8.7.0" - "@typescript-eslint/visitor-keys" "8.7.0" + "@typescript-eslint/scope-manager" "8.9.0" + "@typescript-eslint/type-utils" "8.9.0" + "@typescript-eslint/utils" "8.9.0" + "@typescript-eslint/visitor-keys" "8.9.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^8.0.0": - version "8.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.7.0.tgz#a567b0890d13db72c7348e1d88442ea8ab4e9173" - integrity sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ== +"@typescript-eslint/parser@^8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.9.0.tgz#0cecda6def8aef95d7c7098359c0fda5a362d6ad" + integrity sha512-U+BLn2rqTTHnc4FL3FJjxaXptTxmf9sNftJK62XLz4+GxG3hLHm/SUNaaXP5Y4uTiuYoL5YLy4JBCJe3+t8awQ== dependencies: - "@typescript-eslint/scope-manager" "8.7.0" - "@typescript-eslint/types" "8.7.0" - "@typescript-eslint/typescript-estree" "8.7.0" - "@typescript-eslint/visitor-keys" "8.7.0" + "@typescript-eslint/scope-manager" "8.9.0" + "@typescript-eslint/types" "8.9.0" + "@typescript-eslint/typescript-estree" "8.9.0" + "@typescript-eslint/visitor-keys" "8.9.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@8.7.0": - version "8.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz#90ee7bf9bc982b9260b93347c01a8bc2b595e0b8" - integrity sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg== +"@typescript-eslint/scope-manager@8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz#c98fef0c4a82a484e6a1eb610a55b154d14d46f3" + integrity sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ== dependencies: - "@typescript-eslint/types" "8.7.0" - "@typescript-eslint/visitor-keys" "8.7.0" + "@typescript-eslint/types" "8.9.0" + "@typescript-eslint/visitor-keys" "8.9.0" -"@typescript-eslint/type-utils@8.7.0": - version "8.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz#d56b104183bdcffcc434a23d1ce26cde5e42df93" - integrity sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ== +"@typescript-eslint/type-utils@8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.9.0.tgz#aa86da3e4555fe7c8b42ab75e13561c4b5a8dfeb" + integrity sha512-JD+/pCqlKqAk5961vxCluK+clkppHY07IbV3vett97KOV+8C6l+CPEPwpUuiMwgbOz/qrN3Ke4zzjqbT+ls+1Q== dependencies: - "@typescript-eslint/typescript-estree" "8.7.0" - "@typescript-eslint/utils" "8.7.0" + "@typescript-eslint/typescript-estree" "8.9.0" + "@typescript-eslint/utils" "8.9.0" debug "^4.3.4" ts-api-utils "^1.3.0" -"@typescript-eslint/types@8.7.0": - version "8.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.7.0.tgz#21d987201c07b69ce7ddc03451d7196e5445ad19" - integrity sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w== +"@typescript-eslint/types@8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.9.0.tgz#b733af07fb340b32e962c6c63b1062aec2dc0fe6" + integrity sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ== -"@typescript-eslint/typescript-estree@8.7.0": - version "8.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz#6c7db6baa4380b937fa81466c546d052f362d0e8" - integrity sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg== +"@typescript-eslint/typescript-estree@8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz#1714f167e9063062dc0df49c1d25afcbc7a96199" + integrity sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g== dependencies: - "@typescript-eslint/types" "8.7.0" - "@typescript-eslint/visitor-keys" "8.7.0" + "@typescript-eslint/types" "8.9.0" + "@typescript-eslint/visitor-keys" "8.9.0" debug "^4.3.4" fast-glob "^3.3.2" is-glob "^4.0.3" @@ -1094,22 +1094,22 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@8.7.0": - version "8.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.7.0.tgz#cef3f70708b5b5fd7ed8672fc14714472bd8a011" - integrity sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw== +"@typescript-eslint/utils@8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.9.0.tgz#748bbe3ea5bee526d9786d9405cf1b0df081c299" + integrity sha512-PKgMmaSo/Yg/F7kIZvrgrWa1+Vwn036CdNUvYFEkYbPwOH4i8xvkaRlu148W3vtheWK9ckKRIz7PBP5oUlkrvQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.7.0" - "@typescript-eslint/types" "8.7.0" - "@typescript-eslint/typescript-estree" "8.7.0" + "@typescript-eslint/scope-manager" "8.9.0" + "@typescript-eslint/types" "8.9.0" + "@typescript-eslint/typescript-estree" "8.9.0" -"@typescript-eslint/visitor-keys@8.7.0": - version "8.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz#5e46f1777f9d69360a883c1a56ac3c511c9659a8" - integrity sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ== +"@typescript-eslint/visitor-keys@8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz#5f11f4d9db913f37da42776893ffe0dd1ae78f78" + integrity sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA== dependencies: - "@typescript-eslint/types" "8.7.0" + "@typescript-eslint/types" "8.9.0" eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": diff --git a/aws-lambda-layers/node/8.32.0.json b/aws-lambda-layers/node/8.32.0.json index 618a636a7..4ef356bfc 100644 --- a/aws-lambda-layers/node/8.32.0.json +++ b/aws-lambda-layers/node/8.32.0.json @@ -1 +1,33 @@ -{"name":"AWS Lambda Node Layer","repo_url":"https://github.com/getsentry/sentry-javascript","main_docs_url":"https://docs.sentry.io/platforms/node/guides/aws-lambda","created_at":"2021-02-01T14:28:25.000Z","canonical":"aws-layer:node","sdk_version":"8.32.0","account_number":"943013980633","layer_name":"SentryNodeServerlessSDK","regions":[{"region":"af-south-1","version":"242"},{"region":"ap-south-1","version":"275"},{"region":"eu-north-1","version":"275"},{"region":"eu-west-3","version":"275"},{"region":"eu-south-1","version":"242"},{"region":"eu-west-2","version":"275"},{"region":"eu-west-1","version":"275"},{"region":"ap-northeast-3","version":"267"},{"region":"ap-northeast-2","version":"275"},{"region":"me-south-1","version":"242"},{"region":"ap-northeast-1","version":"275"},{"region":"ca-central-1","version":"275"},{"region":"sa-east-1","version":"275"},{"region":"ap-east-1","version":"242"},{"region":"ap-southeast-1","version":"275"},{"region":"ap-southeast-2","version":"275"},{"region":"eu-central-1","version":"275"},{"region":"us-east-1","version":"275"},{"region":"us-east-2","version":"275"},{"region":"us-west-1","version":"275"},{"region":"us-west-2","version":"275"}]} \ No newline at end of file +{ + "name": "AWS Lambda Node Layer", + "repo_url": "https://github.com/getsentry/sentry-javascript", + "main_docs_url": "https://docs.sentry.io/platforms/node/guides/aws-lambda", + "created_at": "2021-02-01T14:28:25.000Z", + "canonical": "aws-layer:node", + "sdk_version": "8.32.0", + "account_number": "943013980633", + "layer_name": "SentryNodeServerlessSDK", + "regions": [ + { "region": "af-south-1", "version": "242" }, + { "region": "ap-south-1", "version": "275" }, + { "region": "eu-north-1", "version": "275" }, + { "region": "eu-west-3", "version": "275" }, + { "region": "eu-south-1", "version": "242" }, + { "region": "eu-west-2", "version": "275" }, + { "region": "eu-west-1", "version": "275" }, + { "region": "ap-northeast-3", "version": "267" }, + { "region": "ap-northeast-2", "version": "275" }, + { "region": "me-south-1", "version": "242" }, + { "region": "ap-northeast-1", "version": "275" }, + { "region": "ca-central-1", "version": "275" }, + { "region": "sa-east-1", "version": "275" }, + { "region": "ap-east-1", "version": "242" }, + { "region": "ap-southeast-1", "version": "275" }, + { "region": "ap-southeast-2", "version": "275" }, + { "region": "eu-central-1", "version": "275" }, + { "region": "us-east-1", "version": "275" }, + { "region": "us-east-2", "version": "275" }, + { "region": "us-west-1", "version": "275" }, + { "region": "us-west-2", "version": "275" } + ] +} From e05bac55fe41497c9460043d7f091a670cc5ebf1 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 16:17:04 +0200 Subject: [PATCH 17/54] add ci for node api --- .github/workflows/node-api.yml | 111 +++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 .github/workflows/node-api.yml diff --git a/.github/workflows/node-api.yml b/.github/workflows/node-api.yml new file mode 100644 index 000000000..4068890c9 --- /dev/null +++ b/.github/workflows/node-api.yml @@ -0,0 +1,111 @@ +name: Node API CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + setup: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: "20.x" + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/yarn + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Install dependencies + working-directory: ./api-node + run: yarn install --frozen-lockfile + + build: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: "20.x" + - name: Restore cache + uses: actions/cache@v3 + with: + path: ~/.cache/yarn + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + - name: Install dependencies + working-directory: ./api-node + run: yarn install --frozen-lockfile + - name: Build + working-directory: ./api-node + run: yarn build + + lint: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: "20.x" + - name: Restore cache + uses: actions/cache@v3 + with: + path: ~/.cache/yarn + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + - name: Install dependencies + working-directory: ./api-node + run: yarn install --frozen-lockfile + - name: Lint + working-directory: ./api-node + run: yarn lint + + test: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: "20.x" + - name: Restore cache + uses: actions/cache@v3 + with: + path: ~/.cache/yarn + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + - name: Install dependencies + working-directory: ./api-node + run: yarn install --frozen-lockfile + - name: Run unit tests + working-directory: ./api-node + run: yarn test + + e2e-test: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: "20.x" + - name: Restore cache + uses: actions/cache@v3 + with: + path: ~/.cache/yarn + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + - name: Install dependencies + working-directory: ./api-node + run: yarn install --frozen-lockfile + - name: Run e2e tests + working-directory: ./api-node + run: yarn test:e2e From fda682cf7ebf07fff07907eb287516d1002c6c7d Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 16:17:56 +0200 Subject: [PATCH 18/54] update yarn lock --- api-node/yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api-node/yarn.lock b/api-node/yarn.lock index 4a1f0bfbb..f5bbd7b48 100644 --- a/api-node/yarn.lock +++ b/api-node/yarn.lock @@ -4607,10 +4607,10 @@ typescript@5.3.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== -typescript@^5.1.3: - version "5.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" - integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== +typescript@^5.6.2: + version "5.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" + integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== uid@2.0.2: version "2.0.2" From ff63bafecc1beeabe5430d881f235333d2c125d5 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 16:53:42 +0200 Subject: [PATCH 19/54] add unit tests for package utils --- api-node/src/apps/utils.spec.ts | 158 ++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 api-node/src/apps/utils.spec.ts diff --git a/api-node/src/apps/utils.spec.ts b/api-node/src/apps/utils.spec.ts new file mode 100644 index 000000000..141d836aa --- /dev/null +++ b/api-node/src/apps/utils.spec.ts @@ -0,0 +1,158 @@ +import { AppEntry } from './types'; +import { getUrlChecksums, makeDigest, findDownloadUrl } from './utils'; + +describe('findDownloadUrl', () => { + it('returns the correct URL for a matching package', () => { + const appInfo: Partial = { + file_urls: { + 'sentry-cli-Linux-x86_64': + 'https://downloads.sentry-cdn.com/sentry-cli/2.20.6/sentry-cli-Linux-x86_64', + }, + }; + + // @ts-expect-error - this is fine, passing a partial here + const result = findDownloadUrl(appInfo, 'sentry-cli', 'x86_64', 'Linux'); + expect(result).toBe( + 'https://downloads.sentry-cdn.com/sentry-cli/2.20.6/sentry-cli-Linux-x86_64', + ); + }); + + it('returns null for a non-matching package', () => { + const appInfo: Partial = { + file_urls: { + 'sentry-cli-Linux-x86_64': + 'https://downloads.sentry-cdn.com/sentry-cli/2.20.6/sentry-cli-Linux-x86_64', + }, + }; + + // @ts-expect-error - this is fine, passing a partial here + const result = findDownloadUrl(appInfo, 'santry-cli', 'x64', 'linux'); + expect(result).toBeNull(); + }); + + it('handles .exe files correctly', () => { + const appInfo: Partial = { + file_urls: { + 'relay-Windows-x86_64.exe': + 'https://downloads.sentry-cdn.com/relay/24.9.0/relay-Windows-x86_64.exe', + }, + }; + + // @ts-expect-error - this is fine, passing a partial here + const result = findDownloadUrl(appInfo, 'relay', 'x86_64', 'windows'); + expect(result).toBe( + 'https://downloads.sentry-cdn.com/relay/24.9.0/relay-Windows-x86_64.exe', + ); + }); + + it('is case-insensitive for platform and arch', () => { + const appInfo: Partial = { + file_urls: { + 'relay-Windows-x86_64.exe': + 'https://downloads.sentry-cdn.com/relay/24.9.0/relay-Windows-x86_64.exe', + }, + }; + + // @ts-expect-error - this is fine, passing a partial here + const result = findDownloadUrl(appInfo, 'ReLaY', 'X86_64', 'WIndOws'); + expect(result).toBe( + 'https://downloads.sentry-cdn.com/relay/24.9.0/relay-Windows-x86_64.exe', + ); + }); +}); + +describe('getUrlChecksums', () => { + it('returns checksums for a matching URL', () => { + const appInfo: Partial = { + files: { + file1: { + url: 'https://example.com/file1.zip', + checksums: { 'sha256-hex': 'abcdef123456' }, + }, + file2: { + url: 'https://example.com/file2.zip', + checksums: { 'sha256-hex': '987654321fedcba' }, + }, + }, + }; + + // @ts-expect-error - this is fine, passing a partial here + const result = getUrlChecksums(appInfo, 'https://example.com/file1.zip'); + expect(result).toEqual({ 'sha256-hex': 'abcdef123456' }); + }); + + it('returns null for a non-matching URL', () => { + const appInfo: Partial = { + files: { + file1: { + url: 'https://example.com/file1.zip', + checksums: { 'sha256-hex': 'abcdef123456' }, + }, + }, + }; + + const result = getUrlChecksums( + // @ts-expect-error - this is fine, passing a partial here + appInfo, + 'https://example.com/nonexistent.zip', + ); + expect(result).toBeNull(); + }); + + it('returns null when checksums are not present', () => { + const appInfo: Partial = { + files: { + file1: { + url: 'https://example.com/file1.zip', + }, + }, + }; + + // @ts-expect-error - this is fine, passing a partial here + const result = getUrlChecksums(appInfo, 'https://example.com/file1.zip'); + expect(result).toBeNull(); + }); + + it('handles empty files object', () => { + const appInfo: AppEntry = { + files: {}, + } as AppEntry; + + const result = getUrlChecksums(appInfo, 'https://example.com/file1.zip'); + expect(result).toBeNull(); + }); +}); + +describe('makeDigest', () => { + it('returns an empty string for null input', () => { + expect(makeDigest(null)).toBe(''); + }); + + it('correctly processes hex checksums', () => { + const input = { 'sha256-hex': '68656c6c6f' }; // 'hello' in hex + expect(makeDigest(input)).toBe('sha256=aGVsbG8='); + }); + + it('correctly processes base64 checksums', () => { + const input = { 'sha256-base64': 'aGVsbG8=' }; // 'hello' in base64 + expect(makeDigest(input)).toBe('sha256=aGVsbG8='); + }); + + it('handles multiple checksums', () => { + const input = { + 'sha256-hex': '68656c6c6f', + 'md5-base64': 'XUFAKrxLKna5cZ2REBfFkg==', + }; + expect(makeDigest(input)).toBe( + 'sha256=aGVsbG8=,md5=XUFAKrxLKna5cZ2REBfFkg==', + ); + }); + + it('ignores checksums that do not end with -hex or -base64', () => { + const input = { + 'sha256-hex': '68656c6c6f', + 'md5-invalid': 'ignored', + }; + expect(makeDigest(input)).toBe('sha256=aGVsbG8='); + }); +}); From e03f8c1344ae15a09b0804cd0491195523eca142 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 16:57:49 +0200 Subject: [PATCH 20/54] adjust CI e2e tests --- .github/workflows/node-api.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/node-api.yml b/.github/workflows/node-api.yml index 4068890c9..bee7fae1e 100644 --- a/.github/workflows/node-api.yml +++ b/.github/workflows/node-api.yml @@ -8,6 +8,7 @@ on: jobs: setup: + name: Install Dependencies runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -27,6 +28,7 @@ jobs: run: yarn install --frozen-lockfile build: + name: Build needs: setup runs-on: ubuntu-latest steps: @@ -48,6 +50,7 @@ jobs: run: yarn build lint: + name: Lint needs: setup runs-on: ubuntu-latest steps: @@ -69,6 +72,7 @@ jobs: run: yarn lint test: + name: Unit Tests needs: setup runs-on: ubuntu-latest steps: @@ -90,6 +94,7 @@ jobs: run: yarn test e2e-test: + name: E2E Tests needs: setup runs-on: ubuntu-latest steps: @@ -108,4 +113,6 @@ jobs: run: yarn install --frozen-lockfile - name: Run e2e tests working-directory: ./api-node - run: yarn test:e2e + run: | + yarn start:python-api & + yarn test:e2e From cb161d8aa57a62de6353c0a7e416dcab94b113e2 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 17:05:36 +0200 Subject: [PATCH 21/54] adjust python api build/start scripts --- .github/workflows/node-api.yml | 6 +++--- api-node/package.json | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/node-api.yml b/.github/workflows/node-api.yml index bee7fae1e..897ef525f 100644 --- a/.github/workflows/node-api.yml +++ b/.github/workflows/node-api.yml @@ -111,8 +111,8 @@ jobs: - name: Install dependencies working-directory: ./api-node run: yarn install --frozen-lockfile + - name: Build Python API + run: yarn python-api:build - name: Run e2e tests working-directory: ./api-node - run: | - yarn start:python-api & - yarn test:e2e + run: yarn python-api:start && yarn test:e2e diff --git a/api-node/package.json b/api-node/package.json index 9ab5261d3..c3bb4112c 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -18,7 +18,8 @@ "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", - "start:python-api": "docker build -t registry-api-server ../ && docker run -p 5031:5030 registry-api-server" + "python-api:build": "docker build -t registry-api-server ../", + "python-api:start": "docker run -d -p 5031:5030 registry-api-server" }, "dependencies": { "@nestjs/common": "^10.0.0", From aedcb62cc27ede0d10d3158cd0e3146ab616d5b8 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 16 Oct 2024 17:08:47 +0200 Subject: [PATCH 22/54] working dir --- .github/workflows/node-api.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/node-api.yml b/.github/workflows/node-api.yml index 897ef525f..2b71a45a5 100644 --- a/.github/workflows/node-api.yml +++ b/.github/workflows/node-api.yml @@ -112,6 +112,7 @@ jobs: working-directory: ./api-node run: yarn install --frozen-lockfile - name: Build Python API + working-directory: ./api-node run: yarn python-api:build - name: Run e2e tests working-directory: ./api-node From f9e5240a0944d5b99aa30888f4e464fea26e2981 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 17 Oct 2024 09:34:23 +0200 Subject: [PATCH 23/54] fix test --- .github/workflows/node-api.yml | 10 ++++-- api-node/package.json | 3 +- api-node/test/apps.e2e-spec.ts | 4 +-- api-server/apiserver.py | 56 +++++++++++++++++----------------- 4 files changed, 40 insertions(+), 33 deletions(-) diff --git a/.github/workflows/node-api.yml b/.github/workflows/node-api.yml index 2b71a45a5..0a7db478c 100644 --- a/.github/workflows/node-api.yml +++ b/.github/workflows/node-api.yml @@ -1,4 +1,4 @@ -name: Node API CI +name: Node API on: push: @@ -114,6 +114,12 @@ jobs: - name: Build Python API working-directory: ./api-node run: yarn python-api:build + - name: Start Python API + working-directory: ./api-node + run: yarn python-api:start - name: Run e2e tests working-directory: ./api-node - run: yarn python-api:start && yarn test:e2e + run: yarn test:e2e + - name: Stop Python API + working-directory: ./api-node + run: yarn python-api:stop diff --git a/api-node/package.json b/api-node/package.json index c3bb4112c..78ae24dc0 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -19,7 +19,8 @@ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "python-api:build": "docker build -t registry-api-server ../", - "python-api:start": "docker run -d -p 5031:5030 registry-api-server" + "python-api:start": "docker run -d -p 5031:5030 registry-api-server", + "python-api:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-server)" }, "dependencies": { "@nestjs/common": "^10.0.0", diff --git a/api-node/test/apps.e2e-spec.ts b/api-node/test/apps.e2e-spec.ts index cbf901928..24d125f68 100644 --- a/api-node/test/apps.e2e-spec.ts +++ b/api-node/test/apps.e2e-spec.ts @@ -66,7 +66,7 @@ describe('AppsController (e2e)', () => { it('with response=download', async () => { const appId = 'sentry-cli'; - const version = 'latest'; + const version = '2.36.3'; const arch = 'x86_64'; const platform = 'linux'; const pkgName = 'sentry-cli'; @@ -93,7 +93,7 @@ describe('AppsController (e2e)', () => { pythonApiResponse.headers.get('location'), ); expect(r.header.location).toEqual( - 'https://downloads.sentry-cdn.com/sentry-cli/2.36.3/sentry-cli-Linux-x86_64', + `https://downloads.sentry-cdn.com/sentry-cli/${version}/sentry-cli-Linux-x86_64`, ); expect(r.header.digest).toEqual( 'sha256=8cs/OTYjDCCuSrIIAmFPoggGPWI599KeBquwkXqTQRg=', diff --git a/api-server/apiserver.py b/api-server/apiserver.py index e6dc6ee66..7580a6ea9 100644 --- a/api-server/apiserver.py +++ b/api-server/apiserver.py @@ -2,7 +2,7 @@ import binascii from functools import partial -#from datadog import initialize as datadog_initialize, statsd +from datadog import initialize as datadog_initialize, statsd import sentry_sdk from flask import Flask, json, abort, jsonify, request, redirect @@ -16,36 +16,36 @@ NAMESPACE_FILE_MARKER = "__NAMESPACE__" -# class Metrics: +class Metrics: -# PREFIX = os.getenv("METRICS_PREFIX", "release_registry") + PREFIX = os.getenv("METRICS_PREFIX", "release_registry") -# def initialize(self, **kwargs): -# # DATADOG_API_KEY, DATADOG_APP_KEY can be provided from env -# # TODO(michal): Pass statsd_constant_tags from env? -# #datadog_initialize(**kwargs) -# return self + def initialize(self, **kwargs): + # DATADOG_API_KEY, DATADOG_APP_KEY can be provided from env + # TODO(michal): Pass statsd_constant_tags from env? + datadog_initialize(**kwargs) + return self -# @staticmethod -# def tags_from_request(): -# tags = [] -# if request.url_rule: -# tags.append(f"route:{request.url_rule.rule}") -# return tags + @staticmethod + def tags_from_request(): + tags = [] + if request.url_rule: + tags.append(f"route:{request.url_rule.rule}") + return tags -# def _metric(self, name): -# return f"{self.PREFIX}.{name}" + def _metric(self, name): + return f"{self.PREFIX}.{name}" -# def increment(self, name, value=1, tags=None, sample_rate=None): -# statsd.increment( -# self._metric(name), -# value=value, -# tags=self.tags_from_request() + (tags or []), -# sample_rate=sample_rate, -# ) + def increment(self, name, value=1, tags=None, sample_rate=None): + statsd.increment( + self._metric(name), + value=value, + tags=self.tags_from_request() + (tags or []), + sample_rate=sample_rate, + ) -# metrics = Metrics().initialize() +metrics = Metrics().initialize() # SENTRY_DSN will be taken from env sentry_sdk.init(integrations=[FlaskIntegration()]) @@ -284,17 +284,17 @@ def return_cached(): if not request.values: response = cache.get(request.path) if response: - #metrics.increment("cache_hit") + metrics.increment("cache_hit") response.headers["X-From-Cache"] = "1" return response - #metrics.increment("cache_miss") + metrics.increment("cache_miss") def cache_response(response): if not request.values: # Make the response picklable response.freeze() - #metrics.increment("cache_set") + metrics.increment("cache_set") cache.set(request.path, response) return response @@ -353,7 +353,7 @@ def get_url_checksums(app_info, url): # Must come before the caching callbacks otherwise it will never be called for # request served by the caching callback. -#app.before_request(partial(metrics.increment, "request")) +app.before_request(partial(metrics.increment, "request")) app.enable_cache = partial(set_cache_enabled, app) app.enable_cache(is_caching_enabled()) From 011f05c65c51502201acf60a6dac62776512221d Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 17 Oct 2024 09:58:54 +0200 Subject: [PATCH 24/54] add test for js browser --- api-node/test/sdks.e2e-spec.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/api-node/test/sdks.e2e-spec.ts b/api-node/test/sdks.e2e-spec.ts index 59d9c7172..67307d718 100644 --- a/api-node/test/sdks.e2e-spec.ts +++ b/api-node/test/sdks.e2e-spec.ts @@ -116,5 +116,23 @@ describe('SdksController (e2e)', () => { expect(latest).toEqual(pythonApiData.latest); }); }); + + it('Javascript Browser', async () => { + const sdkId = 'sentry.javascript.browser'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/sdks/${sdkId}/versions`, + ); + const pythonApiData = await pythonApiResponse.json(); + + return request(app.getHttpServer()) + .get(`/sdks/${sdkId}/versions`) + .expect((r) => { + expect(r.status).toEqual(200); + const { versions, latest } = r.body; + expect(versions.sort()).toEqual(pythonApiData.versions.sort()); + expect(latest).toEqual(pythonApiData.latest); + }); + }); }); }); From 78c9d0f71da36480b840ae6d3a38ab4b480043bb Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 17 Oct 2024 10:39:17 +0200 Subject: [PATCH 25/54] add sentry --- api-node/package.json | 9 +- api-node/src/app.module.ts | 10 +- api-node/src/instrument.ts | 12 + api-node/src/main.ts | 2 + api-node/tsconfig.json | 4 +- api-node/yarn.lock | 633 ++++++++++++++++++++++++++++++++++++- 6 files changed, 657 insertions(+), 13 deletions(-) create mode 100644 api-node/src/instrument.ts diff --git a/api-node/package.json b/api-node/package.json index 78ae24dc0..1067bbd42 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -6,7 +6,7 @@ "private": true, "license": "UNLICENSED", "scripts": { - "build": "nest build", + "build": "nest build && yarn sentry:sourcemaps", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", @@ -20,12 +20,15 @@ "test:e2e": "jest --config ./test/jest-e2e.json", "python-api:build": "docker build -t registry-api-server ../", "python-api:start": "docker run -d -p 5031:5030 registry-api-server", - "python-api:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-server)" + "python-api:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-server)", + "sentry:sourcemaps": "sentry-cli sourcemaps inject --org sentry --project release-registry-nestjs ./dist && sentry-cli sourcemaps upload --org sentry --project release-registry-nestjs ./dist" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", + "@sentry/cli": "^2.37.0", + "@sentry/nestjs": "^8.34.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1" }, @@ -69,4 +72,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} +} \ No newline at end of file diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index d82b63fdb..9324c1140 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -6,9 +6,10 @@ import { AppsController } from './apps/apps.controller'; import { SdksController } from './sdks/sdks.controller'; import { AwsLambdaLayersController } from './aws-lambda-layers/aws-lambda-layers.controller'; import { RegistryService } from './common/registry.service'; - +import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; +import { APP_FILTER } from '@nestjs/core'; @Module({ - imports: [], + imports: [SentryModule.forRoot()], controllers: [ HealthCheckController, PackagesController, @@ -17,6 +18,9 @@ import { RegistryService } from './common/registry.service'; SdksController, AwsLambdaLayersController, ], - providers: [RegistryService], + providers: [ + RegistryService, + { provide: APP_FILTER, useClass: SentryGlobalFilter }, + ], }) export class AppModule {} diff --git a/api-node/src/instrument.ts b/api-node/src/instrument.ts new file mode 100644 index 000000000..516807257 --- /dev/null +++ b/api-node/src/instrument.ts @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/nestjs'; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const packageJson = require('../package.json'); + +Sentry.init({ + dsn: process.env.SENTRY_DSN, + release: packageJson.version, + environment: process.env.NODE_ENV || 'development', + tracesSampleRate: 1.0, + debug: true, +}); diff --git a/api-node/src/main.ts b/api-node/src/main.ts index 545babdb7..508b7be54 100644 --- a/api-node/src/main.ts +++ b/api-node/src/main.ts @@ -1,3 +1,5 @@ +import './instrument'; + import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; diff --git a/api-node/tsconfig.json b/api-node/tsconfig.json index 95f5641cf..cc47eb846 100644 --- a/api-node/tsconfig.json +++ b/api-node/tsconfig.json @@ -16,6 +16,8 @@ "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "inlineSources": true, + "sourceRoot": "/" } } diff --git a/api-node/yarn.lock b/api-node/yarn.lock index f5bbd7b48..374677cac 100644 --- a/api-node/yarn.lock +++ b/api-node/yarn.lock @@ -774,6 +774,293 @@ consola "^2.15.0" node-fetch "^2.6.1" +"@opentelemetry/api-logs@0.52.1": + version "0.52.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz#52906375da4d64c206b0c4cb8ffa209214654ecc" + integrity sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A== + dependencies: + "@opentelemetry/api" "^1.0.0" + +"@opentelemetry/api-logs@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz#c478cbd8120ec2547b64edfa03a552cfe42170be" + integrity sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw== + dependencies: + "@opentelemetry/api" "^1.0.0" + +"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.8", "@opentelemetry/api@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" + integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== + +"@opentelemetry/context-async-hooks@^1.25.1": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.26.0.tgz#fa92f722cf685685334bba95f258d3ef9fce60f6" + integrity sha512-HedpXXYzzbaoutw6DFLWLDket2FwLkLpil4hGCZ1xYEIMTcivdfwEOISgdbLEWyG3HW52gTq2V9mOVJrONgiwg== + +"@opentelemetry/core@1.26.0", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.25.1", "@opentelemetry/core@^1.8.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.26.0.tgz#7d84265aaa850ed0ca5813f97d831155be42b328" + integrity sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.27.0" + +"@opentelemetry/instrumentation-amqplib@^0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.42.0.tgz#b3cab5a7207736a30d769962eed3af3838f986c4" + integrity sha512-fiuU6OKsqHJiydHWgTRQ7MnIrJ2lEqsdgFtNIH4LbAUJl/5XmrIeoDzDnox+hfkgWK65jsleFuQDtYb5hW1koQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-connect@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.39.0.tgz#32bdbaac464cba061c95df6c850ee81efdd86f8b" + integrity sha512-pGBiKevLq7NNglMgqzmeKczF4XQMTOUOTkK8afRHMZMnrK3fcETyTH7lVaSozwiOM3Ws+SuEmXZT7DYrrhxGlg== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@types/connect" "3.4.36" + +"@opentelemetry/instrumentation-dataloader@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.12.0.tgz#de03a3948dec4f15fed80aa424d6bd5d6a8d10c7" + integrity sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + +"@opentelemetry/instrumentation-express@0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.42.0.tgz#279f195aa66baee2b98623a16666c6229c8e7564" + integrity sha512-YNcy7ZfGnLsVEqGXQPT+S0G1AE46N21ORY7i7yUQyfhGAL4RBjnZUqefMI0NwqIl6nGbr1IpF0rZGoN8Q7x12Q== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-fastify@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.39.0.tgz#96a040e4944daf77c53a8fe5a128bc3b2568e4aa" + integrity sha512-SS9uSlKcsWZabhBp2szErkeuuBDgxOUlllwkS92dVaWRnMmwysPhcEgHKB8rUe3BHg/GnZC1eo1hbTZv4YhfoA== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-fs@0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.15.0.tgz#41658507860f39fee5209bca961cea8d24ca2a83" + integrity sha512-JWVKdNLpu1skqZQA//jKOcKdJC66TWKqa2FUFq70rKohvaSq47pmXlnabNO+B/BvLfmidfiaN35XakT5RyMl2Q== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + +"@opentelemetry/instrumentation-generic-pool@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.39.0.tgz#2b9af16ad82d5cbe67125c0125753cecd162a728" + integrity sha512-y4v8Y+tSfRB3NNBvHjbjrn7rX/7sdARG7FuK6zR8PGb28CTa0kHpEGCJqvL9L8xkTNvTXo+lM36ajFGUaK1aNw== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + +"@opentelemetry/instrumentation-graphql@0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.43.0.tgz#71bb94ea775c70dbd388c739b397ec1418f3f170" + integrity sha512-aI3YMmC2McGd8KW5du1a2gBA0iOMOGLqg4s9YjzwbjFwjlmMNFSK1P3AIg374GWg823RPUGfVTIgZ/juk9CVOA== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + +"@opentelemetry/instrumentation-hapi@0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.41.0.tgz#de8711907256d8fae1b5faf71fc825cef4a7ddbb" + integrity sha512-jKDrxPNXDByPlYcMdZjNPYCvw0SQJjN+B1A+QH+sx+sAHsKSAf9hwFiJSrI6C4XdOls43V/f/fkp9ITkHhKFbQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-http@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.53.0.tgz#0d806adf1b3aba036bc46e16162e3c0dbb8a6b60" + integrity sha512-H74ErMeDuZfj7KgYCTOFGWF5W9AfaPnqLQQxeFq85+D29wwV2yqHbz2IKLYpkOh7EI6QwDEl7rZCIxjJLyc/CQ== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/instrumentation" "0.53.0" + "@opentelemetry/semantic-conventions" "1.27.0" + semver "^7.5.2" + +"@opentelemetry/instrumentation-ioredis@0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.43.0.tgz#dbadabaeefc4cb47c406f878444f1bcac774fa89" + integrity sha512-i3Dke/LdhZbiUAEImmRG3i7Dimm/BD7t8pDDzwepSvIQ6s2X6FPia7561gw+64w+nx0+G9X14D7rEfaMEmmjig== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-kafkajs@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.3.0.tgz#6687bce4dac8b90ef8ccbf1b662d5d1e95a34414" + integrity sha512-UnkZueYK1ise8FXQeKlpBd7YYUtC7mM8J0wzUSccEfc/G8UqHQqAzIyYCUOUPUKp8GsjLnWOOK/3hJc4owb7Jg== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-koa@0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz#963fd192a1b5f6cbae5dabf4ec82e3105cbb23b1" + integrity sha512-lDAhSnmoTIN6ELKmLJBplXzT/Jqs5jGZehuG22EdSMaTwgjMpxMDI1YtlKEhiWPWkrz5LUsd0aOO0ZRc9vn3AQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-lru-memoizer@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.40.0.tgz#dc60d7fdfd2a0c681cb23e7ed4f314d1506ccdc0" + integrity sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + +"@opentelemetry/instrumentation-mongodb@0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.47.0.tgz#f8107d878281433905e717f223fb4c0f10356a7b" + integrity sha512-yqyXRx2SulEURjgOQyJzhCECSh5i1uM49NUaq9TqLd6fA7g26OahyJfsr9NE38HFqGRHpi4loyrnfYGdrsoVjQ== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/sdk-metrics" "^1.9.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-mongoose@0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.42.0.tgz#375afd21adfcd897a8f521c1ffd2d91e6a428705" + integrity sha512-AnWv+RaR86uG3qNEMwt3plKX1ueRM7AspfszJYVkvkehiicC3bHQA6vWdb6Zvy5HAE14RyFbu9+2hUUjR2NSyg== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-mysql2@0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.41.0.tgz#6377b6e2d2487fd88e1d79aa03658db6c8d51651" + integrity sha512-REQB0x+IzVTpoNgVmy5b+UnH1/mDByrneimP6sbDHkp1j8QOl1HyWOrBH/6YWR0nrbU3l825Em5PlybjT3232g== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/sql-common" "^0.40.1" + +"@opentelemetry/instrumentation-mysql@0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.41.0.tgz#2d50691ead5219774bd36d66c35d5b4681485dd7" + integrity sha512-jnvrV6BsQWyHS2qb2fkfbfSb1R/lmYwqEZITwufuRl37apTopswu9izc0b1CYRp/34tUG/4k/V39PND6eyiNvw== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@types/mysql" "2.15.26" + +"@opentelemetry/instrumentation-nestjs-core@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.40.0.tgz#2c0e6405b56caaec32747d55c57ff9a034668ea8" + integrity sha512-WF1hCUed07vKmf5BzEkL0wSPinqJgH7kGzOjjMAiTGacofNXjb/y4KQ8loj2sNsh5C/NN7s1zxQuCgbWbVTGKg== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-pg@0.44.0": + version "0.44.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.44.0.tgz#1e97a0aeb2dca068ee23ce75884a0a0063a7ce3f" + integrity sha512-oTWVyzKqXud1BYEGX1loo2o4k4vaU1elr3vPO8NZolrBtFvQ34nx4HgUaexUDuEog00qQt+MLR5gws/p+JXMLQ== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/sql-common" "^0.40.1" + "@types/pg" "8.6.1" + "@types/pg-pool" "2.0.6" + +"@opentelemetry/instrumentation-redis-4@0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.42.0.tgz#fc01104cfe884c7546385eaae03c57a47edd19d1" + integrity sha512-NaD+t2JNcOzX/Qa7kMy68JbmoVIV37fT/fJYzLKu2Wwd+0NCxt+K2OOsOakA8GVg8lSpFdbx4V/suzZZ2Pvdjg== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-undici@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.6.0.tgz#9436ee155c8dcb0b760b66947c0e0f347688a5ef" + integrity sha512-ABJBhm5OdhGmbh0S/fOTE4N69IZ00CsHC5ijMYfzbw3E5NwLgpQk5xsljaECrJ8wz1SfXbO03FiSuu5AyRAkvQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + +"@opentelemetry/instrumentation@0.53.0", "@opentelemetry/instrumentation@^0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz#e6369e4015eb5112468a4d45d38dcada7dad892d" + integrity sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A== + dependencies: + "@opentelemetry/api-logs" "0.53.0" + "@types/shimmer" "^1.2.0" + import-in-the-middle "^1.8.1" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + +"@opentelemetry/instrumentation@^0.49 || ^0.50 || ^0.51 || ^0.52.0": + version "0.52.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz#2e7e46a38bd7afbf03cf688c862b0b43418b7f48" + integrity sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw== + dependencies: + "@opentelemetry/api-logs" "0.52.1" + "@types/shimmer" "^1.0.2" + import-in-the-middle "^1.8.1" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + +"@opentelemetry/redis-common@^0.36.2": + version "0.36.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz#906ac8e4d804d4109f3ebd5c224ac988276fdc47" + integrity sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g== + +"@opentelemetry/resources@1.26.0", "@opentelemetry/resources@^1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.26.0.tgz#da4c7366018bd8add1f3aa9c91c6ac59fd503cef" + integrity sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/semantic-conventions" "1.27.0" + +"@opentelemetry/sdk-metrics@^1.9.1": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz#37bb0afb1d4447f50aab9cdd05db6f2d8b86103e" + integrity sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + +"@opentelemetry/sdk-trace-base@^1.22", "@opentelemetry/sdk-trace-base@^1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz#0c913bc6d2cfafd901de330e4540952269ae579c" + integrity sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + "@opentelemetry/semantic-conventions" "1.27.0" + +"@opentelemetry/semantic-conventions@1.27.0", "@opentelemetry/semantic-conventions@^1.27.0": + version "1.27.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz#1a857dcc95a5ab30122e04417148211e6f945e6c" + integrity sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg== + +"@opentelemetry/sql-common@^0.40.1": + version "0.40.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz#93fbc48d8017449f5b3c3274f2268a08af2b83b6" + integrity sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg== + dependencies: + "@opentelemetry/core" "^1.1.0" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -784,6 +1071,149 @@ resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== +"@prisma/instrumentation@5.19.1": + version "5.19.1" + resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-5.19.1.tgz#146319cf85f22b7a43296f0f40cfeac55516e66e" + integrity sha512-VLnzMQq7CWroL5AeaW0Py2huiNKeoMfCH3SUxstdzPrlWQi6UQ9UrfcbUkNHlVFqOMacqy8X/8YtE0kuKDpD9w== + dependencies: + "@opentelemetry/api" "^1.8" + "@opentelemetry/instrumentation" "^0.49 || ^0.50 || ^0.51 || ^0.52.0" + "@opentelemetry/sdk-trace-base" "^1.22" + +"@sentry/cli-darwin@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.37.0.tgz#9c890c68abf30ceaad27826212a0963b125b8bbf" + integrity sha512-CsusyMvO0eCPSN7H+sKHXS1pf637PWbS4rZak/7giz/z31/6qiXmeMlcL3f9lLZKtFPJmXVFO9uprn1wbBVF8A== + +"@sentry/cli-linux-arm64@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.37.0.tgz#2070155bade6d72d6b706807c6f365c65f9b82ea" + integrity sha512-2vzUWHLZ3Ct5gpcIlfd/2Qsha+y9M8LXvbZE26VxzYrIkRoLAWcnClBv8m4XsHLMURYvz3J9QSZHMZHSO7kAzw== + +"@sentry/cli-linux-arm@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.37.0.tgz#a08c2133e8e2566074fd6fe4f68e9ffd0c85664a" + integrity sha512-Dz0qH4Yt+gGUgoVsqVt72oDj4VQynRF1QB1/Sr8g76Vbi+WxWZmUh0iFwivYVwWxdQGu/OQrE0tx946HToCRyA== + +"@sentry/cli-linux-i686@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.37.0.tgz#53fff0e7f232b656b0ee3413b66006ee724a4abf" + integrity sha512-MHRLGs4t/CQE1pG+mZBQixyWL6xDZfNalCjO8GMcTTbZFm44S3XRHfYJZNVCgdtnUP7b6OHGcu1v3SWE10LcwQ== + +"@sentry/cli-linux-x64@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.37.0.tgz#2fbaf51ef3884bd6561c987f01ac98f544457150" + integrity sha512-k76ClefKZaDNJZU/H3mGeR8uAzAGPzDRG/A7grzKfBeyhP3JW09L7Nz9IQcSjCK+xr399qLhM2HFCaPWQ6dlMw== + +"@sentry/cli-win32-i686@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.37.0.tgz#fa195664da27ce8c40fdb6db1bf1d125cdf587d9" + integrity sha512-FFyi5RNYQQkEg4GkP2f3BJcgQn0F4fjFDMiWkjCkftNPXQG+HFUEtrGsWr6mnHPdFouwbYg3tEPUWNxAoypvTw== + +"@sentry/cli-win32-x64@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.37.0.tgz#84fa4d070b8a4a115c46ab38f42d29580143fd26" + integrity sha512-nSMj4OcfQmyL+Tu/jWCJwhKCXFsCZW1MUk6wjjQlRt9SDLfgeapaMlK1ZvT1eZv5ZH6bj3qJfefwj4U8160uOA== + +"@sentry/cli@^2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.37.0.tgz#dd01e933cf1caed7d7b6abab5a96044fe1c9c7a1" + integrity sha512-fM3V4gZRJR/s8lafc3O07hhOYRnvkySdPkvL/0e0XW0r+xRwqIAgQ5ECbsZO16A5weUiXVSf03ztDL1FcmbJCQ== + dependencies: + https-proxy-agent "^5.0.0" + node-fetch "^2.6.7" + progress "^2.0.3" + proxy-from-env "^1.1.0" + which "^2.0.2" + optionalDependencies: + "@sentry/cli-darwin" "2.37.0" + "@sentry/cli-linux-arm" "2.37.0" + "@sentry/cli-linux-arm64" "2.37.0" + "@sentry/cli-linux-i686" "2.37.0" + "@sentry/cli-linux-x64" "2.37.0" + "@sentry/cli-win32-i686" "2.37.0" + "@sentry/cli-win32-x64" "2.37.0" + +"@sentry/core@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.34.0.tgz#92efe1cc8ced843beee636c344e66086d8915563" + integrity sha512-adrXCTK/zsg5pJ67lgtZqdqHvyx6etMjQW3P82NgWdj83c8fb+zH+K79Z47pD4zQjX0ou2Ws5nwwi4wJbz4bfA== + dependencies: + "@sentry/types" "8.34.0" + "@sentry/utils" "8.34.0" + +"@sentry/nestjs@^8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry/nestjs/-/nestjs-8.34.0.tgz#5148b9322461ff7251c9ddca4f81b20c063dc96a" + integrity sha512-wJI/Vl1ZP0nCa+aQBhHFaFMKxGjx/7/sA7AidzhZwBon+om/i0CztCSJ8IjhC63axm6p75ivcX2slot36p9qlA== + dependencies: + "@sentry/core" "8.34.0" + "@sentry/node" "8.34.0" + "@sentry/types" "8.34.0" + "@sentry/utils" "8.34.0" + +"@sentry/node@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-8.34.0.tgz#7a2e4f719c8b0ec87c643b74f1084d5c8f362bd9" + integrity sha512-Q7BPp7Y8yCcwD620xoziWSOuPi/PCIdttkczvB0BGzBRYh2s702h+qNusRijRpVNZmzmYOo9m1x7Y1O/b8/v2A== + dependencies: + "@opentelemetry/api" "^1.9.0" + "@opentelemetry/context-async-hooks" "^1.25.1" + "@opentelemetry/core" "^1.25.1" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/instrumentation-amqplib" "^0.42.0" + "@opentelemetry/instrumentation-connect" "0.39.0" + "@opentelemetry/instrumentation-dataloader" "0.12.0" + "@opentelemetry/instrumentation-express" "0.42.0" + "@opentelemetry/instrumentation-fastify" "0.39.0" + "@opentelemetry/instrumentation-fs" "0.15.0" + "@opentelemetry/instrumentation-generic-pool" "0.39.0" + "@opentelemetry/instrumentation-graphql" "0.43.0" + "@opentelemetry/instrumentation-hapi" "0.41.0" + "@opentelemetry/instrumentation-http" "0.53.0" + "@opentelemetry/instrumentation-ioredis" "0.43.0" + "@opentelemetry/instrumentation-kafkajs" "0.3.0" + "@opentelemetry/instrumentation-koa" "0.43.0" + "@opentelemetry/instrumentation-lru-memoizer" "0.40.0" + "@opentelemetry/instrumentation-mongodb" "0.47.0" + "@opentelemetry/instrumentation-mongoose" "0.42.0" + "@opentelemetry/instrumentation-mysql" "0.41.0" + "@opentelemetry/instrumentation-mysql2" "0.41.0" + "@opentelemetry/instrumentation-nestjs-core" "0.40.0" + "@opentelemetry/instrumentation-pg" "0.44.0" + "@opentelemetry/instrumentation-redis-4" "0.42.0" + "@opentelemetry/instrumentation-undici" "0.6.0" + "@opentelemetry/resources" "^1.26.0" + "@opentelemetry/sdk-trace-base" "^1.26.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@prisma/instrumentation" "5.19.1" + "@sentry/core" "8.34.0" + "@sentry/opentelemetry" "8.34.0" + "@sentry/types" "8.34.0" + "@sentry/utils" "8.34.0" + import-in-the-middle "^1.11.0" + +"@sentry/opentelemetry@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry/opentelemetry/-/opentelemetry-8.34.0.tgz#2e4b264521610bf0fc9b5aaf908a49e07e93dc4d" + integrity sha512-WS91L+HVKGVIzOgt0szGp+24iKOs86BZsAHGt0HWnMR4kqWP6Ak+TLvqWDCxnuzniZMxdewDGA8p5hrBAPsmsA== + dependencies: + "@sentry/core" "8.34.0" + "@sentry/types" "8.34.0" + "@sentry/utils" "8.34.0" + +"@sentry/types@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.34.0.tgz#b02da72d1be67df5246aa9a97ca661ee71569372" + integrity sha512-zLRc60CzohGCo6zNsNeQ9JF3SiEeRE4aDCP9fDDdIVCOKovS+mn1rtSip0qd0Vp2fidOu0+2yY0ALCz1A3PJSQ== + +"@sentry/utils@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.34.0.tgz#5ba543381a9de0ada1196df1fc5cde3b891de41e" + integrity sha512-W1KoRlFUjprlh3t86DZPFxLfM6mzjRzshVfMY7vRlJFymBelJsnJ3A1lPeBZM9nCraOSiw6GtOWu6k5BAkiGIg== + dependencies: + "@sentry/types" "8.34.0" + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -871,6 +1301,13 @@ dependencies: "@types/node" "*" +"@types/connect@3.4.36": + version "3.4.36" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.36.tgz#e511558c15a39cb29bd5357eebb57bd1459cd1ab" + integrity sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w== + dependencies: + "@types/node" "*" + "@types/cookiejar@^2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" @@ -955,6 +1392,13 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/mysql@2.15.26": + version "2.15.26" + resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.26.tgz#f0de1484b9e2354d587e7d2bd17a873cc8300836" + integrity sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ== + dependencies: + "@types/node" "*" + "@types/node@*": version "22.7.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.3.tgz#7ddf1ddf13078692b4cfadb835852b2a718ee1ef" @@ -969,6 +1413,31 @@ dependencies: undici-types "~6.19.2" +"@types/pg-pool@2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.6.tgz#1376d9dc5aec4bb2ec67ce28d7e9858227403c77" + integrity sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ== + dependencies: + "@types/pg" "*" + +"@types/pg@*": + version "8.11.10" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.11.10.tgz#b8fb2b2b759d452fe3ec182beadd382563b63291" + integrity sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^4.0.1" + +"@types/pg@8.6.1": + version "8.6.1" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.1.tgz#099450b8dc977e8197a44f5229cedef95c8747f9" + integrity sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^2.2.0" + "@types/qs@*": version "6.9.16" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.16.tgz#52bba125a07c0482d26747d5d4947a64daf8f794" @@ -996,6 +1465,11 @@ "@types/node" "*" "@types/send" "*" +"@types/shimmer@^1.0.2", "@types/shimmer@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.2.0.tgz#9b706af96fa06416828842397a70dfbbf1c14ded" + integrity sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg== + "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -1278,6 +1752,13 @@ acorn@^8.11.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv-formats@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -1685,7 +2166,7 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -cjs-module-lexer@^1.0.0: +cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== @@ -1911,7 +2392,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -2678,6 +3159,14 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -2708,6 +3197,16 @@ import-fresh@^3.2.1, import-fresh@^3.3.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-in-the-middle@^1.11.0, import-in-the-middle@^1.8.1: + version "1.11.2" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.11.2.tgz#dd848e72b63ca6cd7c34df8b8d97fc9baee6174f" + integrity sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA== + dependencies: + acorn "^8.8.2" + acorn-import-attributes "^1.9.5" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + import-local@^3.0.2: version "3.2.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" @@ -3590,6 +4089,11 @@ mkdirp@^0.5.4: dependencies: minimist "^1.2.6" +module-details-from-path@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" + integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -3650,7 +4154,7 @@ node-emoji@1.11.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.1: +node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -3689,6 +4193,11 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== +obuf@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -3845,6 +4354,45 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-numeric@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" + integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== + +pg-protocol@*: + version "1.7.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93" + integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ== + +pg-types@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg-types@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.2.tgz#399209a57c326f162461faa870145bb0f918b76d" + integrity sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng== + dependencies: + pg-int8 "1.0.1" + pg-numeric "1.0.2" + postgres-array "~3.0.1" + postgres-bytea "~3.0.0" + postgres-date "~2.1.0" + postgres-interval "^3.0.0" + postgres-range "^1.1.1" + picocolors@^1.0.0, picocolors@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" @@ -3877,6 +4425,55 @@ pluralize@8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-array@~3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" + integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-bytea@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" + integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== + dependencies: + obuf "~1.1.2" + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-date@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.1.0.tgz#b85d3c1fb6fb3c6c8db1e9942a13a3bf625189d0" + integrity sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + +postgres-interval@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" + integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== + +postgres-range@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.4.tgz#a59c5f9520909bcec5e63e8cf913a92e4c952863" + integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -3908,6 +4505,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -3924,6 +4526,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -4022,6 +4629,15 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-in-the-middle@^7.1.1: + version "7.4.0" + resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz#606977820d4b5f9be75e5a108ce34cfed25b3bb4" + integrity sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ== + dependencies: + debug "^4.3.5" + module-details-from-path "^1.0.3" + resolve "^1.22.8" + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -4044,7 +4660,7 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.20.0: +resolve@^1.20.0, resolve@^1.22.8: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -4126,7 +4742,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: +semver@^7.3.4, semver@^7.3.5, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -4196,6 +4812,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shimmer@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + side-channel@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -4752,7 +5373,7 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -which@^2.0.1: +which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== From e0e71950a6498e37833420dad57d25788f8ed90a Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 17 Oct 2024 10:39:48 +0200 Subject: [PATCH 26/54] cleanup --- api-node/src/instrument.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/api-node/src/instrument.ts b/api-node/src/instrument.ts index 516807257..ab70a003e 100644 --- a/api-node/src/instrument.ts +++ b/api-node/src/instrument.ts @@ -8,5 +8,4 @@ Sentry.init({ release: packageJson.version, environment: process.env.NODE_ENV || 'development', tracesSampleRate: 1.0, - debug: true, }); From a01270a81851f9fd395c4635c10e9dd1e1058783 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 17 Oct 2024 10:51:24 +0200 Subject: [PATCH 27/54] don't upload source maps in CI --- .github/workflows/node-api.yml | 2 +- api-node/package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node-api.yml b/.github/workflows/node-api.yml index 0a7db478c..22b7c2a88 100644 --- a/.github/workflows/node-api.yml +++ b/.github/workflows/node-api.yml @@ -47,7 +47,7 @@ jobs: run: yarn install --frozen-lockfile - name: Build working-directory: ./api-node - run: yarn build + run: yarn build:ci lint: name: Lint diff --git a/api-node/package.json b/api-node/package.json index 1067bbd42..3ba59a782 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -7,6 +7,7 @@ "license": "UNLICENSED", "scripts": { "build": "nest build && yarn sentry:sourcemaps", + "build:ci": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", @@ -72,4 +73,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} \ No newline at end of file +} From 1f118ddc39c33f4c989aa40ba4bacf8d6e19e8e7 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 17 Oct 2024 11:30:30 +0200 Subject: [PATCH 28/54] add caching --- api-node/package.json | 2 ++ api-node/src/app.module.ts | 14 ++++++++++++-- api-node/yarn.lock | 32 +++++++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/api-node/package.json b/api-node/package.json index 3ba59a782..950f8f3e2 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -25,11 +25,13 @@ "sentry:sourcemaps": "sentry-cli sourcemaps inject --org sentry --project release-registry-nestjs ./dist && sentry-cli sourcemaps upload --org sentry --project release-registry-nestjs ./dist" }, "dependencies": { + "@nestjs/cache-manager": "^2.2.2", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@sentry/cli": "^2.37.0", "@sentry/nestjs": "^8.34.0", + "cache-manager": "^5.7.6", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1" }, diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index 9324c1140..3520fb210 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -7,9 +7,15 @@ import { SdksController } from './sdks/sdks.controller'; import { AwsLambdaLayersController } from './aws-lambda-layers/aws-lambda-layers.controller'; import { RegistryService } from './common/registry.service'; import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; -import { APP_FILTER } from '@nestjs/core'; +import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; +import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager'; @Module({ - imports: [SentryModule.forRoot()], + imports: [ + SentryModule.forRoot(), + // max and ttl taken from apiserver.py cache config + // ttl of cache-manager@5 is in milliseconds + CacheModule.register({ max: 200, ttl: 3600 * 1000 }), + ], controllers: [ HealthCheckController, PackagesController, @@ -21,6 +27,10 @@ import { APP_FILTER } from '@nestjs/core'; providers: [ RegistryService, { provide: APP_FILTER, useClass: SentryGlobalFilter }, + { + provide: APP_INTERCEPTOR, + useClass: CacheInterceptor, + }, ], }) export class AppModule {} diff --git a/api-node/yarn.lock b/api-node/yarn.lock index 374677cac..357e9d413 100644 --- a/api-node/yarn.lock +++ b/api-node/yarn.lock @@ -669,6 +669,11 @@ resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== +"@nestjs/cache-manager@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@nestjs/cache-manager/-/cache-manager-2.2.2.tgz#4b0e7c4112c7b8c2a869d64f998aaf8a1bf0040d" + integrity sha512-+n7rpU1QABeW2WV17Dl1vZCG3vWjJU1MaamWgZvbGxYE9EeCM0lVLfw3z7acgDTNwOy+K68xuQPoIMxD0bhjlA== + "@nestjs/cli@^10.0.0": version "10.4.5" resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-10.4.5.tgz#d6563b87e8ca1d0f256c19a7847dbcc96c76a88e" @@ -2078,6 +2083,16 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cache-manager@^5.7.6: + version "5.7.6" + resolved "https://registry.yarnpkg.com/cache-manager/-/cache-manager-5.7.6.tgz#bdd8a154c73e5233824aa09ceb359ed225d37b7e" + integrity sha512-wBxnBHjDxF1RXpHCBD6HGvKER003Ts7IIm0CHpggliHzN1RZditb7rXoduE1rplc2DEFYKxhLKgFuchXMJje9w== + dependencies: + eventemitter3 "^5.0.1" + lodash.clonedeep "^4.5.0" + lru-cache "^10.2.2" + promise-coalesce "^1.1.2" + call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -2705,6 +2720,11 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3923,6 +3943,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -3946,7 +3971,7 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -lru-cache@^10.2.0: +lru-cache@^10.2.0, lru-cache@^10.2.2: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== @@ -4510,6 +4535,11 @@ progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-coalesce@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/promise-coalesce/-/promise-coalesce-1.1.2.tgz#5d3bc4d0b2cf2e41e9df7cbeb6519b2a09459e3d" + integrity sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" From 65e9d4bcd3c9c3e059918c25403b190ec83d694c Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 17 Oct 2024 11:35:07 +0200 Subject: [PATCH 29/54] make caching enabled depend on set env var --- api-node/src/app.module.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index 3520fb210..16402f5b0 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { Module, Provider } from '@nestjs/common'; import { PackagesController } from './packages/packages.controller'; import { HealthCheckController } from './health/healthCheck.controller'; import { MarketingController } from './marketing/marketing.controller'; @@ -9,6 +9,19 @@ import { RegistryService } from './common/registry.service'; import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager'; + +const providers: Provider[] = [ + RegistryService, + { provide: APP_FILTER, useClass: SentryGlobalFilter }, +]; + +if (process.env.REGISTRY_ENABLE_CACHE === '1') { + providers.push({ + provide: APP_INTERCEPTOR, + useClass: CacheInterceptor, + }); +} + @Module({ imports: [ SentryModule.forRoot(), @@ -24,13 +37,6 @@ import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager'; SdksController, AwsLambdaLayersController, ], - providers: [ - RegistryService, - { provide: APP_FILTER, useClass: SentryGlobalFilter }, - { - provide: APP_INTERCEPTOR, - useClass: CacheInterceptor, - }, - ], + providers, }) export class AppModule {} From d6c3f1d5157bdfcb6ffdc8c5e32d320c81f88d44 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 18 Oct 2024 15:07:08 +0200 Subject: [PATCH 30/54] wip test against 20k URLs --- api-node/.gitignore | 2 + api-node/package.json | 7 +- api-node/src/common/registry.service.ts | 37 ++-- api-node/src/common/utils.ts | 3 + api-node/src/instrument.ts | 1 + api-node/src/main.ts | 4 +- api-node/src/packages/packages.controller.ts | 27 ++- api-node/test/everything.e2e-spec.ts | 57 ++++++ api-node/test/marketing.e2e-spec.ts | 2 +- api-node/test/packages.e2e-spec.ts | 85 +++++--- api-node/test/utils.ts | 3 + api-node/test/utils/getUrls.ts | 198 +++++++++++++++++++ api-server/apiserver.py | 2 + 13 files changed, 372 insertions(+), 56 deletions(-) create mode 100644 api-node/src/common/utils.ts create mode 100644 api-node/test/everything.e2e-spec.ts create mode 100644 api-node/test/utils/getUrls.ts diff --git a/api-node/.gitignore b/api-node/.gitignore index 4b56acfbe..ecddcbb6d 100644 --- a/api-node/.gitignore +++ b/api-node/.gitignore @@ -54,3 +54,5 @@ pids # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +.urls.json diff --git a/api-node/package.json b/api-node/package.json index 950f8f3e2..9e6e34bdb 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -18,7 +18,9 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json", + "test:e2e": "jest --config ./test/jest-e2e.json --testPathIgnorePatterns=everything.e2e-spec.ts", + "test:e2e:urls": "NODE_ENV=test jest --config ./test/jest-e2e.json everything.e2e-spec.ts", + "test:e2e:urls:generate": "ts-node ./test/utils/getUrls.ts", "python-api:build": "docker build -t registry-api-server ../", "python-api:start": "docker run -d -p 5031:5030 registry-api-server", "python-api:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-server)", @@ -33,7 +35,8 @@ "@sentry/nestjs": "^8.34.0", "cache-manager": "^5.7.6", "reflect-metadata": "^0.2.0", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "semver": "^7.6.3" }, "devDependencies": { "@nestjs/cli": "^10.0.0", diff --git a/api-node/src/common/registry.service.ts b/api-node/src/common/registry.service.ts index a72b6eef2..88309b2ac 100644 --- a/api-node/src/common/registry.service.ts +++ b/api-node/src/common/registry.service.ts @@ -8,12 +8,9 @@ import type { MarketingSlugs, } from '../marketing/types'; import type { AppEntry, Apps } from '../apps/types'; -import type { - PackageEntry, - Packages, - PackageVersions, -} from '../packages/types'; +import type { PackageEntry, Packages } from '../packages/types'; import type { AwsLambdaLayers } from '../aws-lambda-layers/types'; +import * as semver from 'semver'; const SDKS_PATH = path.join('..', 'sdks'); const APPS_PATH = path.join('..', 'apps'); @@ -79,13 +76,13 @@ export class RegistryService { getSdkVersions(sdkId: string): SdkVersions { const latest = this.getSdk(sdkId); - const { versions } = this.getPackageVersions(latest.canonical); + const versions = this.getPackageVersions(latest.canonical); return { latest, versions }; } // Packages - getPackages(): Packages { + getPackages(strict: boolean = false): Packages { return this.#packages.reduce((acc, canonical) => { const packageDir = getPackageDirFromCanonical(canonical); const latestFilePath = path.join(packageDir, 'latest.json'); @@ -105,7 +102,7 @@ export class RegistryService { }, {}); } - getPackageVersions(packageName: string): PackageVersions { + getPackageVersions(packageName: string): string[] | null { const packageDir = getPackageDirFromCanonical(packageName); try { const versions = fs @@ -118,27 +115,31 @@ export class RegistryService { return versionFile.version; }); - const dedupedVersions = Array.from(new Set(versions)); - - const latest = JSON.parse( - fs.readFileSync(path.join(packageDir, 'latest.json')).toString(), - ); - - return { versions: dedupedVersions, latest }; + // dedupe and sort versions + return Array.from(new Set(versions)).sort((a, b) => { + return semver.parse(a).compare(semver.parse(b)); + }); } catch (e) { console.error(`Failed to read package versions: ${packageName}`); console.error(e); + return []; } } - getPackage(packageName: string, version: string = 'latest'): PackageEntry { + getPackage( + packageName: string, + version: string = 'latest', + ): PackageEntry | null { try { const packageDir = getPackageDirFromCanonical(packageName); const versionFilePath = path.join(packageDir, `${version}.json`); return JSON.parse(fs.readFileSync(versionFilePath).toString()); } catch (e) { - console.error(`Failed to read package by version: ${packageName}`); + console.error( + `Failed to read package ${packageName} for version ${version}`, + ); console.error(e); + return null; } } @@ -177,7 +178,7 @@ export class RegistryService { // Marketing getMarketingSlugs(): MarketingSlugs { - return { slugs: Object.keys(this.#slugs) }; + return { slugs: Object.keys(this.#slugs).sort() }; } resolveMarketingSlug(slug: string): ResolvedMarketingSlug | null { diff --git a/api-node/src/common/utils.ts b/api-node/src/common/utils.ts new file mode 100644 index 000000000..41e5e1f31 --- /dev/null +++ b/api-node/src/common/utils.ts @@ -0,0 +1,3 @@ +export function isTruthy(value: string): boolean { + return value.toLowerCase() === 'true' || value === '1' || value === 'yes'; +} diff --git a/api-node/src/instrument.ts b/api-node/src/instrument.ts index ab70a003e..3b1acf203 100644 --- a/api-node/src/instrument.ts +++ b/api-node/src/instrument.ts @@ -8,4 +8,5 @@ Sentry.init({ release: packageJson.version, environment: process.env.NODE_ENV || 'development', tracesSampleRate: 1.0, + enabled: process.env.NODE_ENV !== 'test', }); diff --git a/api-node/src/main.ts b/api-node/src/main.ts index 508b7be54..63ab90f21 100644 --- a/api-node/src/main.ts +++ b/api-node/src/main.ts @@ -4,7 +4,9 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap(): Promise { - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule, { + logger: ['debug', 'error', 'fatal', 'verbose', 'log', 'warn'], + }); await app.listen(3000); } diff --git a/api-node/src/packages/packages.controller.ts b/api-node/src/packages/packages.controller.ts index 176e75dd1..94a777e57 100644 --- a/api-node/src/packages/packages.controller.ts +++ b/api-node/src/packages/packages.controller.ts @@ -1,4 +1,10 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { + Controller, + Get, + NotFoundException, + Param, + Query, +} from '@nestjs/common'; import { RegistryService } from '../common/registry.service'; import { PackageEntry, Packages, PackageVersions } from './types'; @@ -7,20 +13,29 @@ export class PackagesController { constructor(private registryService: RegistryService) {} @Get() - getPackages(): Packages { - return this.registryService.getPackages(); + getPackages(@Query('strict') strict: boolean = false): Packages { + return this.registryService.getPackages(strict); } @Get('/:package(*)/versions') getPackageVersions(@Param('package') pgkName: string): PackageVersions { - return this.registryService.getPackageVersions(pgkName); + const latest = this.registryService.getPackage(pgkName); + if (!latest) { + throw new NotFoundException(); + } + const versions = this.registryService.getPackageVersions(pgkName); + return { versions, latest }; } @Get('/:package(*)/:version') getPackageByVersion( @Param('package') pkgName: string, @Param('version') version: string, - ): PackageEntry { - return this.registryService.getPackage(pkgName, version); + ): PackageEntry | null { + const pkg = this.registryService.getPackage(pkgName, version); + if (!pkg) { + throw new NotFoundException(); + } + return pkg; } } diff --git a/api-node/test/everything.e2e-spec.ts b/api-node/test/everything.e2e-spec.ts new file mode 100644 index 000000000..4ef124585 --- /dev/null +++ b/api-node/test/everything.e2e-spec.ts @@ -0,0 +1,57 @@ +import { PYTHON_API_URL } from './utils'; +import * as fs from 'fs'; +describe('python and Node api responses match', () => { + const urls = JSON.parse( + fs.readFileSync(`${__dirname}/utils/.urls.json`, 'utf8'), + ); + + it.each(urls)('%s', async ({ url, status }) => { + let pythonResponse, nodeResponse, pythonResponseStatus, nodeResponseStatus; + let pythonResponseBody, nodeResponseBody; + let attempts = 0; + while (attempts < 3) { + try { + pythonResponse = await fetch(`${PYTHON_API_URL}${url}`, { + redirect: 'manual', + }); + nodeResponse = await fetch(`http://localhost:3000${url}`, { + redirect: 'manual', + }); + if (pythonResponse?.status < 400 && nodeResponse?.status < 400) { + pythonResponseStatus = pythonResponse?.status; + nodeResponseStatus = nodeResponse?.status; + pythonResponseBody = await pythonResponse?.text(); + nodeResponseBody = await nodeResponse?.text(); + break; + } + } catch (error) { + console.error(`Attempt ${attempts + 1} failed:`, error); + } + attempts++; + if (attempts < 3) { + pythonResponseStatus = pythonResponse?.status; + nodeResponseStatus = nodeResponse?.status; + pythonResponseBody = await pythonResponse?.text(); + nodeResponseBody = await nodeResponse?.text(); + await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying + } + } + + if (attempts === 3) { + console.error(`Failed to fetch ${url} after 3 attempts`); + } + + expect(pythonResponseStatus).toBe(status); + expect(nodeResponseStatus).toBe(pythonResponseStatus); + + if (pythonResponseStatus === 200) { + try { + const pythonResponseJson = JSON.parse(pythonResponseBody); + const nodeResponseJson = JSON.parse(nodeResponseBody); + expect(nodeResponseJson).toEqual(pythonResponseJson); + } catch { + expect(nodeResponseBody).toEqual(pythonResponseBody); + } + } + }); +}); diff --git a/api-node/test/marketing.e2e-spec.ts b/api-node/test/marketing.e2e-spec.ts index 4ab90783e..f78a7ed59 100644 --- a/api-node/test/marketing.e2e-spec.ts +++ b/api-node/test/marketing.e2e-spec.ts @@ -24,7 +24,7 @@ describe('MarketingController (e2e)', () => { .get('/marketing-slugs') .expect((res) => { expect(res.status).toBe(200); - expect(res.body.slugs.sort()).toEqual(pythonApiData.slugs.sort()); + expect(res.body.slugs).toEqual(pythonApiData.slugs); }); }); diff --git a/api-node/test/packages.e2e-spec.ts b/api-node/test/packages.e2e-spec.ts index a37e35ff9..a99970923 100644 --- a/api-node/test/packages.e2e-spec.ts +++ b/api-node/test/packages.e2e-spec.ts @@ -29,39 +29,68 @@ describe('PackagesController (e2e)', () => { }); }); - it('/packages/:packageName/versions (GET)', async () => { - const packageName = 'npm:@sentry/angular'; + describe('/packages/:packageName/versions (GET)', () => { + it('returns versions for existing package', async () => { + const packageName = 'npm:@sentry/angular'; - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/packages/${packageName}/versions`, - ); - const pythonApiData = await pythonApiResponse.json(); + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/packages/${packageName}/versions`, + ); + const pythonApiData = await pythonApiResponse.json(); - return request(app.getHttpServer()) - .get(`/packages/${packageName}/versions`) - .expect((r) => { - expect(r.status).toEqual(200); - const { versions, latest } = r.body; - expect(versions.length).toEqual(pythonApiData.versions.length); - expect(versions.sort()).toEqual(pythonApiData.versions.sort()); - expect(latest).toEqual(pythonApiData.latest); - }); + return request(app.getHttpServer()) + .get(`/packages/${packageName}/versions`) + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); + + it('returns 404 for non-existing package', async () => { + const nonExistingPackage = 'npm:@sentry/non-existing-package'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/packages/${nonExistingPackage}/versions`, + ); + expect(pythonApiResponse.status).toEqual(404); + + return request(app.getHttpServer()) + .get(`/packages/${nonExistingPackage}/versions`) + .expect(404); + }); }); - it('/packages/:packageName/:version (GET)', async () => { - const packageName = 'npm:@sentry/angular'; - const version = '8.0.0'; + describe('/packages/:packageName/:version (GET)', () => { + it('returns package info for existing package', async () => { + const packageName = 'npm:@sentry/angular'; + const version = '8.0.0'; - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/packages/${packageName}/${version}`, - ); - const pythonApiData = await pythonApiResponse.json(); + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/packages/${packageName}/${version}`, + ); + const pythonApiData = await pythonApiResponse.json(); - return request(app.getHttpServer()) - .get(`/packages/${packageName}/${version}`) - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + return request(app.getHttpServer()) + .get(`/packages/${packageName}/${version}`) + .expect((r) => { + expect(r.status).toEqual(200); + expect(r.body).toEqual(pythonApiData); + }); + }); + + it('returns 404 for non-existent package', async () => { + const nonExistentPackage = 'npm:@sentry/non-existent-package'; + const version = 'latest'; + + const pythonApiResponse = await fetch( + `${PYTHON_API_URL}/packages/${nonExistentPackage}/${version}`, + ); + + expect(pythonApiResponse.status).toEqual(404); + + return request(app.getHttpServer()) + .get(`/packages/${nonExistentPackage}/${version}`) + .expect(404); + }); }); }); diff --git a/api-node/test/utils.ts b/api-node/test/utils.ts index e8eab428a..3c9302694 100644 --- a/api-node/test/utils.ts +++ b/api-node/test/utils.ts @@ -1,2 +1,5 @@ const PYTHON_API_PORT = 5031; export const PYTHON_API_URL = `http://localhost:${PYTHON_API_PORT}`; + +import * as fs from 'fs'; +import * as path from 'path'; diff --git a/api-node/test/utils/getUrls.ts b/api-node/test/utils/getUrls.ts new file mode 100644 index 000000000..5df08cc08 --- /dev/null +++ b/api-node/test/utils/getUrls.ts @@ -0,0 +1,198 @@ +import { PYTHON_API_URL } from '../utils'; +import * as fs from 'fs'; +import * as path from 'path'; + +type URLsList = { + url: string; + status: number; +}[]; + +export async function getAllYesAllUrls(): Promise { + const urls: URLsList = [ + { url: '/packages', status: 200 }, + { url: '/sdks', status: 200 }, + { url: '/apps', status: 200 }, + { url: '/marketing-slugs', status: 200 }, + { url: '/aws-lambda-layers', status: 200 }, + { url: '/healthz', status: 200 }, + ]; + + // ------- packages ------- + + const packages = await fetch(`${PYTHON_API_URL}/packages`); + const packagesData = await packages.json(); + const canonicalPackageNames = Object.entries(packagesData).map( + // @ts-expect-error packagesData is not typed + (e) => e[1].canonical, + ); + + urls.push( + ...canonicalPackageNames.map((p) => ({ + url: `/packages/${p}/latest`, + status: 200, + })), + ); + + let errors = 0; + for (const packageName in packagesData) { + const versionsUrl = `/packages/${packageName}/versions`; + const versionsResponse = await fetch(`${PYTHON_API_URL}/${versionsUrl}`); + + if (versionsResponse.status !== 200) { + urls.push({ + url: versionsUrl, + status: versionsResponse.status, + }); + } else { + try { + const versionsData = await versionsResponse.json(); + + const allVersions: string[] = versionsData.versions; + + urls.push( + ...allVersions.map((v) => ({ + url: `/packages/${packageName}/${v}`, + status: 200, + })), + ); + } catch (e) { + console.debug({ versionsUrl }); + console.error(e); + ++errors; + } + } + } + + // ------- sdks ------- + + const sdks = await fetch(`${PYTHON_API_URL}/sdks`); + const sdksData = await sdks.json(); + const canonicalSdkNames = Object.keys(sdksData); + + urls.push( + ...canonicalSdkNames.map((s) => ({ + url: `/sdks/${s}/latest`, + status: 200, + })), + ); + + for (const sdkName in sdksData) { + const versionsUrl = `/sdks/${sdkName}/versions`; + const versionsResponse = await fetch(`${PYTHON_API_URL}${versionsUrl}`); + + if (versionsResponse.status !== 200) { + urls.push({ + url: versionsUrl, + status: versionsResponse.status, + }); + } else { + urls.push({ + url: versionsUrl, + status: 200, + }); + try { + const versionsData = await versionsResponse.json(); + + const allVersions: string[] = versionsData.versions; + + urls.push( + ...allVersions.map((v) => ({ + url: `/sdks/${sdkName}/${v}`, + status: 200, + })), + ); + } catch (e) { + console.debug({ versionsUrl }); + console.error(e); + ++errors; + } + } + } + + // ------- apps ------- + + const apps = await fetch(`${PYTHON_API_URL}/apps`); + const appsData = await apps.json(); + const appIds = Object.keys(appsData); + + urls.push( + ...appIds.map((a) => ({ + url: `/apps/${a}/latest`, + status: 200, + })), + ); + urls.push( + ...appIds.map((a) => ({ + url: `/apps/${a}/${appsData[a].version}`, + status: 200, + })), + ); + + for (const appEntry of Object.entries(appsData)) { + const appId = appEntry[0]; + // @ts-expect-error appsData is not typed + const fileUrls = appEntry[1].file_urls; + + for (const fileUrl of Object.entries(fileUrls)) { + const parts = fileUrl[0].split('/').at(-1).replace('.exe', '').split('-'); + const platform = parts.at(-2); + const arch = parts.at(-1); + const pkgName = parts.slice(0, -2).join('-'); + if (!pkgName) { + continue; + } + const downloadUrl = `/apps/${appId}/latest?response=download&platform=${platform}&arch=${arch}&package=${pkgName}`; + const url = new URL(downloadUrl, PYTHON_API_URL); + const downloadResponse = await fetch(url, { + redirect: 'manual', + }); + urls.push({ + url: downloadUrl, + status: downloadResponse.status, + }); + } + } + + // ------- marketing slugs ------- + + const marketingSlugs = await fetch(`${PYTHON_API_URL}/marketing-slugs`); + const marketingSlugsData = await marketingSlugs.json(); + const marketingSlugIds = marketingSlugsData.slugs; + + urls.push( + ...marketingSlugIds + .filter((s) => s !== 'createdAt') + .map((s) => ({ + url: `/marketing-slugs/${s}`, + status: 200, + })), + ); + + // ------- summary ------- + + const numberOfUrlsByStatus = urls.reduce( + (acc, url) => { + acc[url.status] = (acc[url.status] || 0) + 1; + return acc; + }, + {} as Record, + ); + + console.table([ + { + ...numberOfUrlsByStatus, + 'URL scraping errors': errors, + }, + ]); + fs.writeFileSync( + path.join(__dirname, '.urls.json'), + JSON.stringify(urls, null, 2), + { + flag: 'w', + }, + ); + + return urls; +} + +getAllYesAllUrls(); diff --git a/api-server/apiserver.py b/api-server/apiserver.py index 7580a6ea9..0e5874167 100644 --- a/api-server/apiserver.py +++ b/api-server/apiserver.py @@ -140,6 +140,8 @@ def get_package_versions(self, canonical): if ":" not in canonical: return registry, package = canonical.split(":", 1) + # Allow ":" to be used as a path separator + package = package.replace(":", "/") rv = set() for filename in os.listdir(self._path("packages", registry, package)): if filename.endswith(".json"): From c216ccac72f52545a755124629f65969d3ffab92 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 23 Oct 2024 14:00:03 +0200 Subject: [PATCH 31/54] add cors --- api-node/src/main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api-node/src/main.ts b/api-node/src/main.ts index 63ab90f21..50bf1f76c 100644 --- a/api-node/src/main.ts +++ b/api-node/src/main.ts @@ -7,6 +7,7 @@ async function bootstrap(): Promise { const app = await NestFactory.create(AppModule, { logger: ['debug', 'error', 'fatal', 'verbose', 'log', 'warn'], }); + app.enableCors({ origin: '*' }); await app.listen(3000); } From 2630082d74d43e466b47f91bd3a88d5a7cddfd53 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 23 Oct 2024 14:01:45 +0200 Subject: [PATCH 32/54] custom cache interceptor --- api-node/src/common/cache.ts | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 api-node/src/common/cache.ts diff --git a/api-node/src/common/cache.ts b/api-node/src/common/cache.ts new file mode 100644 index 000000000..5e859f310 --- /dev/null +++ b/api-node/src/common/cache.ts @@ -0,0 +1,52 @@ +import { CacheInterceptor } from '@nestjs/cache-manager'; +import { ExecutionContext, CallHandler } from '@nestjs/common'; +import { Observable, tap } from 'rxjs'; + +// values taken from apiserver.py cache config +export const CACHE_DEFAULT_SETTINGS = { + max: 200, + // ttl of cache-manager@5 is in milliseconds + ttl: 3600 * 1000, +}; + +/** + * Custom interceptor to + * - disable caching if the REGISTRY_ENABLE_CACHE environment variable is not set to '1'. + * - set the X-From-Cache header to 1 if the response is served from cache (hit). + */ +export class ReleaseRegistryCacheInterceptor extends CacheInterceptor { + public async intercept( + context: ExecutionContext, + next: CallHandler, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): Promise> { + if (!isCachingEnabled()) { + return next.handle(); + } + + const isCacheHit = !!(await this.cacheManager.get(this.trackBy(context))); + + const cachedResponse = await super.intercept(context, next); + + return cachedResponse.pipe( + tap((response) => { + if (response && isCacheHit) { + const httpAdapter = this.httpAdapterHost.httpAdapter; + httpAdapter.setHeader( + context.switchToHttp().getResponse(), + 'X-From-Cache', + '1', + ); + } + }), + ); + } +} + +function isCachingEnabled(): boolean { + const enabledCacheEnvVar = process.env.REGISTRY_ENABLE_CACHE; + if (enabledCacheEnvVar) { + return enabledCacheEnvVar === '1'; + } + return process.env.FLASK_ENV === 'production'; +} From b1ea4474b5773179e917a7a1f5b68160dd57d019 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 23 Oct 2024 14:03:05 +0200 Subject: [PATCH 33/54] 20k url tests, pre-test refactor, --- api-node/src/app.module.ts | 16 ++----- api-node/src/apps/apps.controller.ts | 13 +++++- .../aws-lambda-layers.controller.ts | 4 +- api-node/src/health/healthCheck.controller.ts | 4 +- api-node/src/main.ts | 2 +- .../src/marketing/marketing.controller.ts | 10 ++++- api-node/src/packages/packages.controller.ts | 3 ++ api-node/src/sdks/sdks.controller.ts | 4 +- api-node/test/apps.e2e-spec.ts | 6 ++- api-node/test/everything.e2e-spec.ts | 45 +++++++++++-------- api-node/test/utils/getUrls.ts | 1 - 11 files changed, 70 insertions(+), 38 deletions(-) diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index 16402f5b0..d31328b19 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -7,27 +7,19 @@ import { SdksController } from './sdks/sdks.controller'; import { AwsLambdaLayersController } from './aws-lambda-layers/aws-lambda-layers.controller'; import { RegistryService } from './common/registry.service'; import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; -import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; -import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager'; +import { APP_FILTER } from '@nestjs/core'; +import { CacheModule } from '@nestjs/cache-manager'; +import { CACHE_DEFAULT_SETTINGS } from './common/cache'; const providers: Provider[] = [ RegistryService, { provide: APP_FILTER, useClass: SentryGlobalFilter }, ]; -if (process.env.REGISTRY_ENABLE_CACHE === '1') { - providers.push({ - provide: APP_INTERCEPTOR, - useClass: CacheInterceptor, - }); -} - @Module({ imports: [ SentryModule.forRoot(), - // max and ttl taken from apiserver.py cache config - // ttl of cache-manager@5 is in milliseconds - CacheModule.register({ max: 200, ttl: 3600 * 1000 }), + CacheModule.register(CACHE_DEFAULT_SETTINGS), ], controllers: [ HealthCheckController, diff --git a/api-node/src/apps/apps.controller.ts b/api-node/src/apps/apps.controller.ts index c2fd2b199..aacb7931b 100644 --- a/api-node/src/apps/apps.controller.ts +++ b/api-node/src/apps/apps.controller.ts @@ -1,15 +1,26 @@ -import { Controller, Get, Param, Query, Res } from '@nestjs/common'; +import { + Controller, + Get, + Param, + Query, + Res, + UseInterceptors, +} from '@nestjs/common'; import type { Response } from 'express'; import { RegistryService } from '../common/registry.service'; import { findDownloadUrl, getUrlChecksums, makeDigest } from './utils'; import type { Apps } from './types'; +import { ReleaseRegistryCacheInterceptor } from '../common/cache'; @Controller('apps') export class AppsController { constructor(private readonly registryService: RegistryService) {} @Get() + // Registering interceptor on method level b/c the other endpoint uses @Res which is incompatible with the + // cache interceptor :( + @UseInterceptors(ReleaseRegistryCacheInterceptor) getApps(): Apps { return this.registryService.getApps(); } diff --git a/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts b/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts index ae8ae2227..8c9ad2295 100644 --- a/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts +++ b/api-node/src/aws-lambda-layers/aws-lambda-layers.controller.ts @@ -1,8 +1,10 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, UseInterceptors } from '@nestjs/common'; import { RegistryService } from '../common/registry.service'; import { AwsLambdaLayers } from './types'; +import { ReleaseRegistryCacheInterceptor } from '../common/cache'; @Controller('aws-lambda-layers') +@UseInterceptors(ReleaseRegistryCacheInterceptor) export class AwsLambdaLayersController { constructor(private readonly registryService: RegistryService) {} diff --git a/api-node/src/health/healthCheck.controller.ts b/api-node/src/health/healthCheck.controller.ts index 938dd3348..26ce46525 100644 --- a/api-node/src/health/healthCheck.controller.ts +++ b/api-node/src/health/healthCheck.controller.ts @@ -1,6 +1,8 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, UseInterceptors } from '@nestjs/common'; +import { ReleaseRegistryCacheInterceptor } from '../common/cache'; @Controller('/healthz') +@UseInterceptors(ReleaseRegistryCacheInterceptor) export class HealthCheckController { constructor() {} diff --git a/api-node/src/main.ts b/api-node/src/main.ts index 50bf1f76c..f2b793964 100644 --- a/api-node/src/main.ts +++ b/api-node/src/main.ts @@ -7,7 +7,7 @@ async function bootstrap(): Promise { const app = await NestFactory.create(AppModule, { logger: ['debug', 'error', 'fatal', 'verbose', 'log', 'warn'], }); - app.enableCors({ origin: '*' }); + app.enableCors({ origin: '*', allowedHeaders: ['*'] }); await app.listen(3000); } diff --git a/api-node/src/marketing/marketing.controller.ts b/api-node/src/marketing/marketing.controller.ts index 20bec3ba8..d33ef8459 100644 --- a/api-node/src/marketing/marketing.controller.ts +++ b/api-node/src/marketing/marketing.controller.ts @@ -1,8 +1,16 @@ -import { Controller, Get, Param, NotFoundException } from '@nestjs/common'; +import { + Controller, + Get, + Param, + NotFoundException, + UseInterceptors, +} from '@nestjs/common'; import { MarketingSlugs, ResolvedMarketingSlug } from './types'; import { RegistryService } from '../common/registry.service'; +import { ReleaseRegistryCacheInterceptor } from '../common/cache'; @Controller('marketing-slugs') +@UseInterceptors(ReleaseRegistryCacheInterceptor) export class MarketingController { constructor(private readonly registryService: RegistryService) {} diff --git a/api-node/src/packages/packages.controller.ts b/api-node/src/packages/packages.controller.ts index 94a777e57..0a94d025d 100644 --- a/api-node/src/packages/packages.controller.ts +++ b/api-node/src/packages/packages.controller.ts @@ -4,11 +4,14 @@ import { NotFoundException, Param, Query, + UseInterceptors, } from '@nestjs/common'; import { RegistryService } from '../common/registry.service'; import { PackageEntry, Packages, PackageVersions } from './types'; +import { ReleaseRegistryCacheInterceptor } from '../common/cache'; @Controller('packages') +@UseInterceptors(ReleaseRegistryCacheInterceptor) export class PackagesController { constructor(private registryService: RegistryService) {} diff --git a/api-node/src/sdks/sdks.controller.ts b/api-node/src/sdks/sdks.controller.ts index a2e624a07..bc4b62842 100644 --- a/api-node/src/sdks/sdks.controller.ts +++ b/api-node/src/sdks/sdks.controller.ts @@ -1,8 +1,10 @@ -import { Controller, Get, Param, Query } from '@nestjs/common'; +import { Controller, Get, Param, Query, UseInterceptors } from '@nestjs/common'; import { SdkEntry, Sdks, SdkVersions } from './types'; import { RegistryService } from '../common/registry.service'; +import { ReleaseRegistryCacheInterceptor } from '../common/cache'; @Controller('sdks') +@UseInterceptors(ReleaseRegistryCacheInterceptor) export class SdksController { constructor(private registryService: RegistryService) {} diff --git a/api-node/test/apps.e2e-spec.ts b/api-node/test/apps.e2e-spec.ts index 24d125f68..29b487ba4 100644 --- a/api-node/test/apps.e2e-spec.ts +++ b/api-node/test/apps.e2e-spec.ts @@ -18,13 +18,17 @@ describe('AppsController (e2e)', () => { it('/apps (GET)', async () => { const pythonApiResponse = await fetch(`${PYTHON_API_URL}/apps`); + const pythonApiHeaders = Object.fromEntries(pythonApiResponse.headers); const pythonApiData = await pythonApiResponse.json(); return request(app.getHttpServer()) .get('/apps') .expect((r) => { expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); + expect(r.body).toEqual({ ...pythonApiData }); + const headers = { ...r.headers }; + expect(Object.keys(headers).length).toBe(6); + expect(headers).toEqual(pythonApiHeaders); }); }); diff --git a/api-node/test/everything.e2e-spec.ts b/api-node/test/everything.e2e-spec.ts index 4ef124585..66d2a06c5 100644 --- a/api-node/test/everything.e2e-spec.ts +++ b/api-node/test/everything.e2e-spec.ts @@ -1,31 +1,40 @@ import { PYTHON_API_URL } from './utils'; import * as fs from 'fs'; -describe('python and Node api responses match', () => { +describe('Flask and NestJS api responses match', () => { const urls = JSON.parse( fs.readFileSync(`${__dirname}/utils/.urls.json`, 'utf8'), ); it.each(urls)('%s', async ({ url, status }) => { - let pythonResponse, nodeResponse, pythonResponseStatus, nodeResponseStatus; - let pythonResponseBody, nodeResponseBody; + let pythonResponse, + pythonResponseStatus, + pythonResponseBody, + pythonResponseHeaders; + + let nodeResponse, nodeResponseStatus, nodeResponseBody, nodeResponseHeaders; + let attempts = 0; while (attempts < 3) { try { - pythonResponse = await fetch(`${PYTHON_API_URL}${url}`, { - redirect: 'manual', - }); - nodeResponse = await fetch(`http://localhost:3000${url}`, { - redirect: 'manual', - }); + const [pythonResponse, nodeResponse] = await Promise.all([ + fetch(`${PYTHON_API_URL}${url}`, { + redirect: 'manual', + }), + fetch(`http://localhost:3000${url}`, { + redirect: 'manual', + }), + ]); if (pythonResponse?.status < 400 && nodeResponse?.status < 400) { pythonResponseStatus = pythonResponse?.status; nodeResponseStatus = nodeResponse?.status; pythonResponseBody = await pythonResponse?.text(); nodeResponseBody = await nodeResponse?.text(); + pythonResponseHeaders = pythonResponse?.headers; + nodeResponseHeaders = nodeResponse?.headers; break; } - } catch (error) { - console.error(`Attempt ${attempts + 1} failed:`, error); + } catch { + // console.error(`Attempt ${attempts + 1} failed:`, error); } attempts++; if (attempts < 3) { @@ -33,6 +42,8 @@ describe('python and Node api responses match', () => { nodeResponseStatus = nodeResponse?.status; pythonResponseBody = await pythonResponse?.text(); nodeResponseBody = await nodeResponse?.text(); + pythonResponseHeaders = pythonResponse?.headers; + nodeResponseHeaders = nodeResponse?.headers; await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying } } @@ -44,14 +55,12 @@ describe('python and Node api responses match', () => { expect(pythonResponseStatus).toBe(status); expect(nodeResponseStatus).toBe(pythonResponseStatus); + expect(pythonResponseHeaders).toEqual(nodeResponseHeaders); + if (pythonResponseStatus === 200) { - try { - const pythonResponseJson = JSON.parse(pythonResponseBody); - const nodeResponseJson = JSON.parse(nodeResponseBody); - expect(nodeResponseJson).toEqual(pythonResponseJson); - } catch { - expect(nodeResponseBody).toEqual(pythonResponseBody); - } + const pythonResponseJson = JSON.parse(pythonResponseBody); + const nodeResponseJson = JSON.parse(nodeResponseBody); + expect(nodeResponseJson).toEqual(pythonResponseJson); } }); }); diff --git a/api-node/test/utils/getUrls.ts b/api-node/test/utils/getUrls.ts index 5df08cc08..94df39fc5 100644 --- a/api-node/test/utils/getUrls.ts +++ b/api-node/test/utils/getUrls.ts @@ -14,7 +14,6 @@ export async function getAllYesAllUrls(): Promise { { url: '/apps', status: 200 }, { url: '/marketing-slugs', status: 200 }, { url: '/aws-lambda-layers', status: 200 }, - { url: '/healthz', status: 200 }, ]; // ------- packages ------- From e3375161031c7ec34c579ec5d72d7f245fea9dbe Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 24 Oct 2024 10:53:10 +0200 Subject: [PATCH 34/54] handle 404s, align headers --- api-node/src/apps/appVersion.interceptor.ts | 34 ++++++ .../src/common/alignHeaders.interceptor.ts | 29 +++++ api-node/src/common/htmlTemplates.ts | 5 + .../src/common/notFound.exceptionFilter.ts | 21 ++++ api-node/test/utils/makeRequest.ts | 113 ++++++++++++++++++ 5 files changed, 202 insertions(+) create mode 100644 api-node/src/apps/appVersion.interceptor.ts create mode 100644 api-node/src/common/alignHeaders.interceptor.ts create mode 100644 api-node/src/common/htmlTemplates.ts create mode 100644 api-node/src/common/notFound.exceptionFilter.ts create mode 100644 api-node/test/utils/makeRequest.ts diff --git a/api-node/src/apps/appVersion.interceptor.ts b/api-node/src/apps/appVersion.interceptor.ts new file mode 100644 index 000000000..b3c98af1b --- /dev/null +++ b/api-node/src/apps/appVersion.interceptor.ts @@ -0,0 +1,34 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import type { Response, Request } from 'express'; + +@Injectable() +export class AppVersionInterceptor implements NestInterceptor { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + intercept(context: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe( + map((data) => { + const response = context.switchToHttp().getResponse(); + const request = context.switchToHttp().getRequest(); + + if (request.query.response === 'download') { + response.status(302); + response.header('Location', data.url); + if (data.digest) { + response.header('Digest', data.digest); + } + return ''; + } + + return data; + }), + ); + } +} diff --git a/api-node/src/common/alignHeaders.interceptor.ts b/api-node/src/common/alignHeaders.interceptor.ts new file mode 100644 index 000000000..51bf62651 --- /dev/null +++ b/api-node/src/common/alignHeaders.interceptor.ts @@ -0,0 +1,29 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import type { Response } from 'express'; +/** + * Aligns the response headers sent from this API with the headers from the old Flask API. + * For now, we'll do this to ensure full backwards compatibility. We can tentatively remove + * or change headers in the future if necessary. + */ +@Injectable() +export class AlignHeadersInterceptor implements NestInterceptor { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + intercept(context: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe( + tap(() => { + const response = context.switchToHttp().getResponse(); + response.removeHeader('Connection'); + response.removeHeader('x-powered-by'); + response.removeHeader('date'); + response.removeHeader('etag'); + }), + ); + } +} diff --git a/api-node/src/common/htmlTemplates.ts b/api-node/src/common/htmlTemplates.ts new file mode 100644 index 000000000..22f376ad9 --- /dev/null +++ b/api-node/src/common/htmlTemplates.ts @@ -0,0 +1,5 @@ +export const NOT_FOUND_HTML = ` +404 Not Found +

Not Found

+

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

+`; diff --git a/api-node/src/common/notFound.exceptionFilter.ts b/api-node/src/common/notFound.exceptionFilter.ts new file mode 100644 index 000000000..e660f68d0 --- /dev/null +++ b/api-node/src/common/notFound.exceptionFilter.ts @@ -0,0 +1,21 @@ +import { + ArgumentsHost, + Catch, + ExceptionFilter, + NotFoundException, +} from '@nestjs/common'; + +import type { Response } from 'express'; + +import { NOT_FOUND_HTML } from './htmlTemplates'; + +@Catch(NotFoundException) +export class NotFoundExceptionFilter implements ExceptionFilter { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + catch(_exception: NotFoundException, host: ArgumentsHost): any { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + response.status(404); + response.send(NOT_FOUND_HTML); + } +} diff --git a/api-node/test/utils/makeRequest.ts b/api-node/test/utils/makeRequest.ts new file mode 100644 index 000000000..6a1b460c1 --- /dev/null +++ b/api-node/test/utils/makeRequest.ts @@ -0,0 +1,113 @@ +const PYTHON_API_PORT = 5031; +const API_NODE_PORT = 3000; + +export const PYTHON_API_URL = `http://localhost:${PYTHON_API_PORT}`; +export const API_NODE_URL = `http://localhost:${API_NODE_PORT}`; + +interface ResponseParts { + status: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + body: string | any; + headers: Record; + originalHeaders: Record; +} + +/** + * Make a request to both the Python and Node APIs and return the responses + * + * Also modifies and normalizes certain divergences in both API responses/headers + * to allow for minimal differences but easier testing + * + * @param path + * @param options + * @returns + */ +export async function makeDuplexRequest( + path: string, + options: RequestInit = { redirect: 'manual' }, +): Promise<{ python: ResponseParts; node: ResponseParts }> { + const [pythonResponse, nodeResponse] = await Promise.all([ + fetch(`${PYTHON_API_URL}${path}`, options), + fetch(`${API_NODE_URL}${path}`, options), + ]); + + const pythonStatus = pythonResponse.status; + const nodeStatus = nodeResponse.status; + + const originalPythonHeaders = Object.fromEntries( + pythonResponse.headers.entries(), + ); + const originalNodeHeaders = Object.fromEntries( + nodeResponse.headers.entries(), + ); + const { + pythonHeaders: normalizedPythonHeaders, + nodeHeaders: normalizedNodeHeaders, + } = normalizeHeaders(originalPythonHeaders, originalNodeHeaders); + + const pythonBody = await pythonResponse.text(); + const nodeBody = await nodeResponse.text(); + + return { + python: { + status: pythonStatus, + body: tryJsonParse(pythonBody), + headers: normalizedPythonHeaders, + originalHeaders: originalPythonHeaders, + }, + node: { + status: nodeStatus, + body: tryJsonParse(nodeBody), + headers: normalizedNodeHeaders, + originalHeaders: originalNodeHeaders, + }, + }; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function tryJsonParse(body: string): any | string { + try { + return JSON.parse(body); + } catch { + return body; + } +} + +function normalizeHeaders( + pythonHeaders: Record, + nodeHeaders: Record, +): { + pythonHeaders: Record; + nodeHeaders: Record; +} { + // The python API returns a newline at the end of the response body + // The Node API doesn't. Since this is quite tricky to change, let's accept the difference for now + if (pythonHeaders['content-length']) { + const pythonContentLength = parseInt(pythonHeaders['content-length']); + const nodeContentLength = parseInt(nodeHeaders['content-length']); + if (pythonContentLength - nodeContentLength === 1) { + pythonHeaders['content-length'] = nodeHeaders['content-length']; + } + } + + // NestJS appends "charset=utf-8" to the content-type header + // We just set "application/json" in Flask, so we'll align it here + // This shouldn't have any functional impact (famous last words lol) + const nodeContentType = nodeHeaders['content-type']; + const pythonContentType = pythonHeaders['content-type']; + if ( + nodeContentType && + nodeContentType.startsWith('application/json') && + pythonContentType && + pythonContentType === 'application/json' + ) { + nodeHeaders['content-type'] = 'application/json'; + } + + // caching headers might diverge, depending on number of requests made before + // we can always check on the original headers for the correct value if important for the test + delete nodeHeaders['x-from-cache']; + delete pythonHeaders['x-from-cache']; + + return { pythonHeaders, nodeHeaders }; +} From d2a978a74e51d5ac49f5415efa0e265852359a2e Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 24 Oct 2024 11:59:24 +0200 Subject: [PATCH 35/54] consistent headers in error responses, refactor normal e2e tests --- api-node/package.json | 1 + api-node/src/app.module.ts | 7 + api-node/src/apps/appVersion.interceptor.ts | 3 +- api-node/src/apps/apps.controller.ts | 44 ++--- .../src/common/alignHeaders.interceptor.ts | 25 ++- api-node/src/common/cache.ts | 2 +- api-node/src/common/htmlTemplates.ts | 13 ++ .../src/common/httpClient.exceptionFilter.ts | 37 ++++ .../src/common/notFound.exceptionFilter.ts | 21 --- api-node/src/common/utils.ts | 2 +- api-node/src/main.ts | 10 +- api-node/test/apps.e2e-spec.ts | 178 +++++++----------- api-node/test/awsLambdaLayers.e2e-spec.ts | 32 +--- api-node/test/healthCheck.e2e-spec.ts | 28 +-- api-node/test/marketing.e2e-spec.ts | 65 +++---- api-node/test/packages.e2e-spec.ts | 98 ++++------ api-node/test/sdks.e2e-spec.ts | 149 +++++---------- api-node/test/utils.ts | 5 - api-node/test/utils/makeRequest.ts | 8 +- 19 files changed, 305 insertions(+), 423 deletions(-) create mode 100644 api-node/src/common/httpClient.exceptionFilter.ts delete mode 100644 api-node/src/common/notFound.exceptionFilter.ts delete mode 100644 api-node/test/utils.ts diff --git a/api-node/package.json b/api-node/package.json index 9e6e34bdb..f6b4d9ef7 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -24,6 +24,7 @@ "python-api:build": "docker build -t registry-api-server ../", "python-api:start": "docker run -d -p 5031:5030 registry-api-server", "python-api:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-server)", + "start:all": "yarn build && yarn start:dev & yarn python-api:start", "sentry:sourcemaps": "sentry-cli sourcemaps inject --org sentry --project release-registry-nestjs ./dist && sentry-cli sourcemaps upload --org sentry --project release-registry-nestjs ./dist" }, "dependencies": { diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index d31328b19..de24e5c1c 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -10,10 +10,17 @@ import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; import { APP_FILTER } from '@nestjs/core'; import { CacheModule } from '@nestjs/cache-manager'; import { CACHE_DEFAULT_SETTINGS } from './common/cache'; +import { AppVersionInterceptor } from './apps/appVersion.interceptor'; +import { HttpClientExceptionFilter } from './common/httpClient.exceptionFilter'; const providers: Provider[] = [ RegistryService, { provide: APP_FILTER, useClass: SentryGlobalFilter }, + { + provide: APP_FILTER, + useClass: HttpClientExceptionFilter, + }, + AppVersionInterceptor, ]; @Module({ diff --git a/api-node/src/apps/appVersion.interceptor.ts b/api-node/src/apps/appVersion.interceptor.ts index b3c98af1b..d172a3983 100644 --- a/api-node/src/apps/appVersion.interceptor.ts +++ b/api-node/src/apps/appVersion.interceptor.ts @@ -8,6 +8,7 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import type { Response, Request } from 'express'; +import { getRedirectHtml } from '../common/htmlTemplates'; @Injectable() export class AppVersionInterceptor implements NestInterceptor { @@ -24,7 +25,7 @@ export class AppVersionInterceptor implements NestInterceptor { if (data.digest) { response.header('Digest', data.digest); } - return ''; + return getRedirectHtml(data.url); } return data; diff --git a/api-node/src/apps/apps.controller.ts b/api-node/src/apps/apps.controller.ts index aacb7931b..22c299406 100644 --- a/api-node/src/apps/apps.controller.ts +++ b/api-node/src/apps/apps.controller.ts @@ -3,68 +3,60 @@ import { Get, Param, Query, - Res, UseInterceptors, + NotFoundException, + BadRequestException, } from '@nestjs/common'; -import type { Response } from 'express'; import { RegistryService } from '../common/registry.service'; import { findDownloadUrl, getUrlChecksums, makeDigest } from './utils'; -import type { Apps } from './types'; +import type { AppEntry, Apps } from './types'; import { ReleaseRegistryCacheInterceptor } from '../common/cache'; +import { AppVersionInterceptor } from './appVersion.interceptor'; @Controller('apps') export class AppsController { constructor(private readonly registryService: RegistryService) {} @Get() - // Registering interceptor on method level b/c the other endpoint uses @Res which is incompatible with the - // cache interceptor :( @UseInterceptors(ReleaseRegistryCacheInterceptor) getApps(): Apps { return this.registryService.getApps(); } @Get(':appId/:version') + @UseInterceptors(AppVersionInterceptor) getAppVersion( - @Res() res: Response, @Param('appId') appId: string, @Param('version') version: string, - @Query('response') response?: string, - @Query('arch') arch?: string, - @Query('platform') platform?: string, - @Query('package') pkgName?: string, - ): void { + @Query('response') response?: string | undefined, + @Query('arch') arch?: string | undefined, + @Query('platform') platform?: string | undefined, + @Query('package') pkgName?: string | undefined, + ): AppEntry | Record { const appInfo = this.registryService.getApp(appId, version); if (!appInfo) { - res.status(404).send('App not found'); - return; + throw new NotFoundException(); } if (response === 'download') { - if (!arch || !platform || !pkgName) { - res.status(400).send('Missing required query parameters'); - return; + if (!pkgName || !arch || !platform) { + // The Flask API did this implicitly when accessing non-existing query parameters. + // In NestJS, we have to explicitly throw a 400 error. + throw new BadRequestException(); } - const url = findDownloadUrl(appInfo, pkgName, arch, platform); if (!url) { - res.status(404).send('Download URL not found'); - return; + throw new NotFoundException(); } const checksums = getUrlChecksums(appInfo, url); const digest = makeDigest(checksums); - res.setHeader('Location', url); - if (digest) { - res.setHeader('Digest', digest); - } - res.status(302).send(); - return; + return { url, digest }; } - res.json(appInfo); + return appInfo; } } diff --git a/api-node/src/common/alignHeaders.interceptor.ts b/api-node/src/common/alignHeaders.interceptor.ts index 51bf62651..04363068f 100644 --- a/api-node/src/common/alignHeaders.interceptor.ts +++ b/api-node/src/common/alignHeaders.interceptor.ts @@ -4,26 +4,35 @@ import { ExecutionContext, CallHandler, } from '@nestjs/common'; -import { Observable } from 'rxjs'; -import { tap } from 'rxjs/operators'; +import { Observable, throwError } from 'rxjs'; +import { tap, catchError } from 'rxjs/operators'; import type { Response } from 'express'; + /** * Aligns the response headers sent from this API with the headers from the old Flask API. * For now, we'll do this to ensure full backwards compatibility. We can tentatively remove * or change headers in the future if necessary. + * Importantly, we have to align the headers for regular responses as well as for thrown errors + * resulting in 4xx/5xx responses. */ @Injectable() export class AlignHeadersInterceptor implements NestInterceptor { // eslint-disable-next-line @typescript-eslint/no-explicit-any intercept(context: ExecutionContext, next: CallHandler): Observable { return next.handle().pipe( - tap(() => { - const response = context.switchToHttp().getResponse(); - response.removeHeader('Connection'); - response.removeHeader('x-powered-by'); - response.removeHeader('date'); - response.removeHeader('etag'); + tap(() => this._alignHeaders(context)), + catchError((error) => { + this._alignHeaders(context); + return throwError(() => error); }), ); } + + private _alignHeaders(context: ExecutionContext): void { + const response = context.switchToHttp().getResponse(); + response.removeHeader('Connection'); + response.removeHeader('x-powered-by'); + response.removeHeader('date'); + response.removeHeader('etag'); + } } diff --git a/api-node/src/common/cache.ts b/api-node/src/common/cache.ts index 5e859f310..6027ad947 100644 --- a/api-node/src/common/cache.ts +++ b/api-node/src/common/cache.ts @@ -11,7 +11,7 @@ export const CACHE_DEFAULT_SETTINGS = { /** * Custom interceptor to - * - disable caching if the REGISTRY_ENABLE_CACHE environment variable is not set to '1'. + * - disable caching if the REGISTRY_ENABLE_CACHE environment variable is not set to '1' or FLASK_ENV is not 'production'. * - set the X-From-Cache header to 1 if the response is served from cache (hit). */ export class ReleaseRegistryCacheInterceptor extends CacheInterceptor { diff --git a/api-node/src/common/htmlTemplates.ts b/api-node/src/common/htmlTemplates.ts index 22f376ad9..b087f676f 100644 --- a/api-node/src/common/htmlTemplates.ts +++ b/api-node/src/common/htmlTemplates.ts @@ -3,3 +3,16 @@ export const NOT_FOUND_HTML = `Not Found

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

`; + +export const BAD_REQUEST_HTML = ` +400 Bad Request +

Bad Request

+

The browser (or proxy) sent a request that this server could not understand.

+`; + +export const getRedirectHtml = ( + url: string, +): string => ` +Redirecting... +

Redirecting...

+

You should be redirected automatically to target URL: ${url}. If not click the link.`; diff --git a/api-node/src/common/httpClient.exceptionFilter.ts b/api-node/src/common/httpClient.exceptionFilter.ts new file mode 100644 index 000000000..56fa0ff28 --- /dev/null +++ b/api-node/src/common/httpClient.exceptionFilter.ts @@ -0,0 +1,37 @@ +import { + ArgumentsHost, + BadRequestException, + Catch, + ExceptionFilter, + NotFoundException, +} from '@nestjs/common'; + +import type { Response } from 'express'; + +import { BAD_REQUEST_HTML, NOT_FOUND_HTML } from './htmlTemplates'; + +/** + * Throwing Http Client Error exceptions like NotFoundException and BadRequestException + * makes NestJS respond with the respective status and a JSON body. + * + * In the Flask API, we returned a HTML template in such cases. Therefore, we need + * to implement a custom exception filter to stick to the same behavior. + */ +@Catch(NotFoundException, BadRequestException) +export class HttpClientExceptionFilter implements ExceptionFilter { + catch( + exception: NotFoundException | BadRequestException, + host: ArgumentsHost, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): any { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const status = exception.getStatus(); + response.status(status); + if (status === 404) { + response.send(NOT_FOUND_HTML); + } else if (status === 400) { + response.send(BAD_REQUEST_HTML); + } + } +} diff --git a/api-node/src/common/notFound.exceptionFilter.ts b/api-node/src/common/notFound.exceptionFilter.ts deleted file mode 100644 index e660f68d0..000000000 --- a/api-node/src/common/notFound.exceptionFilter.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - ArgumentsHost, - Catch, - ExceptionFilter, - NotFoundException, -} from '@nestjs/common'; - -import type { Response } from 'express'; - -import { NOT_FOUND_HTML } from './htmlTemplates'; - -@Catch(NotFoundException) -export class NotFoundExceptionFilter implements ExceptionFilter { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - catch(_exception: NotFoundException, host: ArgumentsHost): any { - const ctx = host.switchToHttp(); - const response = ctx.getResponse(); - response.status(404); - response.send(NOT_FOUND_HTML); - } -} diff --git a/api-node/src/common/utils.ts b/api-node/src/common/utils.ts index 41e5e1f31..05f4128ce 100644 --- a/api-node/src/common/utils.ts +++ b/api-node/src/common/utils.ts @@ -1,3 +1,3 @@ export function isTruthy(value: string): boolean { - return value.toLowerCase() === 'true' || value === '1' || value === 'yes'; + return ['true', '1', 'yes'].includes(value.toLowerCase()); } diff --git a/api-node/src/main.ts b/api-node/src/main.ts index f2b793964..af82679c5 100644 --- a/api-node/src/main.ts +++ b/api-node/src/main.ts @@ -2,12 +2,20 @@ import './instrument'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { AlignHeadersInterceptor } from './common/alignHeaders.interceptor'; async function bootstrap(): Promise { const app = await NestFactory.create(AppModule, { logger: ['debug', 'error', 'fatal', 'verbose', 'log', 'warn'], + forceCloseConnections: true, + cors: { origin: '*', allowedHeaders: ['*'] }, }); - app.enableCors({ origin: '*', allowedHeaders: ['*'] }); + + app.useGlobalInterceptors(new AlignHeadersInterceptor()); + + // disable setting the etag header + app.getHttpAdapter().getInstance().set('etag', false); + await app.listen(3000); } diff --git a/api-node/test/apps.e2e-spec.ts b/api-node/test/apps.e2e-spec.ts index 29b487ba4..26d09b8e1 100644 --- a/api-node/test/apps.e2e-spec.ts +++ b/api-node/test/apps.e2e-spec.ts @@ -1,71 +1,50 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; -import { PYTHON_API_URL } from './utils'; +import { + BAD_REQUEST_HTML, + getRedirectHtml, + NOT_FOUND_HTML, +} from '../src/common/htmlTemplates'; +import { makeDuplexRequest } from './utils/makeRequest'; describe('AppsController (e2e)', () => { - let app: INestApplication; + it('/apps (GET)', async () => { + const { python, node } = await makeDuplexRequest('/apps'); - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); - app = moduleFixture.createNestApplication(); - await app.init(); - }); + expect(node.headers).toEqual(python.headers); - it('/apps (GET)', async () => { - const pythonApiResponse = await fetch(`${PYTHON_API_URL}/apps`); - const pythonApiHeaders = Object.fromEntries(pythonApiResponse.headers); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get('/apps') - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual({ ...pythonApiData }); - const headers = { ...r.headers }; - expect(Object.keys(headers).length).toBe(6); - expect(headers).toEqual(pythonApiHeaders); - }); + expect(node.body).toEqual(python.body); }); describe('/apps/:appId/:version (GET)', () => { it('no query params, with latest version', async () => { const appId = 'sentry-cli'; const version = 'latest'; - - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/apps/${appId}/${version}`, + const { python, node } = await makeDuplexRequest( + `/apps/${appId}/${version}`, ); + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); - const pythonApiData = await pythonApiResponse.json(); + expect(node.headers).toEqual(python.headers); - return request(app.getHttpServer()) - .get(`/apps/${appId}/${version}`) - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + expect(node.body).toEqual(python.body); }); it('no query params, with fixed version', async () => { const appId = 'sentry-cli'; const version = '2.0.0'; - - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/apps/${appId}/${version}`, + const { python, node } = await makeDuplexRequest( + `/apps/${appId}/${version}`, ); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get(`/apps/${appId}/${version}`) - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); + + expect(node.body).toEqual(python.body); }); it('with response=download', async () => { @@ -75,71 +54,61 @@ describe('AppsController (e2e)', () => { const platform = 'linux'; const pkgName = 'sentry-cli'; - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/apps/${appId}/${version}?response=download&arch=${arch}&platform=${platform}&package=${pkgName}`, - { redirect: 'manual' }, + const urlPath = `/apps/${appId}/${version}?${new URLSearchParams({ + response: 'download', + arch, + platform, + package: pkgName, + })}`; + + const { python, node } = await makeDuplexRequest(urlPath); + + expect(node.status).toEqual(302); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); + + expect(node.headers.location).toEqual( + `https://downloads.sentry-cdn.com/sentry-cli/${version}/sentry-cli-Linux-x86_64`, + ); + expect(node.headers.digest).toEqual( + 'sha256=8cs/OTYjDCCuSrIIAmFPoggGPWI599KeBquwkXqTQRg=', ); - expect(pythonApiResponse.status).toEqual(302); - - return request(app.getHttpServer()) - .get(`/apps/${appId}/${version}`) - .query({ response: 'download', arch, platform, package: pkgName }) - .expect((r) => { - expect(r.status).toEqual(302); - expect(r.header.location).toEqual( - pythonApiResponse.headers.get('location'), - ); - expect(r.header.digest).toEqual( - pythonApiResponse.headers.get('digest'), - ); - expect(r.header.location).toEqual( - pythonApiResponse.headers.get('location'), - ); - expect(r.header.location).toEqual( - `https://downloads.sentry-cdn.com/sentry-cli/${version}/sentry-cli-Linux-x86_64`, - ); - expect(r.header.digest).toEqual( - 'sha256=8cs/OTYjDCCuSrIIAmFPoggGPWI599KeBquwkXqTQRg=', - ); - }); + expect(node.body).toEqual(getRedirectHtml(node.headers.location)); + expect(node.body).toEqual(python.body); }); it('with invalid appId', async () => { const appId = 'invalid-app'; const version = 'latest'; - - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/apps/${appId}/${version}`, + const { python, node } = await makeDuplexRequest( + `/apps/${appId}/${version}`, ); - expect(pythonApiResponse.status).toEqual(404); + expect(node.status).toEqual(404); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); - return request(app.getHttpServer()) - .get(`/apps/${appId}/${version}`) - .expect((r) => { - expect(r.status).toEqual(404); - expect(r.text).toEqual('App not found'); - }); + expect(node.body).toEqual(NOT_FOUND_HTML); + expect(node.body).toEqual(python.body); }); it('with response=download and missing parameters', async () => { const appId = 'sentry-cli'; const version = 'latest'; - - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/apps/${appId}/${version}?response=download`, + const { python, node } = await makeDuplexRequest( + `/apps/${appId}/${version}?response=download`, ); - expect(pythonApiResponse.status).toEqual(400); + expect(node.status).toEqual(400); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); - return request(app.getHttpServer()) - .get(`/apps/${appId}/${version}`) - .query({ response: 'download' }) - .expect((r) => { - expect(r.status).toEqual(400); - expect(r.text).toEqual('Missing required query parameters'); - }); + expect(node.body).toEqual(BAD_REQUEST_HTML); + expect(node.body).toEqual(python.body); }); it('with response=download and invalid parameters', async () => { @@ -148,20 +117,17 @@ describe('AppsController (e2e)', () => { const arch = 'invalid-arch'; const platform = 'invalid-platform'; const pkgName = 'invalid-package'; - - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/apps/${appId}/${version}?response=download&arch=${arch}&platform=${platform}&package=${pkgName}`, + const { python, node } = await makeDuplexRequest( + `/apps/${appId}/${version}?response=download&arch=${arch}&platform=${platform}&package=${pkgName}`, ); - expect(pythonApiResponse.status).toEqual(404); + expect(node.status).toEqual(404); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); - return request(app.getHttpServer()) - .get(`/apps/${appId}/${version}`) - .query({ response: 'download', arch, platform, package: pkgName }) - .expect((r) => { - expect(r.status).toEqual(404); - expect(r.text).toEqual('Download URL not found'); - }); + expect(node.body).toEqual(NOT_FOUND_HTML); + expect(node.body).toEqual(python.body); }); }); }); diff --git a/api-node/test/awsLambdaLayers.e2e-spec.ts b/api-node/test/awsLambdaLayers.e2e-spec.ts index d34a0919c..374d2562d 100644 --- a/api-node/test/awsLambdaLayers.e2e-spec.ts +++ b/api-node/test/awsLambdaLayers.e2e-spec.ts @@ -1,32 +1,14 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; -import { PYTHON_API_URL } from './utils'; +import { makeDuplexRequest } from './utils/makeRequest'; describe('AwsLambdaLayersController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); + it('/aws-lambda-layers (GET)', async () => { + const { python, node } = await makeDuplexRequest('/aws-lambda-layers'); - app = moduleFixture.createNestApplication(); - await app.init(); - }); + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); - it('/aws-lambda-layers (GET)', async () => { - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/aws-lambda-layers`, - ); - const pythonApiData = await pythonApiResponse.json(); + expect(node.headers).toEqual(python.headers); - return request(app.getHttpServer()) - .get('/aws-lambda-layers') - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + expect(node.body).toEqual(python.body); }); }); diff --git a/api-node/test/healthCheck.e2e-spec.ts b/api-node/test/healthCheck.e2e-spec.ts index 98e32b053..c62a0fca8 100644 --- a/api-node/test/healthCheck.e2e-spec.ts +++ b/api-node/test/healthCheck.e2e-spec.ts @@ -1,28 +1,14 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; -import { PYTHON_API_URL } from './utils'; +import { makeDuplexRequest } from './utils/makeRequest'; describe('HealthCheckController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); + it('/healthz (GET)', async () => { + const { python, node } = await makeDuplexRequest('/healthz'); - app = moduleFixture.createNestApplication(); - await app.init(); - }); + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); - it('/healthz (GET)', async () => { - const pythonApiResponse = await fetch(`${PYTHON_API_URL}/healthz`); - const pythonApiData = await pythonApiResponse.text(); + expect(node.headers).toEqual(python.headers); - return request(app.getHttpServer()) - .get('/healthz') - .expect(200) - .expect(pythonApiData); + expect(node.body).toEqual(python.body); }); }); diff --git a/api-node/test/marketing.e2e-spec.ts b/api-node/test/marketing.e2e-spec.ts index f78a7ed59..7d74ad2c8 100644 --- a/api-node/test/marketing.e2e-spec.ts +++ b/api-node/test/marketing.e2e-spec.ts @@ -1,63 +1,46 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; -import { PYTHON_API_URL } from './utils'; +import { makeDuplexRequest } from './utils/makeRequest'; describe('MarketingController (e2e)', () => { - let app: INestApplication; + it('/marketing-slugs (GET)', async () => { + const { python, node } = await makeDuplexRequest('/marketing-slugs'); - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); - app = moduleFixture.createNestApplication(); - await app.init(); - }); + expect(node.headers).toEqual(python.headers); - it('/marketing-slugs (GET)', async () => { - const pythonApiResponse = await fetch(`${PYTHON_API_URL}/marketing-slugs`); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get('/marketing-slugs') - .expect((res) => { - expect(res.status).toBe(200); - expect(res.body.slugs).toEqual(pythonApiData.slugs); - }); + expect(node.body).toEqual(python.body); }); describe('/marketing-slugs/:slug (GET)', () => { it.each(['python', 'javascript', 'browser', 'flask', 'django', 'rust'])( 'valid slug %s', async (slug) => { - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/marketing-slugs/${slug}`, + const { python, node } = await makeDuplexRequest( + `/marketing-slugs/${slug}`, ); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get(`/marketing-slugs/${slug}`) - .expect((res) => { - expect(res.status).toBe(200); - expect(res.body).toEqual(pythonApiData); - }); + + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); + + expect(node.body).toEqual(python.body); }, ); it('invalid slug', async () => { const slug = 'invalid-slug'; - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/marketing-slugs/${slug}`, + const { python, node } = await makeDuplexRequest( + `/marketing-slugs/${slug}`, ); - return request(app.getHttpServer()) - .get(`/marketing-slugs/${slug}`) - .expect((res) => { - expect(res.status).toBe(pythonApiResponse.status); - expect(res.status).toBe(404); - }); + expect(node.status).toEqual(404); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); + + expect(node.body).toEqual(python.body); }); }); }); diff --git a/api-node/test/packages.e2e-spec.ts b/api-node/test/packages.e2e-spec.ts index a99970923..6385d26cc 100644 --- a/api-node/test/packages.e2e-spec.ts +++ b/api-node/test/packages.e2e-spec.ts @@ -1,62 +1,44 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; - -import { PYTHON_API_URL } from './utils'; +import { makeDuplexRequest } from './utils/makeRequest'; describe('PackagesController (e2e)', () => { - let app: INestApplication; + it('/packages (GET)', async () => { + const { python, node } = await makeDuplexRequest('/packages'); - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); - app = moduleFixture.createNestApplication(); - await app.init(); - }); + expect(node.headers).toEqual(python.headers); - it('/packages (GET)', async () => { - const pythonApiResponse = await fetch(`${PYTHON_API_URL}/packages`); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get('/packages') - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + expect(node.body).toEqual(python.body); }); describe('/packages/:packageName/versions (GET)', () => { it('returns versions for existing package', async () => { const packageName = 'npm:@sentry/angular'; - - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/packages/${packageName}/versions`, + const { python, node } = await makeDuplexRequest( + `/packages/${packageName}/versions`, ); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get(`/packages/${packageName}/versions`) - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); + + expect(node.body).toEqual(python.body); }); it('returns 404 for non-existing package', async () => { const nonExistingPackage = 'npm:@sentry/non-existing-package'; - - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/packages/${nonExistingPackage}/versions`, + const { python, node } = await makeDuplexRequest( + `/packages/${nonExistingPackage}/versions`, ); - expect(pythonApiResponse.status).toEqual(404); - return request(app.getHttpServer()) - .get(`/packages/${nonExistingPackage}/versions`) - .expect(404); + expect(node.status).toEqual(404); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); + + expect(node.body).toEqual(python.body); }); }); @@ -65,32 +47,32 @@ describe('PackagesController (e2e)', () => { const packageName = 'npm:@sentry/angular'; const version = '8.0.0'; - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/packages/${packageName}/${version}`, + const { python, node } = await makeDuplexRequest( + `/packages/${packageName}/${version}`, ); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get(`/packages/${packageName}/${version}`) - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); + + expect(node.body).toEqual(python.body); }); it('returns 404 for non-existent package', async () => { const nonExistentPackage = 'npm:@sentry/non-existent-package'; const version = 'latest'; - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/packages/${nonExistentPackage}/${version}`, + const { python, node } = await makeDuplexRequest( + `/packages/${nonExistentPackage}/${version}`, ); - expect(pythonApiResponse.status).toEqual(404); + expect(node.status).toEqual(404); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); - return request(app.getHttpServer()) - .get(`/packages/${nonExistentPackage}/${version}`) - .expect(404); + expect(node.body).toEqual(python.body); }); }); }); diff --git a/api-node/test/sdks.e2e-spec.ts b/api-node/test/sdks.e2e-spec.ts index 67307d718..dd5f4673b 100644 --- a/api-node/test/sdks.e2e-spec.ts +++ b/api-node/test/sdks.e2e-spec.ts @@ -1,138 +1,77 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; -import { PYTHON_API_URL } from './utils'; +import { makeDuplexRequest } from './utils/makeRequest'; describe('SdksController (e2e)', () => { - let app: INestApplication; + describe('/sdks (GET)', () => { + it('get without strict', async () => { + const { python, node } = await makeDuplexRequest('/sdks'); - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); - app = moduleFixture.createNestApplication(); - await app.init(); - }); + expect(node.headers).toEqual(python.headers); - describe('/sdks (GET)', () => { - it('get without strict', async () => { - const pythonApiResponse = await fetch(`${PYTHON_API_URL}/sdks`); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get('/sdks') - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + expect(node.body).toEqual(python.body); }); it.each(['true', '1', 'yes'])('get with strict=%s', async (strict) => { - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/sdks?strict=${strict}`, + const { python, node } = await makeDuplexRequest( + `/sdks?strict=${strict}`, ); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get('/sdks') - .query({ strict }) - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); + + expect(node.body).toEqual(python.body); }); }); describe('/sdks/:sdkId/:version (GET)', () => { it('latest', async () => { const sdkId = 'sentry.python'; + const { python, node } = await makeDuplexRequest(`/sdks/${sdkId}/latest`); - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/sdks/${sdkId}/latest`, - ); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get(`/sdks/${sdkId}/latest`) - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); + + expect(node.body).toEqual(python.body); }); it('specific version', async () => { const sdkId = 'sentry.python'; const version = '2.0.0'; - - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/sdks/${sdkId}/${version}`, + const { python, node } = await makeDuplexRequest( + `/sdks/${sdkId}/${version}`, ); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get(`/sdks/${sdkId}/${version}`) - .expect((r) => { - expect(r.status).toEqual(200); - expect(r.body).toEqual(pythonApiData); - }); + + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); + + expect(node.headers).toEqual(python.headers); + + expect(node.body).toEqual(python.body); }); }); describe('/sdks/:sdkId/versions (GET)', () => { - it('python', async () => { - const sdkId = 'sentry.python'; - - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/sdks/${sdkId}/versions`, + it.each([ + 'sentry.python', + 'sentry.javascript.nextjs', + 'sentry.javascript.browser', + ])('%s', async (sdkId) => { + const { python, node } = await makeDuplexRequest( + `/sdks/${sdkId}/versions`, ); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get(`/sdks/${sdkId}/versions`) - .expect((r) => { - expect(r.status).toEqual(200); - const { versions, latest } = r.body; - expect(versions.sort()).toEqual(pythonApiData.versions.sort()); - expect(latest).toEqual(pythonApiData.latest); - }); - }); - it('NextJS', async () => { - const sdkId = 'sentry.javascript.nextjs'; + expect(node.status).toEqual(200); + expect(node.status).toEqual(python.status); - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/sdks/${sdkId}/versions`, - ); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get(`/sdks/${sdkId}/versions`) - .expect((r) => { - expect(r.status).toEqual(200); - const { versions, latest } = r.body; - expect(versions.sort()).toEqual(pythonApiData.versions.sort()); - expect(latest).toEqual(pythonApiData.latest); - }); - }); + expect(node.headers).toEqual(python.headers); - it('Javascript Browser', async () => { - const sdkId = 'sentry.javascript.browser'; - - const pythonApiResponse = await fetch( - `${PYTHON_API_URL}/sdks/${sdkId}/versions`, - ); - const pythonApiData = await pythonApiResponse.json(); - - return request(app.getHttpServer()) - .get(`/sdks/${sdkId}/versions`) - .expect((r) => { - expect(r.status).toEqual(200); - const { versions, latest } = r.body; - expect(versions.sort()).toEqual(pythonApiData.versions.sort()); - expect(latest).toEqual(pythonApiData.latest); - }); + expect(node.body).toEqual(python.body); }); }); }); diff --git a/api-node/test/utils.ts b/api-node/test/utils.ts deleted file mode 100644 index 3c9302694..000000000 --- a/api-node/test/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -const PYTHON_API_PORT = 5031; -export const PYTHON_API_URL = `http://localhost:${PYTHON_API_PORT}`; - -import * as fs from 'fs'; -import * as path from 'path'; diff --git a/api-node/test/utils/makeRequest.ts b/api-node/test/utils/makeRequest.ts index 6a1b460c1..e5b7bd467 100644 --- a/api-node/test/utils/makeRequest.ts +++ b/api-node/test/utils/makeRequest.ts @@ -97,11 +97,13 @@ function normalizeHeaders( const pythonContentType = pythonHeaders['content-type']; if ( nodeContentType && - nodeContentType.startsWith('application/json') && + (nodeContentType.startsWith('application/json') || + nodeContentType.startsWith('text/html')) && pythonContentType && - pythonContentType === 'application/json' + (pythonContentType === 'application/json' || + pythonContentType === 'text/html') ) { - nodeHeaders['content-type'] = 'application/json'; + nodeHeaders['content-type'] = pythonContentType; } // caching headers might diverge, depending on number of requests made before From 9f6a1140bc2bdc69917284afe3b85de083e892bd Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 28 Oct 2024 14:15:43 +0100 Subject: [PATCH 36/54] run tests in ci again --- .github/workflows/node-api.yml | 10 +---- api-node/package.json | 5 ++- api-node/scripts/run-e2e-tests.ts | 68 ++++++++++++++++++++++++++++++ api-node/test/utils/getUrls.ts | 2 +- api-node/test/utils/makeRequest.ts | 14 +++--- api-node/tsconfig.build.json | 2 +- api-node/tsconfig.json | 2 +- 7 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 api-node/scripts/run-e2e-tests.ts diff --git a/.github/workflows/node-api.yml b/.github/workflows/node-api.yml index 22b7c2a88..3c45e2800 100644 --- a/.github/workflows/node-api.yml +++ b/.github/workflows/node-api.yml @@ -114,12 +114,6 @@ jobs: - name: Build Python API working-directory: ./api-node run: yarn python-api:build - - name: Start Python API + - name: Run E2E tests working-directory: ./api-node - run: yarn python-api:start - - name: Run e2e tests - working-directory: ./api-node - run: yarn test:e2e - - name: Stop Python API - working-directory: ./api-node - run: yarn python-api:stop + run: yarn test:e2e:ci diff --git a/api-node/package.json b/api-node/package.json index f6b4d9ef7..1113148e1 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -8,6 +8,8 @@ "scripts": { "build": "nest build && yarn sentry:sourcemaps", "build:ci": "nest build", + "clean": "rm -rf dist", + "clean:all": "yarn clean && rm -rf dist node_modules && yarn install", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", @@ -19,10 +21,11 @@ "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json --testPathIgnorePatterns=everything.e2e-spec.ts", + "test:e2e:ci": "NODE_ENV=test ts-node ./scripts/run-e2e-tests.ts", "test:e2e:urls": "NODE_ENV=test jest --config ./test/jest-e2e.json everything.e2e-spec.ts", "test:e2e:urls:generate": "ts-node ./test/utils/getUrls.ts", "python-api:build": "docker build -t registry-api-server ../", - "python-api:start": "docker run -d -p 5031:5030 registry-api-server", + "python-api:start": "docker start $(docker ps -aq --filter ancestor=registry-api-server) && echo 'Flask API started successfully' || (docker run -d -p 5031:5030 registry-api-server && echo 'Flask API started successfully')", "python-api:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-server)", "start:all": "yarn build && yarn start:dev & yarn python-api:start", "sentry:sourcemaps": "sentry-cli sourcemaps inject --org sentry --project release-registry-nestjs ./dist && sentry-cli sourcemaps upload --org sentry --project release-registry-nestjs ./dist" diff --git a/api-node/scripts/run-e2e-tests.ts b/api-node/scripts/run-e2e-tests.ts new file mode 100644 index 000000000..65f0980f3 --- /dev/null +++ b/api-node/scripts/run-e2e-tests.ts @@ -0,0 +1,68 @@ +// A script that runs the e2e tests +// 1. Starts the NestJS and Flask APIs in Docker +// 2. Runs the tests +// 3. Stops the NestJS and Flask APIs + +import * as childProcess from 'child_process'; + +const nodeApi = childProcess.spawn('yarn', ['start:prod']); + +let nodeApiStarted = false; +let pythonApiStarted = false; + +nodeApi.stdout.on('data', (data) => { + console.log('[NestJS]', data.toString()); + if (data.toString().includes('application successfully started')) { + nodeApiStarted = true; + checkAndRunTests(); + } +}); + +nodeApi.stderr.on('data', (data) => { + console.error('[NestJS]', data.toString()); +}); + +const pythonApi = childProcess.spawn('yarn', ['python-api:start']); + +pythonApi.stdout.on('data', (data) => { + console.log('[Flask]', data.toString()); + if (data.toString().includes('Flask API started successfully')) { + pythonApiStarted = true; + checkAndRunTests(); + } +}); + +pythonApi.stderr.on('data', (data) => { + console.error('[Flask]', data.toString()); +}); + +function checkAndRunTests(): void { + console.log('checkAndRunTests', { nodeApiStarted, pythonApiStarted }); + if (nodeApiStarted && pythonApiStarted) { + console.log('Both APIs are running. Starting e2e tests...'); + const tests = childProcess.spawn('yarn', ['test:e2e']); + + tests.stdout.on('data', (data) => { + console.log('[Tests]', data.toString()); + }); + + tests.stderr.on('data', (data) => { + console.error('[Tests]', data.toString()); + }); + + tests.on('close', (code) => { + console.log(`Tests finished with code ${code}`); + cleanup(code); + }); + } +} + +function cleanup(code: number): void { + console.log('Cleaning up...'); + nodeApi.kill(); + pythonApi.kill(); + childProcess.execSync('yarn python-api:stop'); + process.exit(code); +} + +process.on('SIGINT', () => cleanup(0)); diff --git a/api-node/test/utils/getUrls.ts b/api-node/test/utils/getUrls.ts index 94df39fc5..1c6ee3cb8 100644 --- a/api-node/test/utils/getUrls.ts +++ b/api-node/test/utils/getUrls.ts @@ -1,4 +1,4 @@ -import { PYTHON_API_URL } from '../utils'; +import { PYTHON_API_URL } from './makeRequest'; import * as fs from 'fs'; import * as path from 'path'; diff --git a/api-node/test/utils/makeRequest.ts b/api-node/test/utils/makeRequest.ts index e5b7bd467..f8152f575 100644 --- a/api-node/test/utils/makeRequest.ts +++ b/api-node/test/utils/makeRequest.ts @@ -26,10 +26,11 @@ export async function makeDuplexRequest( path: string, options: RequestInit = { redirect: 'manual' }, ): Promise<{ python: ResponseParts; node: ResponseParts }> { - const [pythonResponse, nodeResponse] = await Promise.all([ - fetch(`${PYTHON_API_URL}${path}`, options), - fetch(`${API_NODE_URL}${path}`, options), - ]); + const pythonResponse = await fetch(`${PYTHON_API_URL}${path}`, options); + const pythonBody = await pythonResponse.text(); + + const nodeResponse = await fetch(`${API_NODE_URL}${path}`, options); + const nodeBody = await nodeResponse.text(); const pythonStatus = pythonResponse.status; const nodeStatus = nodeResponse.status; @@ -45,9 +46,6 @@ export async function makeDuplexRequest( nodeHeaders: normalizedNodeHeaders, } = normalizeHeaders(originalPythonHeaders, originalNodeHeaders); - const pythonBody = await pythonResponse.text(); - const nodeBody = await nodeResponse.text(); - return { python: { status: pythonStatus, @@ -86,7 +84,7 @@ function normalizeHeaders( const pythonContentLength = parseInt(pythonHeaders['content-length']); const nodeContentLength = parseInt(nodeHeaders['content-length']); if (pythonContentLength - nodeContentLength === 1) { - pythonHeaders['content-length'] = nodeHeaders['content-length']; + nodeHeaders['content-length'] = pythonHeaders['content-length']; } } diff --git a/api-node/tsconfig.build.json b/api-node/tsconfig.build.json index 64f86c6bd..052134cd3 100644 --- a/api-node/tsconfig.build.json +++ b/api-node/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] + "exclude": ["node_modules", "test", "dist", "**/*spec.ts", "scripts"] } diff --git a/api-node/tsconfig.json b/api-node/tsconfig.json index cc47eb846..abc2067b8 100644 --- a/api-node/tsconfig.json +++ b/api-node/tsconfig.json @@ -18,6 +18,6 @@ "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false, "inlineSources": true, - "sourceRoot": "/" + "sourceRoot": "/src" } } From 9ced5520636369e7fbeecc1e2a82af6c57aa54dd Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 28 Oct 2024 14:23:05 +0100 Subject: [PATCH 37/54] build node API before running e2e tests in CI --- .github/workflows/node-api.yml | 3 +++ api-node/scripts/run-e2e-tests.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/.github/workflows/node-api.yml b/.github/workflows/node-api.yml index 3c45e2800..7bf63e65f 100644 --- a/.github/workflows/node-api.yml +++ b/.github/workflows/node-api.yml @@ -114,6 +114,9 @@ jobs: - name: Build Python API working-directory: ./api-node run: yarn python-api:build + - name: Build Node API + working-directory: ./api-node + run: yarn build:ci - name: Run E2E tests working-directory: ./api-node run: yarn test:e2e:ci diff --git a/api-node/scripts/run-e2e-tests.ts b/api-node/scripts/run-e2e-tests.ts index 65f0980f3..0ec15db70 100644 --- a/api-node/scripts/run-e2e-tests.ts +++ b/api-node/scripts/run-e2e-tests.ts @@ -22,6 +22,13 @@ nodeApi.stderr.on('data', (data) => { console.error('[NestJS]', data.toString()); }); +nodeApi.on('close', (code) => { + if (code !== 0) { + console.error(`NestJS finished with code ${code}`); + process.exit(code); + } +}); + const pythonApi = childProcess.spawn('yarn', ['python-api:start']); pythonApi.stdout.on('data', (data) => { @@ -36,6 +43,13 @@ pythonApi.stderr.on('data', (data) => { console.error('[Flask]', data.toString()); }); +pythonApi.on('close', (code) => { + if (code !== 0) { + console.error(`Flask finished with code ${code}`); + process.exit(code); + } +}); + function checkAndRunTests(): void { console.log('checkAndRunTests', { nodeApiStarted, pythonApiStarted }); if (nodeApiStarted && pythonApiStarted) { From e33aab96a7448435923fcdfb235116f9ac319b20 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 10:25:02 +0100 Subject: [PATCH 38/54] remove state in registry service, fix url test --- api-node/src/common/registry.service.ts | 24 +++++-------- api-node/test/everything.e2e-spec.ts | 47 +++++++++---------------- 2 files changed, 26 insertions(+), 45 deletions(-) diff --git a/api-node/src/common/registry.service.ts b/api-node/src/common/registry.service.ts index 88309b2ac..1e2300a28 100644 --- a/api-node/src/common/registry.service.ts +++ b/api-node/src/common/registry.service.ts @@ -23,16 +23,6 @@ const NAMESPACE_FILE_MARKER = '__NAMESPACE__'; @Injectable() export class RegistryService { - #packages: string[]; - #slugs: Record; - - constructor() { - this.#packages = Array.from(iterPackages()); - this.#slugs = JSON.parse( - fs.readFileSync(path.join('..', 'misc', 'marketing-slugs.json'), 'utf8'), - ); - } - // SDKs getSdks(strict: boolean = false): Sdks { const sdks: Sdks = {}; @@ -83,7 +73,7 @@ export class RegistryService { // Packages getPackages(strict: boolean = false): Packages { - return this.#packages.reduce((acc, canonical) => { + return Array.from(iterPackages()).reduce((acc, canonical) => { const packageDir = getPackageDirFromCanonical(canonical); const latestFilePath = path.join(packageDir, 'latest.json'); @@ -178,11 +168,11 @@ export class RegistryService { // Marketing getMarketingSlugs(): MarketingSlugs { - return { slugs: Object.keys(this.#slugs).sort() }; + return { slugs: Object.keys(getSlugs()).sort() }; } resolveMarketingSlug(slug: string): ResolvedMarketingSlug | null { - const data = this.#slugs[slug]; + const data = getSlugs()[slug]; if (!data) { return null; } @@ -213,8 +203,6 @@ export class RegistryService { }; } - // AWS Lambda Layers - getAwsLambdaLayers(): AwsLambdaLayers { const layers: AwsLambdaLayers = {}; const lambdaLayersDir = path.resolve(AWS_LAMBDA_LAYERS_PATH); @@ -288,3 +276,9 @@ function getPackageDirFromCanonical(canonicalPackageName: string): string { .split(path.sep); return path.resolve(path.join(PACKAGES_PATH, ...pkgPath)); } + +function getSlugs(): Record { + return JSON.parse( + fs.readFileSync(path.join('..', 'misc', 'marketing-slugs.json'), 'utf8'), + ); +} diff --git a/api-node/test/everything.e2e-spec.ts b/api-node/test/everything.e2e-spec.ts index 66d2a06c5..12c204a18 100644 --- a/api-node/test/everything.e2e-spec.ts +++ b/api-node/test/everything.e2e-spec.ts @@ -1,4 +1,4 @@ -import { PYTHON_API_URL } from './utils'; +import { makeDuplexRequest } from './utils/makeRequest'; import * as fs from 'fs'; describe('Flask and NestJS api responses match', () => { const urls = JSON.parse( @@ -16,27 +16,22 @@ describe('Flask and NestJS api responses match', () => { let attempts = 0; while (attempts < 3) { try { - const [pythonResponse, nodeResponse] = await Promise.all([ - fetch(`${PYTHON_API_URL}${url}`, { - redirect: 'manual', - }), - fetch(`http://localhost:3000${url}`, { - redirect: 'manual', - }), - ]); - if (pythonResponse?.status < 400 && nodeResponse?.status < 400) { - pythonResponseStatus = pythonResponse?.status; - nodeResponseStatus = nodeResponse?.status; - pythonResponseBody = await pythonResponse?.text(); - nodeResponseBody = await nodeResponse?.text(); - pythonResponseHeaders = pythonResponse?.headers; - nodeResponseHeaders = nodeResponse?.headers; - break; + const { node, python } = await makeDuplexRequest(url); + + pythonResponseStatus = python.status; + nodeResponseStatus = node.status; + pythonResponseBody = python.body; + nodeResponseBody = node.body; + pythonResponseHeaders = python.headers; + nodeResponseHeaders = node.headers; + break; + } catch (error) { + console.error(`Attempt ${attempts + 1} failed:`, error); + attempts++; + if (attempts >= 3) { + console.error(`Failed to fetch ${url} after 3 attempts`); } - } catch { - // console.error(`Attempt ${attempts + 1} failed:`, error); } - attempts++; if (attempts < 3) { pythonResponseStatus = pythonResponse?.status; nodeResponseStatus = nodeResponse?.status; @@ -48,19 +43,11 @@ describe('Flask and NestJS api responses match', () => { } } - if (attempts === 3) { - console.error(`Failed to fetch ${url} after 3 attempts`); - } - expect(pythonResponseStatus).toBe(status); expect(nodeResponseStatus).toBe(pythonResponseStatus); - expect(pythonResponseHeaders).toEqual(nodeResponseHeaders); + expect(nodeResponseHeaders).toEqual(pythonResponseHeaders); - if (pythonResponseStatus === 200) { - const pythonResponseJson = JSON.parse(pythonResponseBody); - const nodeResponseJson = JSON.parse(nodeResponseBody); - expect(nodeResponseJson).toEqual(pythonResponseJson); - } + expect(nodeResponseBody).toEqual(pythonResponseBody); }); }); From 67fb56b7150469bc615aeef2bdb683365a58431d Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 10:52:34 +0100 Subject: [PATCH 39/54] align error handling, strict flag, ValueError --- api-node/src/common/registry.service.ts | 29 ++++++++------------ api-node/src/common/utils.ts | 5 +++- api-node/src/common/valueError.ts | 6 ++++ api-node/src/packages/packages.controller.ts | 5 ++-- api-node/src/sdks/sdks.controller.ts | 4 +-- 5 files changed, 27 insertions(+), 22 deletions(-) create mode 100644 api-node/src/common/valueError.ts diff --git a/api-node/src/common/registry.service.ts b/api-node/src/common/registry.service.ts index 1e2300a28..4f631e2b3 100644 --- a/api-node/src/common/registry.service.ts +++ b/api-node/src/common/registry.service.ts @@ -11,6 +11,7 @@ import type { AppEntry, Apps } from '../apps/types'; import type { PackageEntry, Packages } from '../packages/types'; import type { AwsLambdaLayers } from '../aws-lambda-layers/types'; import * as semver from 'semver'; +import { ValueError } from './valueError'; const SDKS_PATH = path.join('..', 'sdks'); const APPS_PATH = path.join('..', 'apps'); @@ -37,15 +38,15 @@ export class RegistryService { if (pkg) { sdks[link] = pkg; } else if (strict) { - throw new Error( + throw new ValueError( `Package ${link}, canonical cannot be resolved: ${canonical}`, ); } } catch (error) { - if (strict) { + if (error instanceof ValueError) { throw error; } - // If not strict, continue to the next SDK + // IO and other errors are ignored } } } catch (error) { @@ -73,22 +74,16 @@ export class RegistryService { // Packages getPackages(strict: boolean = false): Packages { - return Array.from(iterPackages()).reduce((acc, canonical) => { - const packageDir = getPackageDirFromCanonical(canonical); - const latestFilePath = path.join(packageDir, 'latest.json'); - - try { - const packageInfo = JSON.parse( - fs.readFileSync(latestFilePath).toString(), + return Array.from(iterPackages()).reduce((acc, pkgName) => { + const pkg = this.getPackage(pkgName); + if (!pkg && strict) { + throw new ValueError( + `Package does not exist or invalid canonical: ${pkgName}`, ); - return { - ...acc, - [packageInfo.canonical]: packageInfo, - }; - } catch (e) { - console.error(`Failed to read package: ${canonical}`); - console.error(e); } + + acc[pkg.canonical] = pkg; + return acc; }, {}); } diff --git a/api-node/src/common/utils.ts b/api-node/src/common/utils.ts index 05f4128ce..c0f59b5c3 100644 --- a/api-node/src/common/utils.ts +++ b/api-node/src/common/utils.ts @@ -1,3 +1,6 @@ -export function isTruthy(value: string): boolean { +export function isTruthy(value: string | undefined): boolean { + if (!value) { + return false; + } return ['true', '1', 'yes'].includes(value.toLowerCase()); } diff --git a/api-node/src/common/valueError.ts b/api-node/src/common/valueError.ts new file mode 100644 index 000000000..83b7f5205 --- /dev/null +++ b/api-node/src/common/valueError.ts @@ -0,0 +1,6 @@ +export class ValueError extends Error { + constructor(message: string) { + super(message); + this.name = 'ValueError'; + } +} diff --git a/api-node/src/packages/packages.controller.ts b/api-node/src/packages/packages.controller.ts index 0a94d025d..c9edbe96b 100644 --- a/api-node/src/packages/packages.controller.ts +++ b/api-node/src/packages/packages.controller.ts @@ -9,6 +9,7 @@ import { import { RegistryService } from '../common/registry.service'; import { PackageEntry, Packages, PackageVersions } from './types'; import { ReleaseRegistryCacheInterceptor } from '../common/cache'; +import { isTruthy } from 'src/common/utils'; @Controller('packages') @UseInterceptors(ReleaseRegistryCacheInterceptor) @@ -16,8 +17,8 @@ export class PackagesController { constructor(private registryService: RegistryService) {} @Get() - getPackages(@Query('strict') strict: boolean = false): Packages { - return this.registryService.getPackages(strict); + getPackages(@Query('strict') strict?: string): Packages { + return this.registryService.getPackages(isTruthy(strict)); } @Get('/:package(*)/versions') diff --git a/api-node/src/sdks/sdks.controller.ts b/api-node/src/sdks/sdks.controller.ts index bc4b62842..da0387651 100644 --- a/api-node/src/sdks/sdks.controller.ts +++ b/api-node/src/sdks/sdks.controller.ts @@ -2,6 +2,7 @@ import { Controller, Get, Param, Query, UseInterceptors } from '@nestjs/common'; import { SdkEntry, Sdks, SdkVersions } from './types'; import { RegistryService } from '../common/registry.service'; import { ReleaseRegistryCacheInterceptor } from '../common/cache'; +import { isTruthy } from 'src/common/utils'; @Controller('sdks') @UseInterceptors(ReleaseRegistryCacheInterceptor) @@ -10,8 +11,7 @@ export class SdksController { @Get() getSdks(@Query('strict') strict?: string): Sdks { - const isStrict = - strict?.toLowerCase() === 'true' || strict === '1' || strict === 'yes'; + const isStrict = isTruthy(strict); return this.registryService.getSdks(isStrict); } From 842963978ec94bf4fee061123dbc478227a133f0 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 11:04:09 +0100 Subject: [PATCH 40/54] lint --- api-server/apiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-server/apiserver.py b/api-server/apiserver.py index 0e5874167..b8dc04496 100644 --- a/api-server/apiserver.py +++ b/api-server/apiserver.py @@ -140,7 +140,7 @@ def get_package_versions(self, canonical): if ":" not in canonical: return registry, package = canonical.split(":", 1) - # Allow ":" to be used as a path separator + # Allow ":" to be used as a path separator package = package.replace(":", "/") rv = set() for filename in os.listdir(self._path("packages", registry, package)): From 8cd0caa8334498d8fc5dd3ec40c38b6a244e951a Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 11:06:01 +0100 Subject: [PATCH 41/54] restore json file --- aws-lambda-layers/node/8.32.0.json | 34 +----------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/aws-lambda-layers/node/8.32.0.json b/aws-lambda-layers/node/8.32.0.json index 4ef356bfc..618a636a7 100644 --- a/aws-lambda-layers/node/8.32.0.json +++ b/aws-lambda-layers/node/8.32.0.json @@ -1,33 +1 @@ -{ - "name": "AWS Lambda Node Layer", - "repo_url": "https://github.com/getsentry/sentry-javascript", - "main_docs_url": "https://docs.sentry.io/platforms/node/guides/aws-lambda", - "created_at": "2021-02-01T14:28:25.000Z", - "canonical": "aws-layer:node", - "sdk_version": "8.32.0", - "account_number": "943013980633", - "layer_name": "SentryNodeServerlessSDK", - "regions": [ - { "region": "af-south-1", "version": "242" }, - { "region": "ap-south-1", "version": "275" }, - { "region": "eu-north-1", "version": "275" }, - { "region": "eu-west-3", "version": "275" }, - { "region": "eu-south-1", "version": "242" }, - { "region": "eu-west-2", "version": "275" }, - { "region": "eu-west-1", "version": "275" }, - { "region": "ap-northeast-3", "version": "267" }, - { "region": "ap-northeast-2", "version": "275" }, - { "region": "me-south-1", "version": "242" }, - { "region": "ap-northeast-1", "version": "275" }, - { "region": "ca-central-1", "version": "275" }, - { "region": "sa-east-1", "version": "275" }, - { "region": "ap-east-1", "version": "242" }, - { "region": "ap-southeast-1", "version": "275" }, - { "region": "ap-southeast-2", "version": "275" }, - { "region": "eu-central-1", "version": "275" }, - { "region": "us-east-1", "version": "275" }, - { "region": "us-east-2", "version": "275" }, - { "region": "us-west-1", "version": "275" }, - { "region": "us-west-2", "version": "275" } - ] -} +{"name":"AWS Lambda Node Layer","repo_url":"https://github.com/getsentry/sentry-javascript","main_docs_url":"https://docs.sentry.io/platforms/node/guides/aws-lambda","created_at":"2021-02-01T14:28:25.000Z","canonical":"aws-layer:node","sdk_version":"8.32.0","account_number":"943013980633","layer_name":"SentryNodeServerlessSDK","regions":[{"region":"af-south-1","version":"242"},{"region":"ap-south-1","version":"275"},{"region":"eu-north-1","version":"275"},{"region":"eu-west-3","version":"275"},{"region":"eu-south-1","version":"242"},{"region":"eu-west-2","version":"275"},{"region":"eu-west-1","version":"275"},{"region":"ap-northeast-3","version":"267"},{"region":"ap-northeast-2","version":"275"},{"region":"me-south-1","version":"242"},{"region":"ap-northeast-1","version":"275"},{"region":"ca-central-1","version":"275"},{"region":"sa-east-1","version":"275"},{"region":"ap-east-1","version":"242"},{"region":"ap-southeast-1","version":"275"},{"region":"ap-southeast-2","version":"275"},{"region":"eu-central-1","version":"275"},{"region":"us-east-1","version":"275"},{"region":"us-east-2","version":"275"},{"region":"us-west-1","version":"275"},{"region":"us-west-2","version":"275"}]} \ No newline at end of file From a1687427e21f4f5408e5b74d2458d30af466dd0c Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 13:07:56 +0100 Subject: [PATCH 42/54] dynamic port --- api-node/src/main.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/api-node/src/main.ts b/api-node/src/main.ts index af82679c5..26cda04c6 100644 --- a/api-node/src/main.ts +++ b/api-node/src/main.ts @@ -4,6 +4,8 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { AlignHeadersInterceptor } from './common/alignHeaders.interceptor'; +const DEFAULT_PORT = 3000; + async function bootstrap(): Promise { const app = await NestFactory.create(AppModule, { logger: ['debug', 'error', 'fatal', 'verbose', 'log', 'warn'], @@ -16,7 +18,18 @@ async function bootstrap(): Promise { // disable setting the etag header app.getHttpAdapter().getInstance().set('etag', false); - await app.listen(3000); + await app.listen(getPort()); +} + +function getPort(): number { + try { + if (process.env.PORT) { + return parseInt(process.env.PORT); + } + } catch (error) { + console.error('Invalid port number', error); + } + return DEFAULT_PORT; } bootstrap(); From e06c27f4d7c3a25ae0afbabeed8a8ed2d2b42da6 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 13:08:05 +0100 Subject: [PATCH 43/54] volta, node 22 --- api-node/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api-node/package.json b/api-node/package.json index 1113148e1..e6d4ebd77 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -81,5 +81,8 @@ ], "coverageDirectory": "../coverage", "testEnvironment": "node" + }, + "volta": { + "node": "22.11.0" } } From 64f1859a87957740aebbeaa729ff1b20b6471ad8 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 15:59:36 +0100 Subject: [PATCH 44/54] docker image for NestJS API --- .dockerignore | 3 +++ Dockerfile.nestjs | 50 ++++++++++++++++++++++++++++++++++++++++++++ api-node/src/main.ts | 19 ++++++++++++++++- 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.nestjs diff --git a/.dockerignore b/.dockerignore index 075a97d4d..dc38da2b2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,3 +7,6 @@ cloudbuild.yaml Dockerfile Makefile README.md +node_modules +dist +.env diff --git a/Dockerfile.nestjs b/Dockerfile.nestjs new file mode 100644 index 000000000..7d6a52cca --- /dev/null +++ b/Dockerfile.nestjs @@ -0,0 +1,50 @@ +FROM node:22-alpine + +ENV \ + NODE_ENV=production \ + PORT=5030 + +ENV \ + REGISTRY_UID=10011 \ + REGISTRY_GID=10011 + +# Create a new user and group with fixed uid/gid +RUN addgroup -S registry -g $REGISTRY_GID \ + && adduser -S registry -G registry -u $REGISTRY_UID + +WORKDIR /work + +# Copy package files +COPY api-node/package.json api-node/yarn.lock api-node/ + +# Remove dev dependencies +RUN cd api-node && yarn install --production --frozen-lockfile + +COPY . . + +WORKDIR /work/api-node + +# Build the application +RUN yarn build + +# Set ownership +RUN chown -R registry:registry ./ + +# Smoke test +RUN node -v && node dist/main.js --smoke + +EXPOSE 5030 + +USER registry + +CMD ["node", "dist/main.js"] +# To start this Docker container, use the following commands: + +# Build the Docker image: +# docker build -t registry-api-nestjs-server -f Dockerfile.nestjs . + +# Run the Docker container: +# docker run -p 503x:5030 registry-api-nestjs-server + +# These commands should be run from the directory containing this Dockerfile. +# The -p 5030:5030 flag maps the container's port 5030 to the host's port 5030. \ No newline at end of file diff --git a/api-node/src/main.ts b/api-node/src/main.ts index 26cda04c6..62c9b063b 100644 --- a/api-node/src/main.ts +++ b/api-node/src/main.ts @@ -18,6 +18,19 @@ async function bootstrap(): Promise { // disable setting the etag header app.getHttpAdapter().getInstance().set('etag', false); + // Add signal handlers + process.on('SIGINT', async () => { + console.log('Received SIGINT. Graceful shutdown...'); + await app.close(); + process.exit(0); + }); + + process.on('SIGTERM', async () => { + console.log('Received SIGTERM. Graceful shutdown...'); + await app.close(); + process.exit(0); + }); + await app.listen(getPort()); } @@ -32,4 +45,8 @@ function getPort(): number { return DEFAULT_PORT; } -bootstrap(); +if (process.argv.includes('--smoke')) { + console.log('Smoke test successful!'); +} else { + bootstrap(); +} From 149eefc6ed3a4ca9f20f820c481c65f8642786af Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 16:15:04 +0100 Subject: [PATCH 45/54] logger, port --- api-node/package.json | 3 +++ api-node/src/app.module.ts | 10 ++++++++-- api-node/src/common/port.ts | 12 ++++++++++++ api-node/src/common/registry.service.ts | 20 +++++++++++--------- api-node/src/main.ts | 14 +------------- 5 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 api-node/src/common/port.ts diff --git a/api-node/package.json b/api-node/package.json index e6d4ebd77..7d6b97fe3 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -27,6 +27,9 @@ "python-api:build": "docker build -t registry-api-server ../", "python-api:start": "docker start $(docker ps -aq --filter ancestor=registry-api-server) && echo 'Flask API started successfully' || (docker run -d -p 5031:5030 registry-api-server && echo 'Flask API started successfully')", "python-api:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-server)", + "docker:build": "docker build -t registry-api-nestjs-server -f Dockerfile.nestjs .", + "docker:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-nestjs-server)", + "docker:run": "docker run -p 5032:5030 registry-api-nestjs-server", "start:all": "yarn build && yarn start:dev & yarn python-api:start", "sentry:sourcemaps": "sentry-cli sourcemaps inject --org sentry --project release-registry-nestjs ./dist && sentry-cli sourcemaps upload --org sentry --project release-registry-nestjs ./dist" }, diff --git a/api-node/src/app.module.ts b/api-node/src/app.module.ts index de24e5c1c..dd659bcd4 100644 --- a/api-node/src/app.module.ts +++ b/api-node/src/app.module.ts @@ -1,4 +1,4 @@ -import { Module, Provider } from '@nestjs/common'; +import { Logger, Module, Provider } from '@nestjs/common'; import { PackagesController } from './packages/packages.controller'; import { HealthCheckController } from './health/healthCheck.controller'; import { MarketingController } from './marketing/marketing.controller'; @@ -12,6 +12,7 @@ import { CacheModule } from '@nestjs/cache-manager'; import { CACHE_DEFAULT_SETTINGS } from './common/cache'; import { AppVersionInterceptor } from './apps/appVersion.interceptor'; import { HttpClientExceptionFilter } from './common/httpClient.exceptionFilter'; +import { getPort } from './common/port'; const providers: Provider[] = [ RegistryService, @@ -38,4 +39,9 @@ const providers: Provider[] = [ ], providers, }) -export class AppModule {} +export class AppModule { + private readonly logger = new Logger(AppModule.name); + constructor() { + this.logger.log(`Server is running on port ${getPort()}`); + } +} diff --git a/api-node/src/common/port.ts b/api-node/src/common/port.ts new file mode 100644 index 000000000..557fe3459 --- /dev/null +++ b/api-node/src/common/port.ts @@ -0,0 +1,12 @@ +const DEFAULT_PORT = 3000; + +export function getPort(): number { + try { + if (process.env.PORT) { + return parseInt(process.env.PORT); + } + } catch (error) { + console.error('Invalid port number', error); + } + return DEFAULT_PORT; +} diff --git a/api-node/src/common/registry.service.ts b/api-node/src/common/registry.service.ts index 4f631e2b3..c52dc1a50 100644 --- a/api-node/src/common/registry.service.ts +++ b/api-node/src/common/registry.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import * as path from 'path'; import * as fs from 'fs'; import type { SdkEntry, Sdks, SdkVersions } from '../sdks/types'; @@ -24,6 +24,8 @@ const NAMESPACE_FILE_MARKER = '__NAMESPACE__'; @Injectable() export class RegistryService { + private readonly logger = new Logger(RegistryService.name); + // SDKs getSdks(strict: boolean = false): Sdks { const sdks: Sdks = {}; @@ -50,7 +52,7 @@ export class RegistryService { } } } catch (error) { - console.error('Error reading SDKs directory:', error); + this.logger.error('Error reading SDKs directory:', error); } return sdks; } @@ -61,7 +63,7 @@ export class RegistryService { const { canonical } = JSON.parse(fs.readFileSync(sdkFilePath, 'utf8')); return this.getPackage(canonical, version); } catch (error) { - console.error('Error reading SDK file:', error); + this.logger.error('Error reading SDK file:', error); } } @@ -105,8 +107,8 @@ export class RegistryService { return semver.parse(a).compare(semver.parse(b)); }); } catch (e) { - console.error(`Failed to read package versions: ${packageName}`); - console.error(e); + this.logger.error(`Failed to read package versions: ${packageName}`); + this.logger.error(e); return []; } } @@ -120,10 +122,10 @@ export class RegistryService { const versionFilePath = path.join(packageDir, `${version}.json`); return JSON.parse(fs.readFileSync(versionFilePath).toString()); } catch (e) { - console.error( + this.logger.error( `Failed to read package ${packageName} for version ${version}`, ); - console.error(e); + this.logger.error(e); return null; } } @@ -146,7 +148,7 @@ export class RegistryService { }, {}); } catch (error) { // Handle error (e.g., log it or throw a custom exception) - console.error('Error reading apps directory:', error); + this.logger.error('Error reading apps directory:', error); } } @@ -217,7 +219,7 @@ export class RegistryService { } } } catch (error) { - console.error('Error reading AWS Lambda Layers directory:', error); + this.logger.error('Error reading AWS Lambda Layers directory:', error); } return layers; diff --git a/api-node/src/main.ts b/api-node/src/main.ts index 62c9b063b..b1d9f2d81 100644 --- a/api-node/src/main.ts +++ b/api-node/src/main.ts @@ -3,8 +3,7 @@ import './instrument'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { AlignHeadersInterceptor } from './common/alignHeaders.interceptor'; - -const DEFAULT_PORT = 3000; +import { getPort } from './common/port'; async function bootstrap(): Promise { const app = await NestFactory.create(AppModule, { @@ -34,17 +33,6 @@ async function bootstrap(): Promise { await app.listen(getPort()); } -function getPort(): number { - try { - if (process.env.PORT) { - return parseInt(process.env.PORT); - } - } catch (error) { - console.error('Invalid port number', error); - } - return DEFAULT_PORT; -} - if (process.argv.includes('--smoke')) { console.log('Smoke test successful!'); } else { From 4b8f95f904c8029b1b59519a59f1539b95e0ed01 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 16:34:31 +0100 Subject: [PATCH 46/54] adjust tests, port, use docker image in test --- .github/workflows/node-api.yml | 2 +- api-node/package.json | 4 ++-- api-node/scripts/run-e2e-tests.ts | 3 ++- api-node/test/utils/makeRequest.ts | 8 ++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/node-api.yml b/.github/workflows/node-api.yml index 7bf63e65f..2683d57bd 100644 --- a/.github/workflows/node-api.yml +++ b/.github/workflows/node-api.yml @@ -116,7 +116,7 @@ jobs: run: yarn python-api:build - name: Build Node API working-directory: ./api-node - run: yarn build:ci + run: yarn docker:build - name: Run E2E tests working-directory: ./api-node run: yarn test:e2e:ci diff --git a/api-node/package.json b/api-node/package.json index 7d6b97fe3..baa37ee36 100644 --- a/api-node/package.json +++ b/api-node/package.json @@ -27,9 +27,9 @@ "python-api:build": "docker build -t registry-api-server ../", "python-api:start": "docker start $(docker ps -aq --filter ancestor=registry-api-server) && echo 'Flask API started successfully' || (docker run -d -p 5031:5030 registry-api-server && echo 'Flask API started successfully')", "python-api:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-server)", - "docker:build": "docker build -t registry-api-nestjs-server -f Dockerfile.nestjs .", + "docker:build": "docker build -t registry-api-nestjs-server -f ../Dockerfile.nestjs ../", + "docker:start": "docker run -p 5032:5030 registry-api-nestjs-server", "docker:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-nestjs-server)", - "docker:run": "docker run -p 5032:5030 registry-api-nestjs-server", "start:all": "yarn build && yarn start:dev & yarn python-api:start", "sentry:sourcemaps": "sentry-cli sourcemaps inject --org sentry --project release-registry-nestjs ./dist && sentry-cli sourcemaps upload --org sentry --project release-registry-nestjs ./dist" }, diff --git a/api-node/scripts/run-e2e-tests.ts b/api-node/scripts/run-e2e-tests.ts index 0ec15db70..d694975f8 100644 --- a/api-node/scripts/run-e2e-tests.ts +++ b/api-node/scripts/run-e2e-tests.ts @@ -5,7 +5,7 @@ import * as childProcess from 'child_process'; -const nodeApi = childProcess.spawn('yarn', ['start:prod']); +const nodeApi = childProcess.spawn('yarn', ['docker:start']); let nodeApiStarted = false; let pythonApiStarted = false; @@ -76,6 +76,7 @@ function cleanup(code: number): void { nodeApi.kill(); pythonApi.kill(); childProcess.execSync('yarn python-api:stop'); + childProcess.execSync('yarn docker:stop'); process.exit(code); } diff --git a/api-node/test/utils/makeRequest.ts b/api-node/test/utils/makeRequest.ts index f8152f575..88e9de095 100644 --- a/api-node/test/utils/makeRequest.ts +++ b/api-node/test/utils/makeRequest.ts @@ -1,8 +1,8 @@ -const PYTHON_API_PORT = 5031; -const API_NODE_PORT = 3000; +const PYTHON_API_TEST_PORT = 5031; +const NODE_API_TEST_PORT = 5032; -export const PYTHON_API_URL = `http://localhost:${PYTHON_API_PORT}`; -export const API_NODE_URL = `http://localhost:${API_NODE_PORT}`; +export const PYTHON_API_URL = `http://localhost:${PYTHON_API_TEST_PORT}`; +export const API_NODE_URL = `http://localhost:${NODE_API_TEST_PORT}`; interface ResponseParts { status: number; From 81995c7bf274077944b42361ea9bca283bda196d Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 17:16:21 +0100 Subject: [PATCH 47/54] test coudbuild of node api --- Dockerfile => Dockerfile.flask | 0 cloudbuild.yaml | 1 + 2 files changed, 1 insertion(+) rename Dockerfile => Dockerfile.flask (100%) diff --git a/Dockerfile b/Dockerfile.flask similarity index 100% rename from Dockerfile rename to Dockerfile.flask diff --git a/cloudbuild.yaml b/cloudbuild.yaml index d679d4052..beb383452 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -2,6 +2,7 @@ steps: - name: 'gcr.io/cloud-builders/docker' args: [ 'build', + '-f', 'Dockerfile.nestjs', '-t', 'us-central1-docker.pkg.dev/$PROJECT_ID/sentry-release-registry/image:latest', '-t', 'us-central1-docker.pkg.dev/$PROJECT_ID/sentry-release-registry/image:$COMMIT_SHA', "--cache-from", 'us-central1-docker.pkg.dev/$PROJECT_ID/sentry-release-registry/image:latest', From 70340c5c73790c9bc8e28aa579aa173c4dd6aebb Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 17:50:25 +0100 Subject: [PATCH 48/54] fix dockerignore and add install dev deps --- .dockerignore | 6 +++--- Dockerfile.nestjs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.dockerignore b/.dockerignore index dc38da2b2..66de9b8f1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,6 +7,6 @@ cloudbuild.yaml Dockerfile Makefile README.md -node_modules -dist -.env +api-node/node_modules +api-node/dist +api-node/.env diff --git a/Dockerfile.nestjs b/Dockerfile.nestjs index 7d6a52cca..ade56704f 100644 --- a/Dockerfile.nestjs +++ b/Dockerfile.nestjs @@ -18,7 +18,7 @@ WORKDIR /work COPY api-node/package.json api-node/yarn.lock api-node/ # Remove dev dependencies -RUN cd api-node && yarn install --production --frozen-lockfile +RUN cd api-node && yarn install --frozen-lockfile COPY . . From 9c8208219f197d40e304819ead1837906a53df5f Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 4 Nov 2024 18:06:05 +0100 Subject: [PATCH 49/54] fix missing dev deps --- Dockerfile.nestjs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Dockerfile.nestjs b/Dockerfile.nestjs index ade56704f..e131a1617 100644 --- a/Dockerfile.nestjs +++ b/Dockerfile.nestjs @@ -1,9 +1,5 @@ FROM node:22-alpine -ENV \ - NODE_ENV=production \ - PORT=5030 - ENV \ REGISTRY_UID=10011 \ REGISTRY_GID=10011 @@ -22,6 +18,12 @@ RUN cd api-node && yarn install --frozen-lockfile COPY . . +# Set to production environment +# Importantly, after installing deps, otherwise dev deps aren't installed +ENV \ + NODE_ENV=production \ + PORT=5030 + WORKDIR /work/api-node # Build the application From 2d869a0722b41c360ee2fa382edc58335d79fc80 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 6 Nov 2024 16:16:26 +0100 Subject: [PATCH 50/54] tmp skip source map upload for docker container for now --- Dockerfile.nestjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.nestjs b/Dockerfile.nestjs index e131a1617..6376d697e 100644 --- a/Dockerfile.nestjs +++ b/Dockerfile.nestjs @@ -27,7 +27,7 @@ ENV \ WORKDIR /work/api-node # Build the application -RUN yarn build +RUN yarn build:ci # Set ownership RUN chown -R registry:registry ./ From 06aeaf6c0cfdb403c583798ae753655263c18863 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 7 Nov 2024 11:11:48 +0100 Subject: [PATCH 51/54] add auth token --- .dockerignore | 1 + Dockerfile.nestjs | 4 +++- cloudbuild.yaml | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 66de9b8f1..0342846f0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,3 +10,4 @@ README.md api-node/node_modules api-node/dist api-node/.env +.env \ No newline at end of file diff --git a/Dockerfile.nestjs b/Dockerfile.nestjs index 6376d697e..5a2421e36 100644 --- a/Dockerfile.nestjs +++ b/Dockerfile.nestjs @@ -27,7 +27,9 @@ ENV \ WORKDIR /work/api-node # Build the application -RUN yarn build:ci +RUN --mount=type=secret,id=SENTRY_AUTH_TOKEN \ + export SENTRY_AUTH_TOKEN=$(cat /run/secrets/SENTRY_AUTH_TOKEN) && \ + yarn build # Set ownership RUN chown -R registry:registry ./ diff --git a/cloudbuild.yaml b/cloudbuild.yaml index beb383452..530a03d03 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -2,6 +2,7 @@ steps: - name: 'gcr.io/cloud-builders/docker' args: [ 'build', + "--secret", "id=SENTRY_AUTH_TOKEN,env=SENTRY_AUTH_TOKEN", '-f', 'Dockerfile.nestjs', '-t', 'us-central1-docker.pkg.dev/$PROJECT_ID/sentry-release-registry/image:latest', '-t', 'us-central1-docker.pkg.dev/$PROJECT_ID/sentry-release-registry/image:$COMMIT_SHA', @@ -25,3 +26,7 @@ steps: images: [ 'us-central1-docker.pkg.dev/$PROJECT_ID/sentry-release-registry/image:$COMMIT_SHA', ] +availableSecrets: + secretManager: + - versionName: projects/294472738882/secrets/release-registry-oauth-token + env: 'SENTRY_AUTH_TOKEN' \ No newline at end of file From fca50e8dd41692e6f1a9598c0a86a2da0aa9d080 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 7 Nov 2024 11:13:58 +0100 Subject: [PATCH 52/54] secretenv --- cloudbuild.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 530a03d03..0f368bef1 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -12,6 +12,7 @@ steps: '.' ] env: [DOCKER_BUILDKIT=1] + secretEnv: ['SENTRY_AUTH_TOKEN'] # Only tag "latest" when on master - name: 'gcr.io/cloud-builders/docker' From 811efa7c3b24660022173ddafadf53a9f98fa807 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 7 Nov 2024 11:16:21 +0100 Subject: [PATCH 53/54] maybe add version helps? --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 0f368bef1..7aa5b2261 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -29,5 +29,5 @@ images: [ ] availableSecrets: secretManager: - - versionName: projects/294472738882/secrets/release-registry-oauth-token + - versionName: projects/294472738882/secrets/release-registry-oauth-token/versions/1 env: 'SENTRY_AUTH_TOKEN' \ No newline at end of file From bdd3e113074a66e4ab36d7d2157272a48dab5042 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 7 Nov 2024 12:51:28 +0100 Subject: [PATCH 54/54] swap secret versionName --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 7aa5b2261..f7aa6cd5b 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -29,5 +29,5 @@ images: [ ] availableSecrets: secretManager: - - versionName: projects/294472738882/secrets/release-registry-oauth-token/versions/1 + - versionName: projects/345757944225/secrets/release-registry-oauth-token/versions/1 env: 'SENTRY_AUTH_TOKEN' \ No newline at end of file