From 3e3b1c46fc80dc86cd2596f7788aadd9f04be21e Mon Sep 17 00:00:00 2001 From: Chip Wasson Date: Mon, 18 Sep 2023 17:39:14 -0600 Subject: [PATCH] Improve parkio, add domainrproxy --- package.json | 3 + src/app.module.ts | 6 +- src/app.service.ts | 2 +- src/auth/auth.controller.ts | 2 + src/config/configuration.ts | 3 + src/domainrproxy/domainrproxy.controller.ts | 20 ++++ src/domainrproxy/domainrproxy.module.ts | 9 ++ src/domainrproxy/domainrproxy.service.ts | 48 +++++++++ src/main.ts | 8 +- src/parkio/parkio.controller.ts | 60 +++++++----- src/parkio/parkio.service.ts | 103 +++++++++++++++++--- views/parkio/dailyreport.hbs | 37 +++++++ yarn.lock | 69 ++++++++++++- 13 files changed, 327 insertions(+), 43 deletions(-) create mode 100644 src/domainrproxy/domainrproxy.controller.ts create mode 100644 src/domainrproxy/domainrproxy.module.ts create mode 100644 src/domainrproxy/domainrproxy.service.ts create mode 100644 views/parkio/dailyreport.hbs diff --git a/package.json b/package.json index d4354fd..7f7a211 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { + "@nestjs/cache-manager": "^2.1.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.0.0", @@ -28,6 +29,8 @@ "@nestjs/swagger": "^7.1.11", "axios": "^1.5.0", "bcrypt": "^5.1.1", + "cache-manager": "^5.2.3", + "hbs": "^4.2.0", "minio": "^7.1.3", "ramda": "^0.29.0", "reflect-metadata": "^0.1.13", diff --git a/src/app.module.ts b/src/app.module.ts index e6adf9c..85b6549 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -6,18 +6,22 @@ import { IswordModule } from './isword/isword.module'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { ConfigModule } from '@nestjs/config'; +import { DomainrproxyModule } from './domainrproxy/domainrproxy.module'; import configuration from './config/configuration'; +import { CacheModule } from '@nestjs/cache-manager'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, - load: [configuration] + load: [configuration], }), + CacheModule.register({ isGlobal: true }), ParkioModule, IswordModule, AuthModule, UsersModule, + DomainrproxyModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/app.service.ts b/src/app.service.ts index 927d7cc..f3a33b6 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -3,6 +3,6 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { - return 'Hello World!'; + return "Hello World! This is Chip's generalized API for fetching information and things. You can contact me on mastodon @chip@talking.dev."; } } diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index a00ac69..8183d69 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -10,6 +10,7 @@ import { } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthGuard } from './auth.guard'; +import { ApiBearerAuth } from '@nestjs/swagger'; @Controller('auth') export class AuthController { @@ -28,6 +29,7 @@ export class AuthController { } @UseGuards(AuthGuard) + @ApiBearerAuth() @Get('profile') getProfile(@Request() req: any) { return req.user; diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 35b18cc..8e652a3 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -1,3 +1,6 @@ export default () => ({ port: parseInt(process.env.PORT ?? '', 10) || 3000, + // UserAgent should be added to calls made to third party apis + userAgent: 'api.us.dev/@chip@talking.dev', + rapidApiKey: process.env.RAPID_API_KEY || '', }); diff --git a/src/domainrproxy/domainrproxy.controller.ts b/src/domainrproxy/domainrproxy.controller.ts new file mode 100644 index 0000000..92e00f0 --- /dev/null +++ b/src/domainrproxy/domainrproxy.controller.ts @@ -0,0 +1,20 @@ +import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; +import { DomainrproxyService } from './domainrproxy.service'; +import { AuthGuard } from 'src/auth/auth.guard'; + +@Controller('domainrproxy') +export class DomainrproxyController { + constructor(private readonly proxyService: DomainrproxyService) {} + + @UseGuards(AuthGuard) + @Get(':domain') + queryDomain(@Param('domain') domain: string) { + return this.proxyService.queryForDomain(domain); + } + + @UseGuards(AuthGuard) + @Post('search') + search(@Body() body: { query: string }) { + return this.proxyService.search(body.query); + } +} diff --git a/src/domainrproxy/domainrproxy.module.ts b/src/domainrproxy/domainrproxy.module.ts new file mode 100644 index 0000000..9878dea --- /dev/null +++ b/src/domainrproxy/domainrproxy.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { DomainrproxyService } from './domainrproxy.service'; +import { DomainrproxyController } from './domainrproxy.controller'; + +@Module({ + providers: [DomainrproxyService], + controllers: [DomainrproxyController] +}) +export class DomainrproxyModule {} diff --git a/src/domainrproxy/domainrproxy.service.ts b/src/domainrproxy/domainrproxy.service.ts new file mode 100644 index 0000000..20330fa --- /dev/null +++ b/src/domainrproxy/domainrproxy.service.ts @@ -0,0 +1,48 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import axios from 'axios'; + +@Injectable() +export class DomainrproxyService { + private readonly logger = new Logger(DomainrproxyService.name); + constructor(private readonly configService: ConfigService) {} + + queryForDomain = async (domain: string) => { + this.logger.verbose(`Handling domainr query for domain ${domain}`); + const options = { + method: 'GET', + url: 'https://domainr.p.rapidapi.com/v2/status', + params: { + domain, + 'mashape-key': this.configService.get('rapidApiKey'), + }, + headers: { + 'X-RapidAPI-Key': this.configService.get('rapidApiKey'), + 'X-RapidAPI-Host': 'domainr.p.rapidapi.com', + }, + }; + + const result = await axios.request(options); + return result.data; + }; + + search = async (query: string) => { + this.logger.verbose(`Handling domainr search for term ${query}`); + const options = { + method: 'GET', + url: 'https://domainr.p.rapidapi.com/v2/search', + params: { + query, + defaults: 'com,net,org,io,sh,wtf', + 'mashape-key': this.configService.get('rapidApiKey'), + }, + headers: { + 'X-RapidAPI-Key': this.configService.get('rapidApiKey'), + 'X-RapidAPI-Host': 'domainr.p.rapidapi.com', + }, + }; + + const result = await axios.request(options); + return result.data; + }; +} diff --git a/src/main.ts b/src/main.ts index b7c7a5c..4ac0115 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,19 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { join } from 'path'; async function bootstrap() { - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule); + app.useStaticAssets(join(__dirname, '..', 'public')); + app.setBaseViewsDir(join(__dirname, '..', 'views')); + app.setViewEngine('hbs'); const config = new DocumentBuilder() .setTitle('us.dev API') .setDescription("Chip's generalized API") .setVersion('1.0') + .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); diff --git a/src/parkio/parkio.controller.ts b/src/parkio/parkio.controller.ts index 8534fb1..552e031 100644 --- a/src/parkio/parkio.controller.ts +++ b/src/parkio/parkio.controller.ts @@ -1,35 +1,47 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Controller, Get, Param, Render, Res } from '@nestjs/common'; import { ParkioService } from './parkio.service'; import { ApiTags } from '@nestjs/swagger'; +import { ParsedDomain } from './types'; @ApiTags('parkio') @Controller('parkio') export class ParkioController { - constructor( - private readonly parkioService: ParkioService - ) {} + constructor(private readonly parkioService: ParkioService) {} - @Get('auctions') - async getAuctions () { - return this.parkioService.fetchAuctions(); - } + @Get('auctions') + async getAuctions() { + return this.parkioService.fetchAuctions(); + } - @Get('domains') - async getDomains () { - return this.parkioService.fetchAllDomains(); - } + @Get('domains') + async getDomains(): Promise { + return this.parkioService.fetchAllDomains(); + } - @Get('short/:maxLength') - async getShort (@Param('maxLength') maxLength: number) { - return { - maxLength, - domains: (await this.parkioService.fetchAllDomains()).filter( - parsedDomain => parsedDomain.domain_length <= maxLength - ), - auctions: (await this.parkioService.fetchAuctions()).filter( - parsedAuction => parsedAuction.domain_length <= maxLength - ), - } + @Get('short/:maxLength') + async getShort(@Param('maxLength') maxLength: number) { + return { + maxLength, + domains: (await this.parkioService.fetchAllDomains()).filter( + (parsedDomain) => parsedDomain.domain_length <= maxLength, + ), + auctions: (await this.parkioService.fetchAuctions()).filter( + (parsedAuction) => parsedAuction.domain_length <= maxLength, + ), + }; + } - } + @Get('report') + @Render('parkio/dailyreport') + async getReport() { + const source = { + auctions: await this.parkioService.fetchAuctions(), + domains: await this.parkioService.fetchAllDomains(), + }; + return { + ...source, + day: new Date().toLocaleDateString(), + wordDomains: source.domains.filter((domain) => domain.is_word), + }; + } } diff --git a/src/parkio/parkio.service.ts b/src/parkio/parkio.service.ts index fcb47c9..da59bbe 100644 --- a/src/parkio/parkio.service.ts +++ b/src/parkio/parkio.service.ts @@ -1,51 +1,124 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Logger } from '@nestjs/common'; import { Auction, AuctionsResponse, Domain, - DomainSet, DomainsResponse, ParkioTld, ParsedAuction, ParsedDomain, + TLDs, } from './types'; import axios from 'axios'; import { filter, map, pipe } from 'ramda'; import { IswordService } from 'src/isword/isword.service'; +import { ConfigService } from '@nestjs/config'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { Cache } from 'cache-manager'; const parkIoEndpoints = { domains: 'https://park.io/domains.json', auctions: 'https://park.io/auctions.json', - tld: (tld: ParkioTld) => `https://park.io/domains/index/${tld}.json`, + tld: (tld: ParkioTld, limit: number = 80_000, page: number = 1) => + `https://park.io/domains/index/${tld}/page:${page}.json?limit=${limit}`, all: (limit: number = 10_000) => `https://park.io/domains/index/all.json?limit=${limit}`, }; @Injectable() export class ParkioService { + private readonly logger = new Logger(ParkioService.name); constructor( - private readonly isWordService: IswordService + private configService: ConfigService, + private readonly isWordService: IswordService, + @Inject(CACHE_MANAGER) private cacheManager: Cache, ) {} - fetchDomainsForTld = async (tld: ParkioTld) => {}; + tldRawCacheKey = (tld: ParkioTld) => `${tld}_raw`; - fetchAllDomains = async (limit: number = 10_000): Promise => { - let response = await axios.get(parkIoEndpoints.all(limit)); + setTldDomainsCache = async ( + tld: ParkioTld, + domains: ParsedDomain[], + ): Promise => + this.cacheManager.set(this.tldRawCacheKey(tld), domains, 300_000); - // There may have been more than our set limit - if (response.data.current === limit) { - return await this.fetchAllDomains(limit + 200); + getTldDomainsCache = (tld: ParkioTld): Promise => + this.cacheManager.get(this.tldRawCacheKey(tld)); + + fetchDomainsForTld = async (tld: ParkioTld): Promise => { + this.logger.verbose(`Fetching domains for ${tld}`); + const cacheValue = await this.getTldDomainsCache(tld); + if (cacheValue !== undefined) { + this.logger.verbose(`${tld} ... cache hit`); + return cacheValue; } - - return map(this.parseDomain, response.data.domains); + this.logger.verbose(`${tld} ... cache miss`); + // Recursively fetch domain + const results = await this.fetchDomainPageForTld(tld); + // Fire the combined results into the cache + this.setTldDomainsCache(tld, results); + return results; }; + fetchDomainPageForTld = async ( + tld: ParkioTld, + pageLimit: number = 1_000, + page: number = 1, + ): Promise => { + this.logger.verbose(`Fetching ${pageLimit} .${tld} domains, page ${page}`); + + const url = parkIoEndpoints.tld(tld, pageLimit, page); + const response = await axios.get(url, { + headers: { + Accept: 'application/json', + 'User-Agent': this.configService.get('userAgent'), + }, + }); + + // There may have been more than our set limit + if (response.data.nextPage) { + return [ + ...response.data.domains.map(this.parseDomain), + ...(await this.fetchDomainPageForTld(tld, pageLimit, page + 1)), + ]; + } + + return response.data.domains.map(this.parseDomain); + }; + + fetchAllDomains = async (): Promise => { + const domainResults = await Promise.all(TLDs.map(this.fetchDomainsForTld)); + return domainResults.reduce((acc, domainResponse) => [ + ...acc, + ...domainResponse, + ]); + }; + + auctionsRawCacheKey = `auctions_raw`; + + setAuctionsCache = async (auctions: ParsedAuction[]): Promise => + this.cacheManager.set(this.auctionsRawCacheKey, auctions); + + getAuctionsCache = async (): Promise => + this.cacheManager.get(this.auctionsRawCacheKey); + fetchAuctions = async (): Promise => { + this.logger.verbose('Fetching auction data'); + const cachedValue = await this.getAuctionsCache(); + if (cachedValue !== undefined) { + this.logger.verbose('... cache hit'); + return cachedValue; + } + this.logger.verbose('... cache miss'); + const response = await axios.get( parkIoEndpoints.auctions, ); - return map(this.parseAuction, response.data.auctions); + this.logger.verbose('Processing auction response'); + const results = map(this.parseAuction, response.data.auctions); + this.setAuctionsCache(results); + return results; }; lengthFilter = (length: number) => (parsedDomain: ParsedDomain) => @@ -80,10 +153,12 @@ export class ParkioService { date_registered: domain.date_registered ? this.parseDate(domain.date_registered) : undefined, - is_word: this.isWordService.isWord(domainName) + is_word: this.isWordService.isWord(domainName), }; }; + parseDomains = map(this.parseDomain); + parseDate = (date: string) => new Date( Date.parse( diff --git a/views/parkio/dailyreport.hbs b/views/parkio/dailyreport.hbs new file mode 100644 index 0000000..11fbf36 --- /dev/null +++ b/views/parkio/dailyreport.hbs @@ -0,0 +1,37 @@ + + + + Daily park.io Report + + +

Daily park.io report for {{day}}

+ + + + + + + + {{#each auctions}} + + + + + + + {{/each}} +
NameCurrent BidBid CountCloses
{{name}}${{price}}{{num_bids}}{{close_date}}
+ + + + + + {{#each wordDomains}} + + + + + {{/each}} +
DomainTLD
{{name}}{{tld}}
+ + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index aab438a..d84bc52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -697,6 +697,11 @@ semver "^7.3.5" tar "^6.1.11" +"@nestjs/cache-manager@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@nestjs/cache-manager/-/cache-manager-2.1.0.tgz#e4dadc4ba9c02c059db4dac5e0b5513466e2895a" + integrity sha512-9kep3a8Mq5cMuXN/anGhSYc0P48CRBXk5wyJJRBFxhNkCH8AIzZF4CASGVDIEMmm3OjVcEUHojjyJwCODS17Qw== + "@nestjs/cli@^10.0.0": version "10.1.17" resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-10.1.17.tgz#c7e90e443e0967be2b12dc912957f89ed4b5c992" @@ -1796,6 +1801,14 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cache-manager@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/cache-manager/-/cache-manager-5.2.3.tgz#b6a8b4469c57fdfdae1deed7f81ea9e057c7eade" + integrity sha512-9OErI8fksFkxAMJ8Mco0aiZSdphyd90HcKiOMJQncSlU1yq/9lHHxrT8PDayxrmr9IIIZPOAEfXuGSD7g29uog== + dependencies: + lodash.clonedeep "^4.5.0" + lru-cache "^9.1.2" + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -2693,6 +2706,11 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreachasync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/foreachasync/-/foreachasync-3.0.0.tgz#5502987dc8714be3392097f32e0071c9dee07cf6" + integrity sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw== + fork-ts-checker-webpack-plugin@8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz#dae45dfe7298aa5d553e2580096ced79b6179504" @@ -2910,6 +2928,18 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +handlebars@4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -2954,6 +2984,14 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hbs@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/hbs/-/hbs-4.2.0.tgz#10e40dcc24d5be7342df9636316896617542a32b" + integrity sha512-dQwHnrfWlTk5PvG9+a45GYpg0VpX47ryKF8dULVd6DtwOE6TEcYQXQ5QM6nyOx/h7v3bvEQbdn19EDAcfUAgZg== + dependencies: + handlebars "4.7.7" + walk "2.3.15" + hexoid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" @@ -3814,6 +3852,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.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -3856,6 +3899,11 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== +lru-cache@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.2.tgz#255fdbc14b75589d6d0e73644ca167a8db506835" + integrity sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ== + macos-release@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.1.tgz#bccac4a8f7b93163a8d163b8ebf385b3c5f55bf9" @@ -3987,7 +4035,7 @@ minimatch@^8.0.2: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.6: +minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -4097,7 +4145,7 @@ negotiator@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: +neo-async@^2.6.0, 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== @@ -5233,6 +5281,11 @@ typescript@^5.1.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +uglify-js@^3.1.4: + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== + uid@2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/uid/-/uid-2.0.2.tgz#4b5782abf0f2feeefc00fa88006b2b3b7af3e3b9" @@ -5315,6 +5368,13 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +walk@2.3.15: + version "2.3.15" + resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.15.tgz#1b4611e959d656426bc521e2da5db3acecae2424" + integrity sha512-4eRTBZljBfIISK1Vnt69Gvr2w/wc3U6Vtrw7qiN5iqYJPH7LElcYh/iU4XWhdCy2dZqv1ToMyYlybDylfG/5Vg== + dependencies: + foreachasync "^3.0.0" + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -5431,6 +5491,11 @@ windows-release@^4.0.0: dependencies: execa "^4.0.2" +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + wrap-ansi@^6.0.1: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"