[7.8] Report page load asset size (#66224) (#68387)

* Report page load asset size (#66224)

* performance_tests: draft version

* update cli runner and script

* ingest metrics

* save asset type for plugins

* Update src/dev/performance/ingest_metrics.ts

Co-authored-by: Spencer <email@spalger.com>

* follow review comments

* fix size calc, add FTR config, move src to kbn/test

* fix import, remove unused interface

* Update packages/kbn-test/src/page_load_metrics/capture_page_load_metrics.ts

Co-authored-by: Spencer <email@spalger.com>

* start chromium with no-sandbox

* add logging

* check page contains expected element, cut apps to 5

* fix locator & typo

* Update packages/kbn-test/src/page_load_metrics/navigation.ts

Co-authored-by: Spencer <email@spalger.com>

* Update navigation.ts

* Update navigation.ts

* bump puppeteer version

* fix typo

* update navigation script

* update config file

* update yarn.lock

* fix

* take screenshot on failure

* update screenshot title

* fix screenshot saving and error

* invalid locator

* Revert "invalid locator"

This reverts commit 3007539a69.

* run script in a loop 10 times

* Revert "run script in a loop 10 times"

This reverts commit 6cfa219140.

* path config value directly

* fix screenshots directory setup

* update maps locator, common for landing and new map

Co-authored-by: Spencer <email@spalger.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Mikhail Shustov <restrry@gmail.com>
# Conflicts:
#	Jenkinsfile

* [page_load_metrics] fix path for discover

* fix url formatting

* fix screenshot title
This commit is contained in:
Dmitry Lemeshko 2020-06-06 09:30:45 +02:00 committed by GitHub
parent d452d21ad3
commit b6d6889cf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 2994 additions and 2 deletions

1
Jenkinsfile vendored
View file

@ -41,6 +41,7 @@ kibanaPipeline(timeoutMinutes: 135, checkPrChanges: true) {
'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9),
'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10),
'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'),
'xpack-pageLoadMetrics': kibanaPipeline.functionalTestProcess('xpack-pageLoadMetrics', './test/scripts/jenkins_xpack_page_load_metrics.sh'),
'xpack-siemCypress': { processNumber ->
whenChanged(['x-pack/plugins/siem/', 'x-pack/test/siem_cypress/']) {
kibanaPipeline.functionalTestProcess('xpack-siemCypress', './test/scripts/jenkins_siem_cypress.sh')(processNumber)

View file

@ -14,6 +14,7 @@
"@kbn/babel-preset": "1.0.0",
"@kbn/dev-utils": "1.0.0",
"@types/parse-link-header": "^1.0.0",
"@types/puppeteer": "^3.0.0",
"@types/strip-ansi": "^5.2.1",
"@types/xml2js": "^0.4.5",
"diff": "^4.0.1"
@ -25,6 +26,7 @@
"getopts": "^2.2.4",
"glob": "^7.1.2",
"parse-link-header": "^1.0.1",
"puppeteer": "^3.3.0",
"strip-ansi": "^5.2.0",
"rxjs": "^6.5.3",
"tar-fs": "^1.16.3",

View file

@ -51,3 +51,5 @@ export { runFailedTestsReporterCli } from './failed_tests_reporter';
export { makeJunitReportPath } from './junit_report_path';
export { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix';
export * from './page_load_metrics';

View file

@ -0,0 +1,81 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ToolingLog } from '@kbn/dev-utils';
import { NavigationOptions, createUrl, navigateToApps } from './navigation';
export async function capturePageLoadMetrics(log: ToolingLog, options: NavigationOptions) {
const responsesByPageView = await navigateToApps(log, options);
const assetSizeMeasurements = new Map<string, number[]>();
const numberOfPagesVisited = responsesByPageView.size;
for (const [, frameResponses] of responsesByPageView) {
for (const [, { url, dataLength }] of frameResponses) {
if (url.length === 0) {
throw new Error('navigateToApps(); failed to identify the url of the request');
}
if (assetSizeMeasurements.has(url)) {
assetSizeMeasurements.set(url, [dataLength].concat(assetSizeMeasurements.get(url) || []));
} else {
assetSizeMeasurements.set(url, [dataLength]);
}
}
}
return Array.from(assetSizeMeasurements.entries())
.map(([url, measurements]) => {
const baseUrl = createUrl('/', options.appConfig.url);
const relativeUrl = url
// remove the baseUrl (expect the trailing slash) to make url relative
.replace(baseUrl.slice(0, -1), '')
// strip the build number from asset urls
.replace(/^\/\d+\//, '/');
return [relativeUrl, measurements] as const;
})
.filter(([url, measurements]) => {
if (measurements.length !== numberOfPagesVisited) {
// ignore urls seen only on some pages
return false;
}
if (url.startsWith('data:')) {
// ignore data urls since they are already counted by other assets
return false;
}
if (url.startsWith('/api/') || url.startsWith('/internal/')) {
// ignore api requests since they don't have deterministic sizes
return false;
}
const allMetricsAreEqual = measurements.every((x, i) =>
i === 0 ? true : x === measurements[i - 1]
);
if (!allMetricsAreEqual) {
throw new Error(`measurements for url [${url}] are not equal [${measurements.join(',')}]`);
}
return true;
})
.map(([url, measurements]) => {
return { group: 'page load asset size', id: url, value: measurements[0] };
});
}

View file

@ -0,0 +1,90 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Url from 'url';
import { run, createFlagError } from '@kbn/dev-utils';
import { resolve, basename } from 'path';
import { capturePageLoadMetrics } from './capture_page_load_metrics';
const defaultScreenshotsDir = resolve(__dirname, 'screenshots');
export function runPageLoadMetricsCli() {
run(
async ({ flags, log }) => {
const kibanaUrl = flags['kibana-url'];
if (!kibanaUrl || typeof kibanaUrl !== 'string') {
throw createFlagError('Expect --kibana-url to be a string');
}
const parsedUrl = Url.parse(kibanaUrl);
const [username, password] = parsedUrl.auth
? parsedUrl.auth.split(':')
: [flags.username, flags.password];
if (typeof username !== 'string' || typeof password !== 'string') {
throw createFlagError(
'Mising username and/or password, either specify in --kibana-url or pass --username and --password'
);
}
const headless = !flags.head;
const screenshotsDir = flags.screenshotsDir || defaultScreenshotsDir;
if (typeof screenshotsDir !== 'string' || screenshotsDir === basename(screenshotsDir)) {
throw createFlagError('Expect screenshotsDir to be valid path string');
}
const metrics = await capturePageLoadMetrics(log, {
headless,
appConfig: {
url: kibanaUrl,
username,
password,
},
screenshotsDir,
});
for (const metric of metrics) {
log.info(`${metric.id}: ${metric.value}`);
}
},
{
description: `Loads several pages with Puppeteer to capture the size of assets`,
flags: {
string: ['kibana-url', 'username', 'password', 'screenshotsDir'],
boolean: ['head'],
default: {
username: 'elastic',
password: 'changeme',
debug: true,
screenshotsDir: defaultScreenshotsDir,
},
help: `
--kibana-url Url for Kibana we should connect to, can include login info
--head Run puppeteer with graphical user interface
--username Set username, defaults to 'elastic'
--password Set password, defaults to 'changeme'
--screenshotsDir Set screenshots directory, defaults to '${defaultScreenshotsDir}'
`,
},
}
);
}

View file

@ -0,0 +1,34 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export interface ResponseReceivedEvent {
frameId: string;
loaderId: string;
requestId: string;
response: Record<string, any>;
timestamp: number;
type: string;
}
export interface DataReceivedEvent {
encodedDataLength: number;
dataLength: number;
requestId: string;
timestamp: number;
}

View file

@ -0,0 +1,21 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export * from './cli';
export { capturePageLoadMetrics } from './capture_page_load_metrics';

View file

@ -0,0 +1,167 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Fs from 'fs';
import Url from 'url';
import _ from 'lodash';
import puppeteer from 'puppeteer';
import { resolve } from 'path';
import { ToolingLog } from '@kbn/dev-utils';
import { ResponseReceivedEvent, DataReceivedEvent } from './event';
export interface NavigationOptions {
headless: boolean;
appConfig: { url: string; username: string; password: string };
screenshotsDir: string;
}
export type NavigationResults = Map<string, Map<string, FrameResponse>>;
interface FrameResponse {
url: string;
dataLength: number;
}
function joinPath(pathA: string, pathB: string) {
return `${pathA.endsWith('/') ? pathA.slice(0, -1) : pathA}/${
pathB.startsWith('/') ? pathB.slice(1) : pathB
}`;
}
export function createUrl(path: string, url: string) {
const baseUrl = Url.parse(url);
return Url.format({
protocol: baseUrl.protocol,
hostname: baseUrl.hostname,
port: baseUrl.port,
pathname: joinPath(baseUrl.pathname || '', path.includes('#') ? path.split('#')[0] : path),
hash: path.includes('#') ? path.split('#')[1] : undefined,
});
}
async function loginToKibana(
log: ToolingLog,
browser: puppeteer.Browser,
options: NavigationOptions
) {
log.debug(`log in to the app..`);
const page = await browser.newPage();
const loginUrl = createUrl('/login', options.appConfig.url);
await page.goto(loginUrl, {
waitUntil: 'networkidle0',
});
await page.type('[data-test-subj="loginUsername"]', options.appConfig.username);
await page.type('[data-test-subj="loginPassword"]', options.appConfig.password);
await page.click('[data-test-subj="loginSubmit"]');
await page.waitForNavigation({ waitUntil: 'networkidle0' });
await page.close();
}
export async function navigateToApps(log: ToolingLog, options: NavigationOptions) {
const browser = await puppeteer.launch({ headless: options.headless, args: ['--no-sandbox'] });
const devToolsResponses: NavigationResults = new Map();
const apps = [
{ path: '/app/kibana#/discover', locator: '[data-test-subj="discover-sidebar"]' },
{ path: '/app/kibana#/home', locator: '[data-test-subj="homeApp"]' },
{ path: '/app/canvas', locator: '[data-test-subj="create-workpad-button"]' },
{ path: '/app/maps', locator: '[title="Maps"]' },
{ path: '/app/apm', locator: '[data-test-subj="apmMainContainer"]' },
];
await loginToKibana(log, browser, options);
await Promise.all(
apps.map(async (app) => {
const page = await browser.newPage();
page.setCacheEnabled(false);
page.setDefaultNavigationTimeout(0);
const frameResponses = new Map<string, FrameResponse>();
devToolsResponses.set(app.path, frameResponses);
const client = await page.target().createCDPSession();
await client.send('Network.enable');
function getRequestData(requestId: string) {
if (!frameResponses.has(requestId)) {
frameResponses.set(requestId, { url: '', dataLength: 0 });
}
return frameResponses.get(requestId)!;
}
client.on('Network.responseReceived', (event: ResponseReceivedEvent) => {
getRequestData(event.requestId).url = event.response.url;
});
client.on('Network.dataReceived', (event: DataReceivedEvent) => {
getRequestData(event.requestId).dataLength += event.dataLength;
});
const url = createUrl(app.path, options.appConfig.url);
log.debug(`goto ${url}`);
await page.goto(url, {
waitUntil: 'networkidle0',
});
let readyAttempt = 0;
let selectorFound = false;
while (!selectorFound) {
readyAttempt += 1;
try {
await page.waitForSelector(app.locator, { timeout: 5000 });
selectorFound = true;
} catch (error) {
log.error(
`Page '${app.path}' was not loaded properly, unable to find '${
app.locator
}', url: ${page.url()}`
);
if (readyAttempt < 6) {
continue;
}
const failureDir = resolve(options.screenshotsDir, 'failure');
const screenshotPath = resolve(
failureDir,
`${app.path.slice(1).split(/#|\//).join('_')}_navigation.png`
);
Fs.mkdirSync(failureDir, { recursive: true });
await page.bringToFront();
await page.screenshot({
path: screenshotPath,
type: 'png',
fullPage: true,
});
log.debug(`Saving screenshot to ${screenshotPath}`);
throw new Error(`Page load timeout: ${app.path} not loaded after 30 seconds`);
}
}
await page.close();
})
);
await browser.close();
return devToolsResponses;
}

View file

@ -0,0 +1,21 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
require('../src/setup_node_env');
require('@kbn/test').runPageLoadMetricsCli();

View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
source test/scripts/jenkins_test_setup_xpack.sh
checks-reporter-with-killswitch "Capture Kibana page load metrics" \
node scripts/functional_tests \
--debug --bail \
--kibana-install-dir "$installDir" \
--config test/page_load_metrics/config.ts;

1
x-pack/.gitignore vendored
View file

@ -3,6 +3,7 @@
/target
/test/functional/failure_debug
/test/functional/screenshots
/test/page_load_metrics/screenshots
/test/functional/apps/reporting/reports/session
/test/reporting/configs/failure_debug/
/legacy/plugins/reporting/.chromium/

View file

@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { resolve } from 'path';
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
import { PuppeteerTestRunner } from './runner';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const kibanaCommonTestsConfig = await readConfigFile(
require.resolve('../../../test/common/config.js')
);
const xpackFunctionalTestsConfig = await readConfigFile(
require.resolve('../functional/config.js')
);
return {
...kibanaCommonTestsConfig.getAll(),
testRunner: PuppeteerTestRunner,
esArchiver: {
directory: resolve(__dirname, 'es_archives'),
},
screenshots: {
directory: resolve(__dirname, 'screenshots'),
},
esTestCluster: {
...xpackFunctionalTestsConfig.get('esTestCluster'),
serverArgs: [...xpackFunctionalTestsConfig.get('esTestCluster.serverArgs')],
},
kbnTestServer: {
...xpackFunctionalTestsConfig.get('kbnTestServer'),
},
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { CiStatsReporter } from '@kbn/dev-utils';
import { capturePageLoadMetrics } from '@kbn/test';
// @ts-ignore not TS yet
import getUrl from '../../../src/test_utils/get_url';
import { FtrProviderContext } from './../functional/ftr_provider_context';
export async function PuppeteerTestRunner({ getService }: FtrProviderContext) {
const log = getService('log');
const config = getService('config');
const esArchiver = getService('esArchiver');
await esArchiver.load('default');
const metrics = await capturePageLoadMetrics(log, {
headless: true,
appConfig: {
url: getUrl.baseUrl(config.get('servers.kibana')),
username: config.get('servers.kibana.username'),
password: config.get('servers.kibana.password'),
},
screenshotsDir: config.get('screenshots.directory'),
});
const reporter = CiStatsReporter.fromEnv(log);
log.debug('Report page load asset size');
await reporter.metrics(metrics);
}

View file

@ -4574,6 +4574,13 @@
dependencies:
"@types/node" "*"
"@types/puppeteer@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-3.0.0.tgz#24cdcc131e319477608d893f0017e08befd70423"
integrity sha512-59+fkfHHXHzX5rgoXIMnZyzum7ZLx/Wc3fhsOduFThpTpKbzzdBHMZsrkKGLunimB4Ds/tI5lXTRLALK8Mmnhg==
dependencies:
"@types/node" "*"
"@types/q@^1.5.1":
version "1.5.2"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
@ -7637,6 +7644,15 @@ bl@^3.0.0:
dependencies:
readable-stream "^3.0.1"
bl@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a"
integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
blob@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
@ -8158,6 +8174,14 @@ buffer@^5.1.0, buffer@^5.2.0:
base64-js "^1.0.2"
ieee754 "^1.1.4"
buffer@^5.2.1, buffer@^5.5.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
dependencies:
base64-js "^1.0.2"
ieee754 "^1.1.4"
builtin-modules@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@ -16649,7 +16673,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@~2.0.3, inherits@~2.0.4:
inherits@2, inherits@2.0.4, inherits@^2.0.4, 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==
@ -20773,6 +20797,11 @@ mixin-object@^2.0.1:
for-in "^0.1.3"
is-extendable "^0.1.1"
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==
mkdirp@0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@ -23895,6 +23924,22 @@ puppeteer@^2.0.0:
rimraf "^2.6.1"
ws "^6.1.0"
puppeteer@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-3.3.0.tgz#95839af9fdc0aa4de7e5ee073a4c0adeb9e2d3d7"
integrity sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw==
dependencies:
debug "^4.1.0"
extract-zip "^2.0.0"
https-proxy-agent "^4.0.0"
mime "^2.0.3"
progress "^2.0.1"
proxy-from-env "^1.0.0"
rimraf "^3.0.2"
tar-fs "^2.0.0"
unbzip2-stream "^1.3.3"
ws "^7.2.3"
q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@ -26251,6 +26296,13 @@ rimraf@^2.5.4, rimraf@^2.7.1:
dependencies:
glob "^7.1.3"
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"
rimraf@~2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.0.3.tgz#f50a2965e7144e9afd998982f15df706730f56a9"
@ -28598,6 +28650,16 @@ tar-fs@^1.16.3:
pump "^1.0.0"
tar-stream "^1.1.2"
tar-fs@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5"
integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.0.0"
tar-stream@^1.1.2, tar-stream@^1.5.2:
version "1.5.5"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55"
@ -28608,6 +28670,17 @@ tar-stream@^1.1.2, tar-stream@^1.5.2:
readable-stream "^2.0.0"
xtend "^4.0.0"
tar-stream@^2.0.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325"
integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==
dependencies:
bl "^4.0.1"
end-of-stream "^1.4.1"
fs-constants "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.1.1"
tar-stream@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3"
@ -28897,7 +28970,7 @@ through2@~2.0.3:
readable-stream "~2.3.6"
xtend "~4.0.1"
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3.4, through@~2.3.6, through@~2.3.8:
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3.4, through@~2.3.6, 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=
@ -30091,6 +30164,14 @@ unbzip2-stream@^1.0.9:
buffer "^3.0.1"
through "^2.3.6"
unbzip2-stream@^1.3.3:
version "1.4.2"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz#84eb9e783b186d8fb397515fbb656f312f1a7dbf"
integrity sha512-pZMVAofMrrHX6Ik39hCk470kulCbmZ2SWfQLPmTWqfJV/oUm0gn1CblvHdUu4+54Je6Jq34x8kY6XjTy6dMkOg==
dependencies:
buffer "^5.2.1"
through "^2.3.8"
unc-path-regex@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
@ -32065,6 +32146,11 @@ ws@^7.0.0:
dependencies:
async-limiter "^1.0.0"
ws@^7.2.3:
version "7.3.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd"
integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==
ws@~3.3.1:
version "3.3.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"