build: create script to run benchmarks and perform comparisons (#50745)

This commit creates a new script that solves the following use-cases:

 - Running benchmarks. It's not trivial to figure out the benchmark
   target names, and it's also easy to mess up the right Bazel flags.

 - Performing comparisons. When e.g. working on a runtime senstive
   change, it should be trivial to run benchmarks between the current
   working stage, and a base revision (e.g. `main`).

The script takes care of both these use-cases and comes with a
prompt-based command line tool experience.

The script will also be used by a future GitHub action that can run
comparisons triggered via GitHub PR comment (by trusted team members).

PR Close #50745
This commit is contained in:
Paul Gschwendtner
2023-06-16 12:53:35 +00:00
parent a2e44d9773
commit bb52edee31
6 changed files with 447 additions and 5 deletions
+7 -1
View File
@@ -42,7 +42,8 @@
"devtools:devserver:demo-standalone": "ibazel run //devtools/projects/demo-standalone/src:devserver",
"devtools:build:chrome": "bazelisk build --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=chrome -- devtools/projects/shell-browser/src:prodapp",
"devtools:build:firefox": "bazelisk build --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=firefox -- devtools/projects/shell-browser/src:prodapp",
"devtools:test": "bazelisk test --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=chrome -- //devtools/..."
"devtools:test": "bazelisk test --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=chrome -- //devtools/...",
"benchmarks": "ts-node --esm scripts/benchmarks/index.mts"
},
"// 1": "dependencies are used locally and by bazel",
"dependencies": {
@@ -156,6 +157,7 @@
},
"// 2": "devDependencies are not used under Bazel. Many can be removed after test.sh is deleted.",
"devDependencies": {
"@actions/core": "^1.10.0",
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#7dad055464ea9847e4870b9e3baad1f0c417bdf7",
"@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#dec40448ead7b52cddae4d32bdb21d8c48589413",
"@babel/helper-remap-async-to-generator": "^7.18.9",
@@ -163,9 +165,12 @@
"@bazel/buildifier": "^6.0.0",
"@bazel/ibazel": "^0.16.0",
"@octokit/graphql": "^5.0.0",
"@types/adm-zip": "^0.5.0",
"@types/cldrjs": "^0.4.22",
"@types/cli-progress": "^3.4.2",
"@types/inquirer": "^9.0.3",
"@yarnpkg/lockfile": "^1.1.0",
"adm-zip": "^0.5.10",
"check-side-effects": "0.0.23",
"cldr": "7.4.1",
"cldrjs": "0.5.5",
@@ -176,6 +181,7 @@
"gulp": "^4.0.2",
"gulp-conventional-changelog": "^3.0.0",
"husky": "8.0.3",
"inquirer": "^9.2.7",
"karma-sauce-launcher": "^4.3.6",
"madge": "^6.0.0",
"multimatch": "^6.0.0",
+172
View File
@@ -0,0 +1,172 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import yargs from 'yargs';
import {bold, yellow, GitClient, green, Log} from '@angular/ng-dev';
import inquirer from 'inquirer';
import {exec} from './utils.mjs';
import {ResolvedTarget, findBenchmarkTargets, getTestlogPath, resolveTarget} from './targets.mjs';
import {collectBenchmarkResults} from './results.mjs';
import {setOutput} from '@actions/core';
const benchmarkTestFlags = [
'--cache_test_results=no',
'--color=yes',
'--curses=no',
// We may have RBE set up, but test should run locally on the same machine to
// reduce fluctuation. Output streamed ensures that deps can build with RBE, but
// tests run locally while also providing useful output for debugging.
'--test_output=streamed',
];
await yargs(process.argv.slice(2))
.command(
'run-compare <compare-ref> [bazel-target]',
'Runs a benchmark between two SHAs',
(argv) =>
argv
.positional('compare-ref', {description: 'Comparison SHA', type: 'string', demandOption: true})
.positional('bazel-target', {description: 'Bazel target', type: 'string'}),
(args) => runCompare(args.bazelTarget, args.compareRef)
)
.command(
'run [bazel-target]',
'Runs a benchmark',
(argv) => argv.positional('bazel-target', {description: 'Bazel target', type: 'string'}),
(args) => runBenchmarkCmd(args.bazelTarget)
)
.command(
'extract-compare-comment <comment-body>',
false, // Do not show in help.
(argv) => argv.positional('comment-body', {demandOption: true, type: 'string'}),
(args) => extractCompareComment(args.commentBody)
)
.demandCommand()
.scriptName('$0')
.help()
.strict()
.parseAsync();
/** Prompts for a benchmark target. */
async function promptForBenchmarkTarget(): Promise<string> {
const targets = await findBenchmarkTargets();
return (
await inquirer.prompt<{bazelTarget: string}>({
name: 'bazelTarget',
message: 'Select benchmark target to run:',
type: 'list',
choices: targets.map((t) => ({value: t, name: t})),
})
).bazelTarget;
}
/**
* Extracts arguments from a benchmark compare comment.
*
* This is a helper used by the GitHub action to perform benchmark
* comparisons. Commands follow the format of: `/benchmark-compare <sha> <target>`.
*/
async function extractCompareComment(commentBody: string): Promise<void> {
const matches = /\/[^ ]+ ([^ ]+) ([^ ]+)/.exec(commentBody);
if (matches === null) {
Log.error('Could not extract information from comment', commentBody);
process.exit(1);
}
setOutput('compareRef', matches[1]);
setOutput('benchmarkTarget', matches[2]);
}
/** Runs a specified benchmark, or a benchmark selected via prompt. */
async function runBenchmarkCmd(bazelTargetRaw: string | undefined): Promise<void> {
if (bazelTargetRaw === undefined) {
bazelTargetRaw = await promptForBenchmarkTarget();
}
await runBenchmarkTarget(await resolveTarget(bazelTargetRaw));
}
/** Runs a benchmark Bazel target. */
async function runBenchmarkTarget(bazelTarget: ResolvedTarget): Promise<void> {
await exec('bazel', ['test', bazelTarget, ...benchmarkTestFlags]);
}
/**
* Performs a comparison of benchmark results between the current
* working stage and the comparison Git reference.
*/
async function runCompare(bazelTargetRaw: string | undefined, compareRef: string): Promise<void> {
const git = await GitClient.get();
const initialRef = git.getCurrentBranchOrRevision();
if (git.hasUncommittedChanges()) {
Log.warn(bold('You have uncommitted changes.'));
Log.warn('The script will stash your changes and re-apply them so that');
Log.warn('the comparison ref can be checked out.');
Log.warn('');
}
if (bazelTargetRaw === undefined) {
bazelTargetRaw = await promptForBenchmarkTarget();
}
const bazelTarget = await resolveTarget(bazelTargetRaw);
const testlogPath = await getTestlogPath(bazelTarget);
Log.log(green('Test log path:', testlogPath));
// Run benchmark with the current working stage.
await runBenchmarkTarget(bazelTarget);
const workingDirResults = await collectBenchmarkResults(testlogPath);
// Stash working directory as we might be in the middle of developing
// and we wouldn't want to discard changes when checking out the compare SHA.
git.run(['stash']);
try {
Log.log(green('Fetching comparison revision.'));
git.run(['fetch', '--depth=1', git.getRepoGitUrl(), compareRef]);
Log.log(green('Checking out comparison revision.'));
git.run(['checkout', 'FETCH_HEAD']);
await exec('yarn');
await runBenchmarkTarget(bazelTarget);
} finally {
restoreWorkingStage(git, initialRef);
}
// Re-install dependencies for `HEAD`.
await exec('yarn');
const comparisonResults = await collectBenchmarkResults(testlogPath);
// If we are running in a GitHub action, expose the benchmark text
// results as outputs. Useful if those are exposed as a GitHub comment then.
if (process.env.GITHUB_ACTION !== undefined) {
setOutput('comparisonResultsText', comparisonResults.textSummary);
setOutput('workingStageResultsText', workingDirResults.textSummary);
}
Log.info('\n\n\n');
Log.info(bold(green('Results!')));
Log.info(bold(yellow('Comparison results')), '\n');
Log.info(comparisonResults.textSummary);
Log.info(bold(yellow('Working stage results')), '\n');
Log.info(workingDirResults.textSummary);
}
function restoreWorkingStage(git: GitClient, initialRef: string) {
Log.log(green('Restoring working stage'));
git.run(['checkout', '-f', initialRef]);
// Stash apply could fail if there were not changes in the working stage.
git.runGraceful(['stash', 'apply']);
}
+63
View File
@@ -0,0 +1,63 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import path from 'path';
import Zip from 'adm-zip';
import type {JsonReport} from '../../packages/benchpress/src/reporter/json_file_reporter_types.js';
/** Results of an individual benchmark scenario. */
export interface ScenarioResult {
id: string;
data: JsonReport;
textSummary: string;
}
/**
* Overall result of a benchmark target.
* A benchmark target may contain multiple scenarios.
*/
export interface OverallResult {
scenarios: ScenarioResult[];
textSummary: string;
}
/** Collects and parses the benchmark results of the given Bazel target testlog directory. */
export function collectBenchmarkResults(testlogDir: string): OverallResult {
const z = new Zip(path.join(testlogDir, 'test.outputs/outputs.zip'));
const scenarioResults: ScenarioResult[] = [];
for (const e of z.getEntries()) {
if (path.extname(e.entryName) !== '.json') {
continue;
}
const data = JSON.parse(z.readAsText(e.entryName));
// Skip files that do not look like benchpress reports.
if (!isJsonReport(data)) {
continue;
}
scenarioResults.push({
id: data.description.id,
data,
textSummary: `${data.metricsText}\n${data.validSampleTexts.join('\n')}\n${data.statsText}`,
});
}
return {
scenarios: scenarioResults,
textSummary: scenarioResults.map((s) => `### ${s.id}\n\n${s.textSummary}`).join('`\n'),
};
}
/** Whether the object corresponds to a benchpress JSON report. */
function isJsonReport(data: any): data is JsonReport {
return data['completeSample'] !== undefined;
}
+50
View File
@@ -0,0 +1,50 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import path from 'path';
import {exec} from './utils.mjs';
/** Branded string representing a resolved Bazel benchmark target. */
export type ResolvedTarget = string & {
__resolvedTarget: true;
};
/** Finds all benchmark Bazel targets in the project. */
export async function findBenchmarkTargets(): Promise<string[]> {
return (
await exec('bazel', [
'query',
'--output=label',
`'kind("^web_test", //modules/...) intersect attr("name", "perf", //modules/...)'`,
])
)
.split(/\r?\n/)
.filter((t) => t !== '');
}
/** Gets the testlog path of a given Bazel target. */
export async function getTestlogPath(target: ResolvedTarget): Promise<string> {
return path.join(await bazelTestlogDir(), target.substring(2).replace(':', '/'));
}
/** Resolves a given benchmark Bazel target to the fully expanded label. */
export async function resolveTarget(target: string): Promise<ResolvedTarget> {
// If the target does not specify an explicit browser test target, we attempt
// to automatically add the Chromium suffix. This is necessary for e.g.
// resolving testlogs which would reside under the actual test target.
if (!target.endsWith('_chromium')) {
target = `${target}_chromium`;
}
return (await exec('bazel', ['query', '--output=label', target])).trim() as ResolvedTarget;
}
let testlogDir: string | null = null;
async function bazelTestlogDir(): Promise<string> {
return testlogDir ?? (testlogDir = (await exec('bazel', ['info', 'bazel-testlogs'])).trim());
}
+51
View File
@@ -0,0 +1,51 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Log} from '@angular/ng-dev';
import childProcess from 'child_process';
import path from 'path';
import url from 'url';
const scriptDir = path.dirname(url.fileURLToPath(import.meta.url));
/** Absolute disk path to the project directory. */
export const projectDir = path.join(scriptDir, '../..');
/**
* Executes the given command, forwarding stdin, stdout and stderr while
* still capturing stdout in order to return it.
*/
export function exec(cmd: string, args: string[] = []): Promise<string> {
return new Promise((resolve, reject) => {
Log.info('Running command:', cmd, args.join(' '));
const proc = childProcess.spawn(cmd, args, {
shell: true,
cwd: projectDir,
// Only capture `stdout`. Forward the rest to the parent TTY.
stdio: ['inherit', 'pipe', 'inherit'],
});
let stdout = '';
proc.stdout.on('data', (chunk) => {
stdout += chunk.toString('utf8');
process.stdout.write(chunk);
});
proc.on('close', (status, signal) => {
if (status !== 0 || signal !== null) {
reject(`Command failed. Status code: ${status}. Signal: ${signal}`);
}
resolve(stdout);
});
proc.on('error', (err) => {
reject(`Command failed: ${err}`);
});
});
}
+104 -4
View File
@@ -2,6 +2,21 @@
# yarn lockfile v1
"@actions/core@^1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.10.0.tgz#44551c3c71163949a2f06e94d9ca2157a0cfac4f"
integrity sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==
dependencies:
"@actions/http-client" "^2.0.1"
uuid "^8.3.2"
"@actions/http-client@^2.0.1":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-2.1.0.tgz#b6d8c3934727d6a50d10d19f00a711a964599a9f"
integrity sha512-BonhODnXr3amchh4qkmjPMUO8mFi/zLaaCeCAJZqch8iQqyDnVIkySjB38VHAC8IJ+bnlgfOqlhpyCUZHlQsqw==
dependencies:
tunnel "^0.0.6"
"@ampproject/remapping@2.2.1", "@ampproject/remapping@^2.2.0":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
@@ -3367,6 +3382,13 @@
"@tufjs/canonical-json" "1.0.0"
minimatch "^9.0.0"
"@types/adm-zip@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@types/adm-zip/-/adm-zip-0.5.0.tgz#94c90a837ce02e256c7c665a6a1eb295906333c1"
integrity sha512-FCJBJq9ODsQZUNURo5ILAQueuA8WJhRvuihS3ke2iI25mJlfV2LK8jG2Qj2z2AWg8U0FtWWqBHVRetceLskSaw==
dependencies:
"@types/node" "*"
"@types/angular@^1.6.47":
version "1.8.5"
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.8.5.tgz#71c7d3581898f1863100eeed9d25bb737a8e1062"
@@ -3617,6 +3639,14 @@
dependencies:
"@types/node" "*"
"@types/inquirer@^9.0.3":
version "9.0.3"
resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-9.0.3.tgz#dc99da4f2f6de9d26c284b4f6aaab4d98c456db1"
integrity sha512-CzNkWqQftcmk2jaCWdBTf9Sm7xSw4rkI1zpU/Udw3HX5//adEZUIm9STtoRP1qgWj0CWQtJ9UTvqmO2NNjhMJw==
dependencies:
"@types/through" "*"
rxjs "^7.2.0"
"@types/is-windows@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/is-windows/-/is-windows-1.0.0.tgz#1011fa129d87091e2f6faf9042d6704cdf2e7be0"
@@ -3880,6 +3910,13 @@
resolved "https://registry.yarnpkg.com/@types/systemjs/-/systemjs-0.19.32.tgz#e9204c4cdbc8e275d645c00e6150e68fc5615a24"
integrity sha512-8jtvyxk9DI+KTQ/uh8iMW3FqCG2JnBPO6tEb2Z9r+4NDNpdBwOLjwIAGnprgyX/4yA6sgnc3sGqNpApZEJsXFg==
"@types/through@*":
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895"
integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==
dependencies:
"@types/node" "*"
"@types/tmp@^0.2.1":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165"
@@ -4354,7 +4391,7 @@ adjust-sourcemap-loader@^4.0.0:
loader-utils "^2.0.0"
regex-parser "^2.2.11"
adm-zip@^0.5.2:
adm-zip@^0.5.10, adm-zip@^0.5.2:
version "0.5.10"
resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.10.tgz#4a51d5ab544b1f5ce51e1b9043139b639afff45b"
integrity sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==
@@ -4488,7 +4525,7 @@ ansi-colors@^1.0.1:
dependencies:
ansi-wrap "^0.1.0"
ansi-escapes@^4.2.1:
ansi-escapes@^4.2.1, ansi-escapes@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
@@ -5841,6 +5878,11 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
cli-width@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.0.0.tgz#a5622f6a3b0a9e3e711a25f099bf2399f608caf6"
integrity sha512-ZksGS2xpa/bYkNzN3BAw1wEjsLV/ZKOf/CCrJ/QOBsxx6fOARIkwTutxp1XIOIohi6HKmOFjMoK/XaqDVUpEEw==
cliui@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
@@ -7034,6 +7076,12 @@ decompress-tarbz2@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
dependencies:
decompress-tar "^4.1.0"
file-type "^6.1.0"
is-stream "^1.1.0"
seek-bzip "^1.0.5"
unbzip2-stream "^1.0.9"
decompress-targz@^4.0.0:
version "4.1.1"
@@ -7599,6 +7647,9 @@ ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==
dependencies:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
version "1.0.11"
@@ -7930,6 +7981,11 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
escape-string-regexp@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
escodegen@^1.13.0, escodegen@^1.8.1:
version "1.14.3"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503"
@@ -8337,6 +8393,14 @@ figures@^3.0.0:
dependencies:
escape-string-regexp "^1.0.5"
figures@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-5.0.0.tgz#126cd055052dea699f8a54e8c9450e6ecfc44d5f"
integrity sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==
dependencies:
escape-string-regexp "^5.0.0"
is-unicode-supported "^1.2.0"
file-type@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-11.1.0.tgz#93780f3fed98b599755d846b99a1617a2ad063b8"
@@ -10027,6 +10091,27 @@ inquirer@^8.2.0:
through "^2.3.6"
wrap-ansi "^7.0.0"
inquirer@^9.2.7:
version "9.2.7"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.2.7.tgz#61e00658efa9b4c76a83c2c3cb3ceb88fec70ac7"
integrity sha512-Bf52lnfvNxGPJPltiNO2tLBp3zC339KNlGMqOkW+dsvNikBhcVDK5kqU2lVX2FTPzuXUFX5WJDlsw//w3ZwoTw==
dependencies:
ansi-escapes "^4.3.2"
chalk "^5.2.0"
cli-cursor "^3.1.0"
cli-width "^4.0.0"
external-editor "^3.0.3"
figures "^5.0.0"
lodash "^4.17.21"
mute-stream "1.0.0"
ora "^5.4.1"
run-async "^3.0.0"
rxjs "^7.8.1"
string-width "^4.2.3"
strip-ansi "^6.0.1"
through "^2.3.6"
wrap-ansi "^6.0.1"
install-artifact-from-github@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.3.tgz#57d89bacfa0f47d7307fe41b6247cda9f9a8079c"
@@ -10423,6 +10508,11 @@ is-unicode-supported@^0.1.0:
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
is-unicode-supported@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714"
integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==
is-url-superb@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-url-superb/-/is-url-superb-4.0.0.tgz#b54d1d2499bb16792748ac967aa3ecb41a33a8c2"
@@ -12078,6 +12168,11 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
mute-stream@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e"
integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==
nan@^2.12.1, nan@^2.17.0:
version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
@@ -14280,6 +14375,11 @@ run-async@^2.4.0:
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
run-async@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad"
integrity sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
@@ -14304,7 +14404,7 @@ rxjs@7.8.0:
dependencies:
tslib "^2.1.0"
rxjs@7.8.1, rxjs@^7.5.5:
rxjs@7.8.1, rxjs@^7.2.0, rxjs@^7.5.5, rxjs@^7.8.1:
version "7.8.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
@@ -16870,7 +16970,7 @@ wrap-ansi@^2.0.0:
string-width "^1.0.1"
strip-ansi "^3.0.1"
wrap-ansi@^6.2.0:
wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==