diff --git a/.eslintignore b/.eslintignore index 4d536eb..29a3217 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,4 @@ # Build output /lib/dist +/examples/*/out diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 1a5745c..6e65fff 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -15,6 +15,10 @@ declare global { TF_NEXTIMAGE_SOURCE_BUCKET?: string; __DEBUG__USE_LOCAL_BUCKET?: string; NEXT_SHARP_PATH?: string; + /** + * Use this endpoint to fetch the Next.js config. + */ + IMAGE_CONFIG_ENDPOINT?: string; } } } diff --git a/lib/fetch-timeout.ts b/lib/fetch-timeout.ts new file mode 100644 index 0000000..ecd0a2e --- /dev/null +++ b/lib/fetch-timeout.ts @@ -0,0 +1,56 @@ +/** + * TODO: Use native AbortController from Node.js, once we upgraded to v16.x + */ +import { AbortController } from 'abort-controller'; +import NodeFetch, { RequestInit, Response } from 'node-fetch'; + +/** + * Fetch with timeout + * @param timeout Timeout in milliseconds + * @param url + * @param etag + * @returns + */ +export async function fetchTimeout( + fetch: typeof NodeFetch, + timeout: number, + url: string, + etag?: string +): Promise { + const controller = new AbortController(); + const timeoutFunc = setTimeout(() => { + controller.abort(); + }, timeout); + + let error: Error | undefined; + let fetchResponse: Response | undefined; + + const params: RequestInit = { signal: controller.signal }; + + // Apply If-None-Match header if etag is present + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match + if (etag) { + params.headers = { + 'If-None-Match': `"${etag}"`, + }; + } + + try { + fetchResponse = await fetch(url, params); + } catch (err: any) { + if (err.name === 'AbortError') { + error = new Error(`Timeout while fetching from ${url}`); + } else { + error = err; + } + } finally { + clearTimeout(timeoutFunc); + } + + if (error) { + throw error; + } + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return fetchResponse!; +} diff --git a/lib/handler.ts b/lib/handler.ts index 897097f..8466e62 100644 --- a/lib/handler.ts +++ b/lib/handler.ts @@ -6,10 +6,8 @@ process.env.NEXT_SHARP_PATH = require.resolve('sharp'); import { parse as parseUrl } from 'url'; -import { - defaultConfig, - NextConfigComplete, -} from 'next/dist/server/config-shared'; +import { ETagCache } from '@millihq/etag-cache'; +import createFetch from '@vercel/fetch-cached-dns'; import type { APIGatewayProxyEventV2, APIGatewayProxyStructuredResultV2, @@ -18,14 +16,15 @@ import type { // eslint-disable-next-line import/no-unresolved } from 'aws-lambda'; import S3 from 'aws-sdk/clients/s3'; +import nodeFetch from 'node-fetch'; import { imageOptimizer, S3Config } from './image-optimizer'; import { normalizeHeaders } from './normalized-headers'; - -/* ----------------------------------------------------------------------------- - * Types - * ---------------------------------------------------------------------------*/ -type ImageConfig = Partial; +import { + fetchImageConfigGenerator, + getImageConfig, + NextImageConfig, +} from './image-config'; /* ----------------------------------------------------------------------------- * Utils @@ -51,64 +50,21 @@ function generateS3Config(bucketName?: string): S3Config | undefined { }; } -function parseFromEnv(key: string, defaultValue: T) { - try { - const envValue = process.env[key]; - if (typeof envValue === 'string') { - return JSON.parse(envValue) as T; - } - - return defaultValue; - } catch (err) { - console.error(`Could not parse ${key} from environment variable`); - console.error(err); - return defaultValue; - } -} - /* ----------------------------------------------------------------------------- * Globals * ---------------------------------------------------------------------------*/ -// `images` property is defined on default config -// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -const imageConfigDefault = defaultConfig.images!; - -const domains = parseFromEnv( - 'TF_NEXTIMAGE_DOMAINS', - imageConfigDefault.domains ?? [] -); -const deviceSizes = parseFromEnv( - 'TF_NEXTIMAGE_DEVICE_SIZES', - imageConfigDefault.deviceSizes -); -const formats = parseFromEnv( - 'TF_NEXTIMAGE_FORMATS', - imageConfigDefault.formats -); -const imageSizes = parseFromEnv( - 'TF_NEXTIMAGE_IMAGE_SIZES', - imageConfigDefault.imageSizes -); -const dangerouslyAllowSVG = parseFromEnv( - 'TF_NEXTIMAGE_DANGEROUSLY_ALLOW_SVG', - imageConfigDefault.dangerouslyAllowSVG -); -const contentSecurityPolicy = parseFromEnv( - 'TF_NEXTIMAGE_CONTENT_SECURITY_POLICY', - imageConfigDefault.contentSecurityPolicy -); + const sourceBucket = process.env.TF_NEXTIMAGE_SOURCE_BUCKET ?? undefined; const baseOriginUrl = process.env.TF_NEXTIMAGE_BASE_ORIGIN ?? undefined; -const imageConfig: ImageConfig = { - ...imageConfigDefault, - domains, - deviceSizes, - formats, - imageSizes, - dangerouslyAllowSVG, - contentSecurityPolicy, -}; +/** + * We use a custom fetch implementation here that caches DNS resolutions + * to improve performance for repeated requests. + */ +// eslint-disable-next-line +const fetch = createFetch(); +const fetchImageConfig = fetchImageConfigGenerator(fetch as typeof nodeFetch); +const configCache = new ETagCache(60, fetchImageConfig); /* ----------------------------------------------------------------------------- * Handler @@ -117,18 +73,16 @@ const imageConfig: ImageConfig = { export async function handler( event: APIGatewayProxyEventV2 ): Promise { + const headers = normalizeHeaders(event.headers); + const hostname = headers['host']; + const imageConfig = await getImageConfig({ configCache, hostname }); const s3Config = generateS3Config(sourceBucket); - const parsedUrl = parseUrl(`/?${event.rawQueryString}`, true); - const imageOptimizerResult = await imageOptimizer( - { headers: normalizeHeaders(event.headers) }, - imageConfig, - { - baseOriginUrl, - parsedUrl, - s3Config, - } - ); + const imageOptimizerResult = await imageOptimizer({ headers }, imageConfig, { + baseOriginUrl, + parsedUrl, + s3Config, + }); if ('error' in imageOptimizerResult) { return { diff --git a/lib/image-config.ts b/lib/image-config.ts new file mode 100644 index 0000000..fc4b244 --- /dev/null +++ b/lib/image-config.ts @@ -0,0 +1,148 @@ +import { ETagCache } from '@millihq/etag-cache'; +import { + defaultConfig, + NextConfigComplete, +} from 'next/dist/server/config-shared'; +import { fetchTimeout } from './fetch-timeout'; + +type NextImageConfig = Partial; +type NodeFetch = typeof import('node-fetch').default; + +/* ----------------------------------------------------------------------------- + * Default Configuration + * ---------------------------------------------------------------------------*/ + +// Timeout the connection before 30000ms to be able to print an error message +// See Lambda@Edge Limits for origin-request event here: +// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-lambda-at-edge +const FETCH_TIMEOUT = 29500; + +// `images` property is defined on default config +// eslint-disable-next-line @typescript-eslint/no-non-null-assertion +const defaultImageConfig = defaultConfig.images!; + +const domains = parseFromEnv( + 'TF_NEXTIMAGE_DOMAINS' +); +const deviceSizes = parseFromEnv( + 'TF_NEXTIMAGE_DEVICE_SIZES' +); +const formats = parseFromEnv( + 'TF_NEXTIMAGE_FORMATS' +); +const imageSizes = parseFromEnv( + 'TF_NEXTIMAGE_IMAGE_SIZES' +); +const dangerouslyAllowSVG = parseFromEnv< + typeof defaultImageConfig.dangerouslyAllowSVG +>('TF_NEXTIMAGE_DANGEROUSLY_ALLOW_SVG'); +const contentSecurityPolicy = parseFromEnv< + typeof defaultImageConfig.contentSecurityPolicy +>('TF_NEXTIMAGE_CONTENT_SECURITY_POLICY'); + +const imageConfigEndpoint = process.env.IMAGE_CONFIG_ENDPOINT; + +function parseFromEnv(key: string): T | undefined { + try { + const envValue = process.env[key]; + if (typeof envValue === 'string') { + return JSON.parse(envValue) as T; + } + + return undefined; + } catch (err) { + console.error(`Could not parse ${key} from environment variable`); + console.error(err); + return undefined; + } +} + +/* ----------------------------------------------------------------------------- + * fetchImageConfig + * ---------------------------------------------------------------------------*/ +function fetchImageConfigGenerator(fetch: NodeFetch) { + return async function fetchImageConfig( + hostname: string, + eTag?: string + ): Promise<{ item?: NextImageConfig; eTag: string } | null> { + const url = imageConfigEndpoint + '/' + hostname; + const response = await fetchTimeout(fetch, FETCH_TIMEOUT, url, eTag); + + // Existing cache is still valid + if (response.status === 304 && eTag) { + return { + eTag, + }; + } + + if (response.status === 200) { + const nextConfig = (await response.json()) as NextConfigComplete; + // Etag is always present on CloudFront responses + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const responseEtag = response.headers.get('etag')!; + + return { + eTag: responseEtag, + item: nextConfig.images, + }; + } + + return null; + }; +} + +/* ----------------------------------------------------------------------------- + * getImageConfig + * ---------------------------------------------------------------------------*/ + +/** + * Filters undefined values from an object + * @param obj + * @returns + */ +function filterObject(obj: { [key: string]: any }) { + const ret: { [key: string]: any } = {}; + Object.keys(obj) + .filter((key) => obj[key] !== undefined) + .forEach((key) => (ret[key] = obj[key])); + return ret; +} + +type GetImageConfigOptions = { + configCache: ETagCache; + hostname: string; +}; + +async function getImageConfig({ + configCache, + hostname, +}: GetImageConfigOptions): Promise { + let externalConfig: NextImageConfig | undefined; + + // Use external Config + if (imageConfigEndpoint) { + externalConfig = await configCache.get(hostname); + } + + // Generate config object by merging the items in the following order: + // 1. defaultConfig + // 2. externalConfig (if exists) + // 3. vales set from environment variables + const imageConfig: NextImageConfig = { + ...defaultConfig, + ...externalConfig, + ...filterObject({ + domains, + deviceSizes, + formats, + imageSizes, + dangerouslyAllowSVG, + contentSecurityPolicy, + }), + }; + + return imageConfig; +} + +export { fetchImageConfigGenerator, getImageConfig }; +export type { NextImageConfig }; diff --git a/lib/package.json b/lib/package.json index 161d2a6..1b8f3f0 100644 --- a/lib/package.json +++ b/lib/package.json @@ -16,7 +16,10 @@ "postpack": "rm ./LICENSE ./third-party-licenses.txt" }, "dependencies": { + "@millihq/etag-cache": "^1.1.0", "@millihq/pixel-core": "4.2.0", + "@vercel/fetch-cached-dns": "^2.1.0", + "abort-controller": "3.0.0", "aws-sdk": "*", "next": "12.1.4", "node-fetch": "2.6.7", @@ -25,11 +28,11 @@ }, "devDependencies": { "@types/aws-lambda": "8.10.56", - "@types/node-fetch": "^2.5.7", + "@types/node-fetch": "^2.6.2", "@types/react": "^17.0.3", "@types/react-dom": "^17.0.3", "glob": "^7.1.6", - "typescript": "^4.1.3" + "typescript": "*" }, "files": [ "dist.zip", diff --git a/package.json b/package.json index 607e680..a4ab6dd 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,11 @@ "@millihq/sammy": "^2.0.1", "@ofhouse/keep-a-changelog": "2.3.0-no-increment-fix", "@release-it/bumper": "^3.0.1", - "@tsconfig/node14": "^1.0.1", + "@tsconfig/node14": "^1.0.3", "@types/jest": "^27.0.1", "@types/mime-types": "^2.1.1", "@types/node": "^14.0.0", - "@types/node-fetch": "^2.5.10", + "@types/node-fetch": "^2.6.2", "@typescript-eslint/eslint-plugin": "^4.30.0", "@typescript-eslint/parser": "^4.30.0", "@vercel/nft": "0.17.5", @@ -44,10 +44,11 @@ "release-it": "^14.12.3", "release-it-yarn-workspaces": "^2.0.1", "ts-jest": "^27.0.5", - "typescript": "^4.6.3" + "typescript": "*" }, "resolutions": { - "aws-sdk": "2.1001.0" + "aws-sdk": "2.1001.0", + "typescript": "4.7.4" }, "release-it": { "git": { diff --git a/variables.tf b/variables.tf index 2e2d744..14b3858 100644 --- a/variables.tf +++ b/variables.tf @@ -13,42 +13,6 @@ variable "next_image_base_origin" { default = null } -variable "next_image_domains" { - description = "Allowed origin domains that can be used for fetching images." - type = list(string) - default = [] -} - -variable "next_image_device_sizes" { - description = "Allowed device sizes that should be used for image optimization." - type = list(number) - default = null -} - -variable "next_image_formats" { - description = "If the Accept head matches more than one of the configured formats, the first match in the array is used. Therefore, the array order matters. If there is no match, the Image Optimization API will fallback to the original image's format." - type = list(string) - default = ["image/webp"] -} - -variable "next_image_image_sizes" { - description = "Allowed image sizes that should be used for image optimization." - type = list(number) - default = null -} - -variable "next_image_dangerously_allow_SVG" { - description = "Enable the optimization of SVG images." - type = bool - default = false -} - -variable "next_image_content_security_policy" { - description = "Set the value of the Content-Security-Policy header in the response of the image optimizer." - type = string - default = null -} - variable "lambda_memory_size" { description = "Amount of memory in MB the worker Lambda Function can use. Valid value between 128 MB to 10,240 MB, in 1 MB increments." type = number @@ -95,6 +59,58 @@ variable "source_bucket_id" { default = null } +variable "nextjs_config_endpoint_url" { + description = "Endpoint that could be used to retrieve a Next.js config from (Allows to use different configs depending on the hostname). Should accept requests of the format https://example.com/.../{endpoint}." + type = string + default = null +} + +######################## +# Next.js image settings +######################## + +# See: https://nextjs.org/docs/api-reference/next/image#domains +variable "next_image_domains" { + description = "Allowed origin domains that can be used for fetching images." + type = list(string) + default = [] +} + +# See: https://nextjs.org/docs/api-reference/next/image#device-sizes +variable "next_image_device_sizes" { + description = "Allowed device sizes that should be used for image optimization." + type = list(number) + default = null +} + +# See: https://nextjs.org/docs/api-reference/next/image#acceptable-formats +variable "next_image_formats" { + description = "If the Accept head matches more than one of the configured formats, the first match in the array is used. Therefore, the array order matters. If there is no match, the Image Optimization API will fallback to the original image's format." + type = list(string) + default = ["image/webp"] +} + +# See: https://nextjs.org/docs/api-reference/next/image#image-sizes +variable "next_image_image_sizes" { + description = "Allowed image sizes that should be used for image optimization." + type = list(number) + default = null +} + +# See: https://nextjs.org/docs/api-reference/next/image#dangerously-allow-svg +variable "next_image_dangerously_allow_SVG" { + description = "Enable the optimization of SVG images." + type = bool + default = false +} + +# See: https://nextjs.org/docs/api-reference/next/image#dangerously-allow-svg +variable "next_image_content_security_policy" { + description = "Set the value of the Content-Security-Policy header in the response of the image optimizer." + type = string + default = null +} + ##################### # CloudFront settings ##################### diff --git a/yarn.lock b/yarn.lock index 92e90e0..94e2443 100644 --- a/yarn.lock +++ b/yarn.lock @@ -775,6 +775,11 @@ semver "^7.3.4" tar "^6.1.0" +"@millihq/etag-cache@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@millihq/etag-cache/-/etag-cache-1.1.0.tgz#5bff66a278ed5a7ff755091a382ea069f0dddf3d" + integrity sha512-mQl1JfXfGlboOwvuoj+cMuvgOZWBm3Fx7kYemcy7GHrH7HGa5yTzXsmtQNAdXcpyDH/0Fgo99od00wlrcxjAvA== + "@millihq/pixel-core@4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@millihq/pixel-core/-/pixel-core-4.2.0.tgz#977cddef9a768a4255272e9e22fba4f328899382" @@ -1048,10 +1053,15 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@tsconfig/node14@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== +"@tsconfig/node14@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@types/async-retry@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/async-retry/-/async-retry-1.2.1.tgz#fa9ac165907a8ee78f4924f4e393b656c65b5bb4" + integrity sha512-yMQ6CVgICWtyFNBqJT3zqOc+TnqqEPLo4nKJNPFwcialiylil38Ie6q1ENeFTjvaLOkVim9K5LisHgAKJWidGQ== "@types/aws-lambda@8.10.56": version "8.10.56" @@ -1168,6 +1178,11 @@ dependencies: "@types/node" "*" +"@types/lru-cache@4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-4.1.1.tgz#b2d87a5e3df8d4b18ca426c5105cd701c2306d40" + integrity sha512-8mNEUG6diOrI6pMqOHrHPDBB1JsrpedeMK9AWGzVCQ7StRRribiT9BRvUmF8aUws9iBbVlgVekOT5Sgzc1MTKw== + "@types/mime-types@^2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.1.tgz#d9ba43490fa3a3df958759adf69396c3532cf2c1" @@ -1178,18 +1193,10 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== -"@types/node-fetch@^2.5.10": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132" - integrity sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - -"@types/node-fetch@^2.5.7": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" - integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw== +"@types/node-fetch@^2.6.1", "@types/node-fetch@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" + integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== dependencies: "@types/node" "*" form-data "^3.0.0" @@ -1199,6 +1206,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.34.tgz#07935194fc049069a1c56c0c274265abeddf88da" integrity sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA== +"@types/node@10.12.18": + version "10.12.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" + integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ== + "@types/node@^14.0.0": version "14.14.37" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e" @@ -1340,6 +1352,14 @@ "@typescript-eslint/types" "4.30.0" eslint-visitor-keys "^2.0.0" +"@vercel/fetch-cached-dns@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@vercel/fetch-cached-dns/-/fetch-cached-dns-2.1.0.tgz#15332b49cbeeb476eecbed0d3660e76808b3365a" + integrity sha512-dIQWF+bG2EOYeCCCeT3E77qZZa7VgW2quEKw4k8/keeoD8lRMjiNi//Ww7LJ8wXecfv7XXtprwN5uLLLGo/ktg== + dependencies: + "@types/node-fetch" "^2.6.1" + "@zeit/dns-cached-resolve" "^2.1.2" + "@vercel/nft@0.17.5": version "0.17.5" resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.17.5.tgz#eab288a3786b8bd6fc08c0ef0b70d162984d1643" @@ -1357,6 +1377,17 @@ resolve-from "^5.0.0" rollup-pluginutils "^2.8.2" +"@zeit/dns-cached-resolve@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@zeit/dns-cached-resolve/-/dns-cached-resolve-2.1.2.tgz#2c2e33d682d67f94341c9a06ac0e2a8f14ff035f" + integrity sha512-A/5gbBskKPETTBqHwvlaW1Ri2orO62yqoFoXdxna1SQ7A/lXjpWgpJ1wdY3IQEcz5LydpS4sJ8SzI2gFyyLEhg== + dependencies: + "@types/async-retry" "1.2.1" + "@types/lru-cache" "4.1.1" + "@types/node" "10.12.18" + async-retry "1.2.3" + lru-cache "5.1.1" + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -1367,6 +1398,13 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abort-controller@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" @@ -1577,6 +1615,13 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +async-retry@1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.2.3.tgz#a6521f338358d322b1a0012b79030c6f411d1ce0" + integrity sha512-tfDb02Th6CE6pJUF2gjW5ZVjsgwlucVXOEQMvEX9JgSJMs9gAX+Nz3xRuJBKuUYjTSYORqvDBORdAQ3LU59g7Q== + dependencies: + retry "0.12.0" + async-retry@1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" @@ -2726,6 +2771,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -4434,6 +4484,13 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +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" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -5565,6 +5622,11 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +retry@0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + retry@0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" @@ -6300,15 +6362,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.1.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" - integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== - -typescript@^4.6.3: - version "4.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" - integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== +typescript@*, typescript@4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== unbox-primitive@^1.0.0, unbox-primitive@^1.0.1: version "1.0.1" @@ -6647,7 +6704,7 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^3.0.0, yallist@^3.1.1: +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==