From 10f9eaf6b01fbd8c8cfea3f9514d2e22694253ec Mon Sep 17 00:00:00 2001 From: David Tanner Date: Thu, 29 Sep 2022 09:59:25 -0600 Subject: [PATCH] feat: Set up proxy to local lambda handler file --- .github/workflows/pr-branch-build.yml | 2 +- .github/workflows/release.yaml | 2 +- README.md | 25 +++++--- package.json | 9 ++- src/alphaProxy.ts | 3 + src/cli.ts | 7 ++- src/plugins/lambda-handler.ts | 50 +++++++++++++++ src/types.ts | 2 + test/lambdaHandlers.test.ts | 32 ++++++++++ test/lambdaHandlers/exportApp.ts | 21 +++++++ test/lambdaHandlers/exportHandler.ts | 19 ++++++ test/plugins/lambda-handler.test.ts | 91 +++++++++++++++++++++++++++ yarn.lock | 78 +++++++++++++++++++++-- 13 files changed, 323 insertions(+), 18 deletions(-) create mode 100644 src/plugins/lambda-handler.ts create mode 100644 test/lambdaHandlers.test.ts create mode 100644 test/lambdaHandlers/exportApp.ts create mode 100644 test/lambdaHandlers/exportHandler.ts create mode 100644 test/plugins/lambda-handler.test.ts diff --git a/.github/workflows/pr-branch-build.yml b/.github/workflows/pr-branch-build.yml index 04d5b95..4e97edb 100644 --- a/.github/workflows/pr-branch-build.yml +++ b/.github/workflows/pr-branch-build.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 cache: yarn - run: yarn install - run: yarn test --testTimeout=120000 --maxWorkers=50% diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 09c467b..23fc15e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 cache: yarn - run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc - run: yarn install --frozen-lockfile diff --git a/README.md b/README.md index 9e145bb..432976d 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,22 @@ $ npm install -g @lifeomic/alpha-cli ```bash $ alpha --help Options: - --help Show help [boolean] - -H, --header Pass custom header line to server - -X, --request Specify the request method to use - --data-binary Send binary data - --proxy Run a local http proxy that passes requests to alpha - --proxy-port The port to run the http proxy on - -V, --version Show the version number and quit [boolean] - --sign Sign requests using aws SignatureV4 [boolean] - --role Use STS to assume a role when signing + --help Show help [boolean] + -H, --header Pass custom header line to server [string] + --lambda-handler A javascript/typescript lambda handler to send requests + to [string] + --env-file File to load as environment variables when importing + lambda [string] + -X, --request Specify the request method to use + --data-binary Send binary data + --proxy-port port to proxy requests on [number] [default: 9000] + --proxy http proxy requests to alpha [boolean] [default: false] + --sign Sign requests with AWS SignatureV4 + [boolean] [default: false] + --role Role to assume when signing [string] + --validate-status Validate the HTTP response code and fail if not 2XX + [boolean] [default: false] + -V, --version Show the version number and quit [boolean] $ alpha lambda://user-service/users/jagoda | jq { diff --git a/package.json b/package.json index c93c0a7..4efd6e4 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ }, "devDependencies": { "@aws-sdk/types": "^3.110.0", + "@koa/router": "^12.0.0", "@lifeomic/eslint-config-standards": "^3.0.0", "@lifeomic/jest-config": "^1.1.3", "@lifeomic/typescript-config": "^1.0.3", @@ -31,8 +32,10 @@ "@swc/jest": "^0.2.21", "@types/glob": "^7.2.0", "@types/jest": "^28.1.3", + "@types/js-yaml": "^4.0.5", "@types/koa": "^2.13.4", "@types/koa-bodyparser": "^4.3.7", + "@types/koa__router": "^12.0.0", "@types/node": "^16", "aws-sdk-client-mock": "^1.0.0", "conventional-changelog-conventionalcommits": "^4.6.3", @@ -41,15 +44,19 @@ "koa": "^2.13.4", "koa-bodyparser": "^4.3.0", "semantic-release": "^19.0.2", + "serverless-http": "^3.0.2", "ts-jest": "^28.0.5", "ts-node": "^10.8.1", - "typescript": "^4.7.4" + "typescript": "^4.7.4", + "ulid": "^2.3.0" }, "dependencies": { "@aws-sdk/client-sts": "^3.121.0", "@lifeomic/alpha": "^5.1.0", "axios": "^0.27.2", + "dotenv": "^16.0.2", "glob": "^8.0.3", + "js-yaml": "^4.1.0", "yargs": "^17.5.1" }, "publishConfig": { diff --git a/src/alphaProxy.ts b/src/alphaProxy.ts index e02b05a..fe197ad 100644 --- a/src/alphaProxy.ts +++ b/src/alphaProxy.ts @@ -16,6 +16,9 @@ export const alphaProxy = (baseConfig: AlphaCliConfig) => { ...req.headers as Record, }, }; + if (requestConfig.lambda) { + requestConfig.url = url; + } let data = ''; diff --git a/src/cli.ts b/src/cli.ts index e19b5bd..ce23431 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -23,7 +23,12 @@ const run = async () => { const args = await yargs.parse(); - const skipRequest = plugins.some((execute) => execute(config, args)); + const results: any[] = []; + for (const plugin of plugins) { + results.push(await plugin(config, args)); + } + + const skipRequest = results.some((next) => next); const { proxied, diff --git a/src/plugins/lambda-handler.ts b/src/plugins/lambda-handler.ts new file mode 100644 index 0000000..97df239 --- /dev/null +++ b/src/plugins/lambda-handler.ts @@ -0,0 +1,50 @@ +import { AlphaCliArguments, AlphaCliConfig } from '../types'; +import { Argv } from 'yargs'; +import { promises as fs } from 'fs'; +import path from 'path'; +import { parse } from 'dotenv'; +import { load } from 'js-yaml'; + +const loadEnvFile = async (envFile: string) => { + const ext = path.extname(envFile).toLowerCase(); + const contents = await fs.readFile(envFile, 'utf-8'); + let newEnv: Record = {}; + if (ext === '.env') { + newEnv = parse(contents); + } else if (ext === '.json') { + newEnv = JSON.parse(contents); + } else if (['.yaml', '.yml'].includes(ext)) { + newEnv = load(contents) as Record; + } else { + throw new Error(`Unable to load ${envFile}, unrecognized extension ${ext}`); + } + Object.assign(process.env, newEnv); +}; + +export default (yargs: Argv) => { + yargs + .option('lambda-handler', { + type: 'string', + describe: 'A javascript/typescript lambda handler to send requests to', + }) + .option('env-file', { + type: 'string', + describe: 'File to load as environment variables when importing lambda', + }); + + return async ( + config: AlphaCliConfig, + { + 'lambda-handler': lambdaHandler, + 'env-file': envFile, + }: AlphaCliArguments, + ) => { + if (lambdaHandler) { + if (envFile) { + await loadEnvFile(envFile); + } + const exported = await import(lambdaHandler); + config.lambda = exported.handler || exported.default.handler; + } + }; +}; diff --git a/src/types.ts b/src/types.ts index 3c164ac..9075094 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,6 +12,8 @@ export interface AlphaCliArguments { 'data-binary'?: any; proxy?: boolean; 'proxy-port'?: number; + 'lambda-handler'?: string; + 'env-file'?: string; 'validate-status'?: boolean; version?: boolean; sign?: boolean; diff --git a/test/lambdaHandlers.test.ts b/test/lambdaHandlers.test.ts new file mode 100644 index 0000000..eec40b4 --- /dev/null +++ b/test/lambdaHandlers.test.ts @@ -0,0 +1,32 @@ +import path from 'path'; +import { getPort, runCommand, spawnProxy } from './utils'; +import { ulid } from 'ulid'; + +describe.each([ + 'exportApp', + 'exportHandler', +])('can send requests to %s', (handlerFile) => { + const param: string = ulid(); + const filePath = path.join(__dirname, 'lambdaHandlers', `${handlerFile}.ts`); + + test('will send request to exported handler', async () => { + const { stdout, stderr } = await runCommand('--lambda-handler', filePath, `/echo/${param}`); + expect(stderr).toBeFalsy(); + expect(stdout).toBe(param); + }); + + test('will proxy requests to the exported handler', async () => { + const proxyPort = await getPort(); + const process = await spawnProxy('--proxy', '--proxy-port', proxyPort, '--lambda-handler', filePath); + + try { + const { stdout, stderr } = await runCommand(`http://127.0.0.1:${proxyPort}/echo/${param}`); + + expect(stdout).toBe(param); + expect(stderr).toBeFalsy(); + } finally { + process.kill(); + } + }); +}); + diff --git a/test/lambdaHandlers/exportApp.ts b/test/lambdaHandlers/exportApp.ts new file mode 100644 index 0000000..aee4854 --- /dev/null +++ b/test/lambdaHandlers/exportApp.ts @@ -0,0 +1,21 @@ +import Koa from 'koa'; +import Router from '@koa/router'; +import serverless from 'serverless-http'; + +const app = new Koa() as Koa & { handler: ReturnType; }; + +const router = new Router(); + +router.get('/echo/:param', (ctx) => { + ctx.body = ctx.params.param; + ctx.status = 200; +}); + +// eslint-disable-next-line @typescript-eslint/no-unsafe-argument +app.use(router.routes()); +// eslint-disable-next-line @typescript-eslint/no-unsafe-argument +app.use(router.allowedMethods()); + +app.handler = serverless(app); + +export default app; diff --git a/test/lambdaHandlers/exportHandler.ts b/test/lambdaHandlers/exportHandler.ts new file mode 100644 index 0000000..8829786 --- /dev/null +++ b/test/lambdaHandlers/exportHandler.ts @@ -0,0 +1,19 @@ +import Koa from 'koa'; +import Router from '@koa/router'; +import serverless from 'serverless-http'; + +const app = new Koa(); + +const router = new Router(); + +router.get('/echo/:param', (ctx) => { + ctx.body = ctx.params.param; + ctx.status = 200; +}); + +// eslint-disable-next-line @typescript-eslint/no-unsafe-argument +app.use(router.routes()); +// eslint-disable-next-line @typescript-eslint/no-unsafe-argument +app.use(router.allowedMethods()); + +export const handler = serverless(app); diff --git a/test/plugins/lambda-handler.test.ts b/test/plugins/lambda-handler.test.ts new file mode 100644 index 0000000..14d0e7e --- /dev/null +++ b/test/plugins/lambda-handler.test.ts @@ -0,0 +1,91 @@ +import path from 'path'; +import yargs from 'yargs'; +import { ulid } from 'ulid'; +import { dump } from 'js-yaml'; +import { promises as fs } from 'fs'; + +import lambdaHandlerPlugin from '../../src/plugins/lambda-handler'; +import { AlphaCliArguments, AlphaCliConfig } from '../../src/types'; + +const buildDir = path.join(__dirname, '..', 'build'); +const lambdaHandler = path.join(__dirname, '..', 'lambdaHandlers', 'exportHandler.ts'); +const pluginFunc = lambdaHandlerPlugin(yargs); + +beforeAll(async () => { + await fs.mkdir(buildDir, { recursive: true }); +}); + +test.each([ + 'exportHandler.ts', + 'exportApp.ts', +])('%s will load the lambda-handler function', async (lambdaHandlerFile) => { + const config: AlphaCliConfig = { + responsePostProcessors: [], + }; + const cliArgs: AlphaCliArguments = { + _: [''], + 'lambda-handler': path.join(__dirname, '..', 'lambdaHandlers', lambdaHandlerFile), + }; + await expect(pluginFunc(config, cliArgs)).resolves.toBeUndefined(); + expect(config).toHaveProperty('lambda', expect.any(Function)); +}); + +test('will throw exception on unknown extension', async () => { + const ext = `.${ulid()}`; + const envFile = path.join(buildDir, `${ulid()}${ext.toUpperCase()}`); + await fs.writeFile(envFile, '', 'utf-8'); + + const config: AlphaCliConfig = { + responsePostProcessors: [], + }; + const cliArgs: AlphaCliArguments = { + _: [''], + 'lambda-handler': lambdaHandler, + 'env-file': envFile, + }; + try { + await expect(pluginFunc(config, cliArgs)).rejects.toThrowError(`Unable to load ${envFile}, unrecognized extension ${ext.toLowerCase()}`); + } finally { + await fs.rm(envFile, { force: true }); + } +}); + +describe.each([ + 'json', + 'yaml', + 'yml', + 'env', +])('will load %s into the process.env', (ext) => { + const envFile = path.join(buildDir, `${ulid()}.${ext}`); + const envVars = { + [ulid()]: ulid(), + }; + beforeAll(async () => { + let envString = ''; + if (ext === 'json') { + envString = JSON.stringify(envVars); + } else if (ext === 'env') { + envString = Object.entries(envVars).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join('\n'); + } else if (['yaml', 'yml'].includes(ext)) { + envString = dump(envVars); + } + + await fs.writeFile(envFile, envString, 'utf-8'); + }); + afterAll(async () => { + await fs.rm(envFile, { force: true }); + }); + + test('will load the env file', async () => { + const config: AlphaCliConfig = { + responsePostProcessors: [], + }; + const cliArgs: AlphaCliArguments = { + _: [''], + 'lambda-handler': lambdaHandler, + 'env-file': envFile, + }; + await expect(pluginFunc(config, cliArgs)).resolves.toBeUndefined(); + expect(process.env).toStrictEqual(expect.objectContaining(envVars)); + }); +}); diff --git a/yarn.lock b/yarn.lock index 89a3f30..499f8c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1289,6 +1289,16 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@koa/router@^12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@koa/router/-/router-12.0.0.tgz#2ae7937093fd392761c0e5833c368379d4a35737" + integrity sha512-cnnxeKHXlt7XARJptflGURdJaO+ITpNkOHmQu7NHmCoRinPbyvFzce/EG/E8Zy81yQ1W9MoSdtklc3nyaDReUw== + dependencies: + http-errors "^2.0.0" + koa-compose "^4.1.0" + methods "^1.1.2" + path-to-regexp "^6.2.1" + "@lifeomic/alpha@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@lifeomic/alpha/-/alpha-5.1.0.tgz#858d7dab94c3250825618cf18c88de1dc90a3d8a" @@ -2030,6 +2040,11 @@ jest-matcher-utils "^28.0.0" pretty-format "^28.0.0" +"@types/js-yaml@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" + integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== + "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -2068,6 +2083,13 @@ "@types/koa-compose" "*" "@types/node" "*" +"@types/koa__router@^12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-12.0.0.tgz#19b0f287f9708267dfd8842feb5f2de407d93af2" + integrity sha512-S6eHyZyoWCZLNHyy8j0sMW85cPrpByCbGGU2/BO4IzGiI87aHJ92lZh4E9xfsM9DcbCT469/OIqyC0sSJXSIBQ== + dependencies: + "@types/koa" "*" + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -3107,16 +3129,16 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +depd@2.0.0, depd@^2.0.0, 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== + depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -depd@^2.0.0, 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== - deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" @@ -3181,6 +3203,11 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" +dotenv@^16.0.2: + version "16.0.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.2.tgz#0b0f8652c016a3858ef795024508cddc4bffc5bf" + integrity sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA== + duplexer2@~0.1.0: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -3862,6 +3889,17 @@ http-errors@^1.6.3: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +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" + http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" @@ -5028,6 +5066,11 @@ merge2@^1.3.0, merge2@^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: + 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.2, micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" @@ -5776,6 +5819,11 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" +path-to-regexp@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" + integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -6238,6 +6286,11 @@ semver@^7.1.1, semver@^7.1.2, semver@^7.1.3, semver@^7.3.2, semver@^7.3.4, semve dependencies: lru-cache "^6.0.0" +serverless-http@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/serverless-http/-/serverless-http-3.0.2.tgz#998fc2e7adcbb5496addd909d013e62448284f56" + integrity sha512-0r4TEhb8umOmbzvn9y9aFjdWdrapyNhTHd2oz1YsCRn+9A5RV3DOj6Pl3DH8BQgHnAlG6g88hiBB6/zefnvPRg== + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -6407,6 +6460,11 @@ stack-utils@^2.0.3: 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== + "statuses@>= 1.5.0 < 2", statuses@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -6636,6 +6694,11 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +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" @@ -6776,6 +6839,11 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.8.tgz#a82e6e53c9be14f7382de3d068ef1e26e7d4aaf8" integrity sha512-fvBeuXOsvqjecUtF/l1dwsrrf5y2BCUk9AOJGzGcm6tE7vegku5u/YvqjyDaAGr422PLoLnrxg3EnRvTqsdC1w== +ulid@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/ulid/-/ulid-2.3.0.tgz#93063522771a9774121a84d126ecd3eb9804071f" + integrity sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"