From c5abd3dd6fd288b469a5b27b4b8e9a0e68512ce9 Mon Sep 17 00:00:00 2001 From: Satish Gandham Date: Tue, 28 Dec 2021 16:09:15 +0530 Subject: [PATCH] feat: UI Performance Infra setup v1 (#9835) Co-authored-by: Satish Gandham --- .../workflows/integration-tests-command.yml | 173 ++++++++ .gitignore | 3 + app/client/perf/dsl/simple-typing.js | 85 ++++ app/client/perf/index.js | 55 +++ app/client/perf/package.json | 15 + app/client/perf/perf.js | 136 ++++++ app/client/perf/start-test.sh | 1 + app/client/perf/summary.js | 86 ++++ app/client/perf/utils/utils.js | 143 +++++++ app/client/perf/yarn.lock | 402 ++++++++++++++++++ 10 files changed, 1099 insertions(+) create mode 100644 app/client/perf/dsl/simple-typing.js create mode 100644 app/client/perf/index.js create mode 100644 app/client/perf/package.json create mode 100644 app/client/perf/perf.js create mode 100644 app/client/perf/start-test.sh create mode 100644 app/client/perf/summary.js create mode 100644 app/client/perf/utils/utils.js create mode 100644 app/client/perf/yarn.lock diff --git a/.github/workflows/integration-tests-command.yml b/.github/workflows/integration-tests-command.yml index c43893b376..4501ecefff 100644 --- a/.github/workflows/integration-tests-command.yml +++ b/.github/workflows/integration-tests-command.yml @@ -570,3 +570,176 @@ jobs: return result; } + perf-test: + needs: [build, server-build] + # Only run if the build step is successful + if: success() + runs-on: ubuntu-latest + defaults: + run: + working-directory: app/client + shell: bash + strategy: + fail-fast: false + matrix: job:[0] + + # Service containers to run with this job. Required for running tests + services: + # Label used to access the service container + redis: + # Docker Hub image for Redis + image: redis + ports: + # Opens tcp port 6379 on the host and service container + - 6379:6379 + mongo: + image: mongo + ports: + - 27017:27017 + + steps: + # Check out merge commitGIT BRANCH + - name: Fork based /ok-to-test-perf checkout + uses: actions/checkout@v2 + with: + ref: "refs/pull/${{ github.event.client_payload.pull_request.number }}/merge" + + - name: Use Node.js 14.15.4 + uses: actions/setup-node@v1 + with: + node-version: "14.15.4" + + - name: Get yarn cache directory path + id: yarn-dep-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + # Retrieve npm dependencies from cache. After a successful run, these dependencies are cached again + - name: Cache npm dependencies + id: yarn-dep-cache + uses: actions/cache@v2 + env: + cache-name: cache-yarn-dependencies + with: + path: | + ${{ steps.yarn-dep-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-dep-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn-dep- + + # Install all the dependencies + - name: Install dependencies + run: yarn install + + - name: Download the react build artifact + uses: actions/download-artifact@v2 + with: + name: build + path: app/client/build + + - name: Download the server build artifact + uses: actions/download-artifact@v2 + with: + name: build + path: app/server/dist + + # Start server + - name: start server + working-directory: app/server + env: + APPSMITH_MONGODB_URI: "mongodb://localhost:27017/mobtools" + APPSMITH_REDIS_URL: "redis://127.0.0.1:6379" + APPSMITH_ENCRYPTION_PASSWORD: "password" + APPSMITH_ENCRYPTION_SALT: "salt" + APPSMITH_IS_SELF_HOSTED: false + APPSMITH_CLOUD_SERVICES_BASE_URL: https://release-cs.appsmith.com + APPSMITH_CLOUD_SERVICES_USERNAME: "" + APPSMITH_CLOUD_SERVICES_PASSWORD: "" + run: | + ls -l + ls -l scripts/ + ls -l dist/ + # Run the server in the background and redirect logs to a log file + ./scripts/start-dev-server.sh &> server-logs.log & + + - name: Wait for 30 seconds for server to start + run: | + sleep 30s + + - name: Exit if Server hasnt started + run: | + if [[ `ps -ef | grep "server-1.0-SNAPSHOT" | grep java |wc -l` == 0 ]]; then + echo "Server Not Started"; + exit 1; + else + echo "Server Found"; + fi + + - name: Installing Yarn serve + run: | + yarn global add serve + echo "$(yarn global bin)" >> $GITHUB_PATH + + - name: Setting up the perf tests + shell: bash + env: + APPSMITH_SSL_CERTIFICATE: ${{ secrets.APPSMITH_SSL_CERTIFICATE }} + APPSMITH_SSL_KEY: ${{ secrets.APPSMITH_SSL_KEY }} + CYPRESS_URL: ${{ secrets.CYPRESS_URL }} + CYPRESS_USERNAME: ${{ secrets.CYPRESS_USERNAME }} + CYPRESS_PASSWORD: ${{ secrets.CYPRESS_PASSWORD }} + CYPRESS_TESTUSERNAME1: ${{ secrets.CYPRESS_TESTUSERNAME1 }} + CYPRESS_TESTPASSWORD1: ${{ secrets.CYPRESS_TESTPASSWORD1 }} + CYPRESS_TESTUSERNAME2: ${{ secrets.CYPRESS_TESTUSERNAME2 }} + CYPRESS_TESTPASSWORD2: ${{ secrets.CYPRESS_TESTPASSWORD1 }} + CYPRESS_S3_ACCESS_KEY: ${{ secrets.CYPRESS_S3_ACCESS_KEY }} + CYPRESS_S3_SECRET_KEY: ${{ secrets.CYPRESS_S3_SECRET_KEY }} + APPSMITH_DISABLE_TELEMETRY: true + APPSMITH_GOOGLE_MAPS_API_KEY: ${{ secrets.APPSMITH_GOOGLE_MAPS_API_KEY }} + POSTGRES_PASSWORD: postgres + run: | + ./cypress/setup-test.sh + + - name: Installing performance tests dependencies + working-directory: app/client/perf + shell: bash + run: yarn install + + - name: Change test script permissions + working-directory: app/client/perf + run: chmod +x ./start-test.sh + + - name: Run performance tests + working-directory: app/client/perf + shell: bash + env: + APPSMITH_SSL_CERTIFICATE: ${{ secrets.APPSMITH_SSL_CERTIFICATE }} + APPSMITH_SSL_KEY: ${{ secrets.APPSMITH_SSL_KEY }} + CYPRESS_TESTUSERNAME1: ${{ secrets.CYPRESS_TESTUSERNAME9 }} + CYPRESS_TESTPASSWORD1: ${{ secrets.CYPRESS_TESTPASSWORD9 }} + APPSMITH_DISABLE_TELEMETRY: true + POSTGRES_PASSWORD: postgres + NODE_TLS_REJECT_UNAUTHORIZED: "0" + run: ./start-test.sh + + - uses: actions/upload-artifact@v2 + with: + name: performance-summaries + path: app/client/perf/traces + + + - name: Read summary file + id: read_summary + uses: andstor/file-reader-action@v1 + with: + path: app/client/perf/traces/reports/summary.md + + - name: Add a comment with the results on the PR with link to workflow run + uses: peter-evans/create-or-update-comment@v1 + with: + issue-number: ${{ github.event.client_payload.pull_request.number }} + body: | + UI Performance test run logs and artifacts: . + Commit: `${{ github.event.client_payload.slash_command.sha }}`. + Results: ${{ steps.read_summary.outputs.contents }} + + diff --git a/.gitignore b/.gitignore index b3cb968bc7..7a0bab977c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ package-lock.json coverage-summary.json app/client/cypress/locators/Widgets.json deploy/ansible/appsmith_playbook/inventory + +# performance tests +app/client/perf/traces/* \ No newline at end of file diff --git a/app/client/perf/dsl/simple-typing.js b/app/client/perf/dsl/simple-typing.js new file mode 100644 index 0000000000..2c041461b6 --- /dev/null +++ b/app/client/perf/dsl/simple-typing.js @@ -0,0 +1,85 @@ +exports.dsl = { + dsl: { + widgetName: "MainContainer", + backgroundColor: "none", + rightColumn: 1936, + snapColumns: 64, + detachFromLayout: true, + widgetId: "0", + topRow: 0, + bottomRow: 1290, + containerStyle: "none", + snapRows: 125, + parentRowSpace: 1, + type: "CANVAS_WIDGET", + canExtend: true, + version: 46, + minHeight: 1292, + parentColumnSpace: 1, + dynamicBindingPathList: [], + leftColumn: 0, + children: [ + { + isVisible: true, + text: "

{{Input1.text}}

", + fontSize: "HEADING1", + fontStyle: "BOLD", + textAlign: "LEFT", + textColor: "#231F20", + widgetName: "Text1", + version: 1, + type: "TEXT_WIDGET", + hideCard: false, + displayName: "Text", + key: "4ln743vbxf", + iconSVG: "/static/media/icon.97c59b52.svg", + widgetId: "oylox3e28e", + renderMode: "CANVAS", + isLoading: false, + parentColumnSpace: 30.0625, + parentRowSpace: 10, + leftColumn: 1, + rightColumn: 39, + topRow: 16, + bottomRow: 40, + parentId: "0", + dynamicBindingPathList: [ + { + key: "text", + }, + ], + dynamicTriggerPathList: [], + }, + { + isVisible: true, + inputType: "TEXT", + label: "", + widgetName: "Input1", + version: 1, + defaultText: "", + iconAlign: "left", + autoFocus: false, + labelStyle: "", + resetOnSubmit: true, + isRequired: false, + isDisabled: false, + allowCurrencyChange: false, + type: "INPUT_WIDGET", + hideCard: false, + displayName: "Input", + key: "4xvbov2itw", + iconSVG: "/static/media/icon.9f505595.svg", + widgetId: "d454uqlxd0", + renderMode: "CANVAS", + isLoading: false, + parentColumnSpace: 30.0625, + parentRowSpace: 10, + leftColumn: 1, + rightColumn: 12, + topRow: 9, + bottomRow: 13, + parentId: "0", + }, + ], + }, +}; diff --git a/app/client/perf/index.js b/app/client/perf/index.js new file mode 100644 index 0000000000..58fe3755e3 --- /dev/null +++ b/app/client/perf/index.js @@ -0,0 +1,55 @@ +const path = require("path"); +const Perf = require("./perf.js"); +const dsl = require("./dsl/simple-typing").dsl; +var fs = require("fs"); +const { summaries } = require("./summary"); + +// Set the perf directory as APP_ROOT on the global level +global.APP_ROOT = path.resolve(__dirname); + +// Create the directory +const dir = `${APP_ROOT}/traces/reports`; +if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); +} + +process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; + +async function testTyping() { + const perf = new Perf({ + ignoreHTTPSErrors: true, // @todo Remove it after initial testing + }); + await perf.launch(); + const page = perf.getPage(); + await perf.loadDSL(dsl); + + const selector = "input.bp3-input"; // Input selector + await page.waitForSelector(selector); + const input = await page.$(selector); + + await perf.startTrace("Edit input"); + await page.type(selector, "Hello Appsmith"); + await perf.stopTrace(); + + await perf.startTrace("Clear input"); + await input.click({ clickCount: 3 }); + await input.press("Backspace"); + await perf.stopTrace(); + + await perf.startTrace("Edit input again"); + await page.type(selector, "Howdy satish"); + await perf.stopTrace(); + + await perf.generateReport(); + await perf.close(); +} + +async function runTests() { + await testTyping(); + await testTyping(); + await testTyping(); + await testTyping(); + await testTyping(); + summaries(`${APP_ROOT}/traces/reports`); +} +runTests(); diff --git a/app/client/perf/package.json b/app/client/perf/package.json new file mode 100644 index 0000000000..8302752bcc --- /dev/null +++ b/app/client/perf/package.json @@ -0,0 +1,15 @@ +{ + "name": "ui-performance-infra", + "version": "1.0.0", + "description": "Tools to automate chrome performance profiling", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "puppeteer": "^12.0.1", + "tracelib": "^1.0.1" + } +} diff --git a/app/client/perf/perf.js b/app/client/perf/perf.js new file mode 100644 index 0000000000..e30fe86963 --- /dev/null +++ b/app/client/perf/perf.js @@ -0,0 +1,136 @@ +const Tracelib = require("tracelib"); +const puppeteer = require("puppeteer"); +const fs = require("fs"); +const { + delay, + login, + getFormattedTime, + sortObjectKeys, +} = require("./utils/utils"); + +module.exports = class Perf { + constructor(launchOptions = {}) { + this.launchOptions = { + ...launchOptions, + }; + this.traces = []; + this.traceInProgress = false; + this.browser = null; + } + /** + * Launches the browser and, gives you the page + */ + launch = async () => { + this.browser = await puppeteer.launch(this.launchOptions); + const pages_ = await this.browser.pages(); + this.page = pages_[0]; + await this._login(); + }; + + _login = async () => { + await login(this.page); + await delay(2000, "after login"); + }; + + startTrace = async (action = "foo") => { + if (this.traceInProgress) { + console.warn("Trace progress. You can run only one trace at a time"); + return; + } + + this.traceInProgress = true; + await delay(3000, `before starting trace ${action}`); + const path = `${APP_ROOT}/traces/${action}-${getFormattedTime()}-chrome-profile.json`; + await this.page.tracing.start({ + path: path, + screenshots: true, + }); + this.traces.push({ action, path }); + }; + + stopTrace = async () => { + this.traceInProgress = false; + await delay(5000, "before stoping the trace"); + await this.page.tracing.stop(); + }; + + getPage = () => { + if (this.page) return this.page; + throw Error("Can't find the page, please call launch method."); + }; + + loadDSL = async (dsl) => { + const selector = ".createnew"; + await this.page.waitForSelector(selector); + await this.page.click(selector); + // We goto the newly created app. + // Lets update the page + await this.page.waitForNavigation(); + + const currentUrl = this.page.url(); + const pageIdRegex = /pages(.*)/; + const match = pageIdRegex.exec(currentUrl); + const pageId = match[1].split("/")[1]; + + await this.page.evaluate( + async ({ pageId, dsl }) => { + const layoutId = await fetch(`/api/v1/pages/${pageId}`) + .then((response) => response.json()) + .then((data) => data.data.layouts[0].id); + + const pageSaveUrl = "/api/v1/layouts/" + layoutId + "/pages/" + pageId; + await fetch(pageSaveUrl, { + headers: { + accept: "application/json, text/plain, */*", + "content-type": "application/json", + }, + + referrerPolicy: "strict-origin-when-cross-origin", + body: JSON.stringify(dsl), + method: "PUT", + mode: "cors", + credentials: "include", + }) + .then((res) => + console.log("Save page with new DSL response:", res.json()), + ) + .catch((err) => { + console.log("Save page with new DSL error:", err); + }); + }, + { pageId, dsl }, + ); + await this.page.goto(currentUrl.replace("generate-page", ""), { + waitUntil: "networkidle2", + timeout: 60000, + }); + }; + + generateReport = async () => { + const report = {}; + this.traces.forEach(({ path, action }) => { + report[action] = {}; + const trace = require(path); + const tasks = new Tracelib.default(trace.traceEvents); + report[action].path = path; + report[action].summary = sortObjectKeys(tasks.getSummary()); + report[action].warnings = sortObjectKeys(tasks.getWarningCounts()); + }); + + fs.writeFile( + `${APP_ROOT}/traces/reports/${getFormattedTime()}.json`, + JSON.stringify(report, "", 4), + (err) => { + if (err) { + console.log("Error writing file", err); + } else { + console.log("Successfully wrote report"); + } + }, + ); + }; + + close = async () => { + this.browser.close(); + }; +}; diff --git a/app/client/perf/start-test.sh b/app/client/perf/start-test.sh new file mode 100644 index 0000000000..82ce50eb92 --- /dev/null +++ b/app/client/perf/start-test.sh @@ -0,0 +1 @@ +node index.js diff --git a/app/client/perf/summary.js b/app/client/perf/summary.js new file mode 100644 index 0000000000..093155986e --- /dev/null +++ b/app/client/perf/summary.js @@ -0,0 +1,86 @@ +const fs = require("fs"); +const path = require("path"); + +exports.summaries = async (directory) => { + const files = await fs.promises.readdir(directory); + const results = {}; + files.forEach((file) => { + if (file.endsWith(".json")) { + const content = require(`${APP_ROOT}/traces/reports/${file}`); + Object.keys(content).forEach((key) => { + if (!results[key]) { + results[key] = {}; + } + if (!results[key]?.scripting) { + results[key].scripting = []; + } + results[key].scripting.push(content[key].summary.scripting); + + if (!results[key]?.painting) { + results[key].painting = []; + } + results[key].painting.push(content[key].summary.painting); + + if (!results[key]?.rendering) { + results[key].rendering = []; + } + results[key].rendering.push(content[key].summary.rendering); + }); + } + }); + generateReport(results); +}; + +const generateReport = (results) => { + var size = 5; + Object.keys(results).forEach((key) => { + const action = results[key]; + Object.keys(action).forEach((key) => { + size = action[key].length; + const sum = action[key].reduce((sum, val) => sum + val, 0); + const avg = (sum / action[key].length).toFixed(2); + action[key].push(avg); + }); + }); + + generateMarkdown(results, size); +}; + +const generateMarkdown = (results, size = 5) => { + let markdown = `
Click to view performance test results\n\n| `; + for (let i = 0; i < size; i++) { + markdown = markdown + `| Run #${i + 1} `; + } + markdown = markdown + `| Avg `; + + markdown += "|\n"; + + for (let i = 0; i <= size + 1; i++) { + markdown = markdown + `| ------------- `; + } + markdown += "|\n"; + + Object.keys(results).forEach((key) => { + const action = results[key]; + markdown = markdown + key; + for (let i = 0; i <= size; i++) { + markdown = markdown + `| `; + } + markdown += "|\n"; + Object.keys(action).forEach((key) => { + markdown += `| ${key} | `; + markdown += action[key].reduce((sum, val) => `${sum} | ${val} `); + markdown += "| \n"; + }); + }); + + markdown += "
"; + + fs.writeFile(`${APP_ROOT}/traces/reports/summary.md`, markdown, (err) => { + if (err) { + console.log("Error writing file", err); + } else { + console.log("Successfully wrote summary"); + } + }); +}; diff --git a/app/client/perf/utils/utils.js b/app/client/perf/utils/utils.js new file mode 100644 index 0000000000..fb23b499f7 --- /dev/null +++ b/app/client/perf/utils/utils.js @@ -0,0 +1,143 @@ +const fs = require("fs"); +const path = require("path"); + +const delay = (time, msg = "") => { + console.log(`waiting ${msg}:`, time / 1000, "s"); + return new Promise(function(resolve) { + setTimeout(resolve, time); + }); +}; + +exports.delay = delay; + +exports.getDevToolsPage = async (browser) => { + const targets = await browser.targets(); + const devtoolsTarget = targets.filter((t) => { + return t.type() === "other" && t.url().startsWith("devtools://"); + })[0]; + + // Hack to get a page pointing to the devtools + devtoolsTarget._targetInfo.type = "page"; + const devtoolsPage = await devtoolsTarget.page(); + return devtoolsPage; +}; + +exports.gotoProfiler = async (devtoolsPage) => { + await devtoolsPage.bringToFront(); + await devtoolsPage.keyboard.down("MetaLeft"); + await devtoolsPage.keyboard.press("["); + await devtoolsPage.keyboard.up("MetaLeft"); +}; + +exports.getProfilerFrame = async (devtoolsPage) => { + const frames = await devtoolsPage.frames(); + const reactProfiler = frames[2]; // This is not foolproof + return reactProfiler; +}; + +exports.startReactProfile = async (reactProfiler) => { + const recordButton = + "#container > div > div > div > div > div.Toolbar___30kHu > button.Button___1-PiG.InactiveRecordToggle___2CUtF"; + await reactProfiler.waitForSelector(recordButton); + const container = await reactProfiler.$(recordButton); + console.log("Satring recording"); + await reactProfiler.evaluate((el) => el.click(), container); + console.log("Recording started"); +}; + +exports.stopReactProfile = async (reactProfiler) => { + const stopRecordingButton = + "#container > div > div > div > div > div.Toolbar___30kHu > button.Button___1-PiG.ActiveRecordToggle___1Cpcb"; + await reactProfiler.waitForSelector(stopRecordingButton); + const container = await reactProfiler.$(stopRecordingButton); + console.log("Stopping recording"); + await reactProfiler.evaluate((el) => el.click(), container); + console.log("Recording stopped"); +}; + +exports.downloadReactProfile = async (reactProfiler) => { + const saveProfileButton = + "#container > div > div > div > div.LeftColumn___3I7-I > div.Toolbar___30kHu > button:nth-child(8)"; + await reactProfiler.waitForSelector(saveProfileButton); + const container = await reactProfiler.$(saveProfileButton); + await reactProfiler.evaluate((el) => el.click(), container); + console.log("Downlaoded the profile"); +}; + +exports.saveProfile = async (reactProfiler, name) => { + const anchorSelector = + "#container > div > div > div > div.LeftColumn___3I7-I > div.Toolbar___30kHu > a"; + await reactProfiler.waitForSelector(anchorSelector); + const anchor = await reactProfiler.$(anchorSelector); + await reactProfiler.evaluate( + (el) => console.log(el.getAttribute("href")), + anchor, + ); + const attr = await reactProfiler.$$eval(anchorSelector, (el) => + el.map((x) => x.getAttribute("href")), + ); + + const url = attr[0]; + + const profile = await reactProfiler.evaluate(async (href) => { + const blob = await fetch(href).then(async (r) => r.blob()); + const text = await blob.text(); + return text; + }, url); + const location = path.join(__dirname, `/profiles/${name}.json`); + fs.writeFileSync(location, profile); +}; + +exports.login = async (page) => { + const url = "https://dev.appsmith.com/user/login"; + + await page.goto(url); + await page.setViewport({ width: 1440, height: 714 }); + + await delay(1000, "before login"); + + const emailSelector = "input[name='username']"; + const passwordSelector = "input[name='password']"; + const buttonSelector = "button[type='submit']"; + + try { + await page.waitForSelector(emailSelector); + await page.waitForSelector(passwordSelector); + await page.waitForSelector(buttonSelector); + } catch (e) { + console.error(e); + console.log( + "Screenshot:", + `${APP_ROOT}/traces/reports/login-selector-error.png`, + ); + await page.screenshot({ + path: `${APP_ROOT}/traces/reports/login-selector-error.png`, + }); + } + + await page.type(emailSelector, process.env.CYPRESS_TESTUSERNAME1); + await page.type(passwordSelector, process.env.CYPRESS_TESTPASSWORD1); + delay(1000, "before clicking login button"); + await page.click(buttonSelector); +}; + +exports.getFormattedTime = () => { + var today = new Date(); + var y = today.getFullYear(); + var m = today.getMonth() + 1; + var d = today.getDate(); + var h = today.getHours(); + var mi = today.getMinutes(); + var s = today.getSeconds(); + return y + "-" + m + "-" + d + "-" + h + "-" + mi + "-" + s; +}; + +exports.sortObjectKeys = (obj) => { + const sortedObj = {}; + Object.keys(obj) + .sort() + .forEach((key) => { + sortedObj[key] = obj[key]; + }); + return sortedObj; +}; diff --git a/app/client/perf/yarn.lock b/app/client/perf/yarn.lock new file mode 100644 index 0000000000..97aa4b264c --- /dev/null +++ b/app/client/perf/yarn.lock @@ -0,0 +1,402 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@*": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.0.tgz#62797cee3b8b497f6547503b2312254d4fe3c2bb" + integrity sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw== + +"@types/yauzl@^2.9.1": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" + integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA== + dependencies: + "@types/node" "*" + +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" + +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== + +bl@^4.0.3: + 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" + +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" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + +buffer@^5.2.1, 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" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +debug@4, debug@^4.1.1: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + +debug@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + +devtools-protocol@0.0.937139: + version "0.0.937139" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.937139.tgz#bdee3751fdfdb81cb701fd3afa94b1065dafafcf" + integrity sha512-daj+rzR3QSxsPRy5vjjthn58axO8c11j58uY0lG5vvlJk/EiOdCWOptGdkXDjtuRHr78emKq0udHCXM4trhoDQ== + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +extract-zip@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + +find-up@^4.0.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" + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +glob@^7.1.3: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +https-proxy-agent@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + +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== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +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" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-fetch@2.6.5: + version "2.6.5" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" + integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ== + dependencies: + whatwg-url "^5.0.0" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +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-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-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== + +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 sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +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" + +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== + +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== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +puppeteer@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-12.0.1.tgz#ae79d0e174a07563e0bf2e05c94ccafce3e70033" + integrity sha512-YQ3GRiyZW0ddxTW+iiQcv2/8TT5c3+FcRUCg7F8q2gHqxd5akZN400VRXr9cHQKLWGukmJLDiE72MrcLK9tFHQ== + dependencies: + debug "4.3.2" + devtools-protocol "0.0.937139" + extract-zip "2.0.1" + https-proxy-agent "5.0.0" + node-fetch "2.6.5" + pkg-dir "4.2.0" + progress "2.0.3" + proxy-from-env "1.1.0" + rimraf "3.0.2" + tar-fs "2.1.1" + unbzip2-stream "1.4.3" + ws "8.2.3" + +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +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" + +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== + +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" + +tar-fs@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +tracelib@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tracelib/-/tracelib-1.0.1.tgz#bb44ea96c19b8d7a6c85a6ee1cac9945c5b75c64" + integrity sha512-T2Vkpa/7Vdm3sV8nXRn8vZ0tnq6wlnO4Zx7Pux+JA1W6DMlg5EtbNcPZu/L7XRTPc9S0eAKhEFR4p/u0GcsDpQ== + +unbzip2-stream@1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" + integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" + integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0"