├── .circleci └── config.yml ├── .dockerignore ├── .env.default ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cli.js ├── docker-compose.yml ├── docker └── files │ └── usr │ └── local │ └── bin │ └── entrypoint ├── lib ├── BBB.js ├── command-helpers.js ├── commands │ ├── list-meetings.js │ └── stress.js ├── stress-test.js └── username.js ├── package.json └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration file anchors 2 | generate-version-file: &generate-version-file 3 | run: 4 | name: Create a version.json 5 | command: | 6 | # Create a version.json à-la-mozilla 7 | # https://github.com/mozilla-services/Dockerflow/blob/master/docs/version_object.md 8 | printf '{"commit":"%s","version":"%s","source":"https://github.com/%s/%s","build":"%s"}\n' \ 9 | "$CIRCLE_SHA1" \ 10 | "$CIRCLE_TAG" \ 11 | "$CIRCLE_PROJECT_USERNAME" \ 12 | "$CIRCLE_PROJECT_REPONAME" \ 13 | "$CIRCLE_BUILD_URL" > version.json 14 | 15 | version: 2 16 | jobs: 17 | # Git jobs 18 | # Check that the git history is clean and complies with our expectations 19 | lint-git: 20 | docker: 21 | - image: circleci/python:3.8-buster 22 | working_directory: ~/fun 23 | steps: 24 | - checkout 25 | # Make sure the changes don't add a "print" statement to the code base. 26 | # We should exclude the ".circleci" folder from the search as the very command that checks 27 | # the absence of "print" is including a "print(" itself. 28 | - run: 29 | name: enforce absence of print statements in code 30 | command: | 31 | ! git diff origin/master..HEAD -- . ':(exclude).circleci' | grep "print(" 32 | - run: 33 | name: Check absence of fixup commits 34 | command: | 35 | ! git log | grep 'fixup!' 36 | - run: 37 | name: Install gitlint 38 | command: | 39 | pip install --user gitlint 40 | - run: 41 | name: lint commit messages added to master 42 | command: | 43 | ~/.local/bin/gitlint --commits origin/master..HEAD 44 | 45 | # Check that the CHANGELOG has been updated in the current branch 46 | check-changelog: 47 | docker: 48 | - image: circleci/buildpack-deps:stretch-scm 49 | working_directory: ~/fun 50 | steps: 51 | - checkout 52 | - run: 53 | name: Check that the CHANGELOG has been modified in the current branch 54 | command: | 55 | git whatchanged --name-only --pretty="" origin..HEAD | grep CHANGELOG 56 | 57 | # Check that the CHANGELOG max line length does not exceed 80 characters 58 | lint-changelog: 59 | docker: 60 | - image: debian:stretch 61 | working_directory: ~/fun 62 | steps: 63 | - checkout 64 | - run: 65 | name: Check CHANGELOG max line length 66 | command: | 67 | # Get the longuest line width (ignoring release links) 68 | test $(cat CHANGELOG.md | grep -Ev "^\[.*\]: https://github.com/openfun" | wc -L) -le 80 69 | 70 | # ---- Docker jobs ---- 71 | # Build the Docker image ready to publish 72 | build-docker: 73 | docker: 74 | - image: circleci/buildpack-deps:stretch 75 | working_directory: ~/fun 76 | steps: 77 | # Checkout repository sources 78 | - checkout 79 | # Generate a version.json file describing app release 80 | - <<: *generate-version-file 81 | # Activate docker-in-docker (with layers caching enabled) 82 | - setup_remote_docker: 83 | docker_layer_caching: true 84 | # Each image is tagged with the current git commit sha1 to avoid collisions in parallel builds. 85 | - run: 86 | name: Build distribution image 87 | command: docker build -t bbb-stress-test:${CIRCLE_SHA1} --target dist . 88 | - run: 89 | name: Check built image availability 90 | command: docker images "bbb-stress-test:${CIRCLE_SHA1}*" 91 | 92 | 93 | # ---- JS related jobs ---- 94 | 95 | # Build development environment 96 | build: 97 | docker: 98 | - image: circleci/node:10 99 | working_directory: ~/fun 100 | steps: 101 | - checkout: 102 | path: ~/fun 103 | - restore_cache: 104 | keys: 105 | - v1-dependencies-{{ checksum "yarn.lock" }} 106 | - v1-dependencies- 107 | # If the yarn.lock file is not up-to-date with the package.json file, 108 | # using the --frozen-lockfile should fail. 109 | - run: 110 | name: Install dependencies 111 | command: yarn install --frozen-lockfile 112 | environment: 113 | - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: "true" 114 | - save_cache: 115 | paths: 116 | - ./node_modules 117 | key: v1-dependencies-{{ checksum "yarn.lock" }} 118 | 119 | lint: 120 | docker: 121 | - image: circleci/node:10 122 | working_directory: ~/fun/ 123 | steps: 124 | - checkout: 125 | path: ~/fun 126 | - restore_cache: 127 | keys: 128 | - v1-dependencies-{{ checksum "yarn.lock" }} 129 | - run: 130 | name: Lint JS/TS/JSON code with prettier 131 | command: yarn prettier --list-different "**/*.+(ts|tsx|json|js|jsx)" "*.+(ts|tsx|json|js|jsx)" 132 | 133 | 134 | # ---- DockerHub publication job ---- 135 | hub: 136 | docker: 137 | - image: circleci/buildpack-deps:stretch 138 | working_directory: ~/fun 139 | steps: 140 | # Checkout repository sources 141 | - checkout 142 | # Generate a version.json file describing app release 143 | - <<: *generate-version-file 144 | # Activate docker-in-docker (with layers caching enabled) 145 | - setup_remote_docker: 146 | docker_layer_caching: true 147 | - run: 148 | name: Build distribution image (using cached layers) 149 | command: docker build -t bbb-stress-test:${CIRCLE_SHA1} --target dist . 150 | - run: 151 | name: Check built images availability 152 | command: docker images "bbb-stress-test:${CIRCLE_SHA1}*" 153 | # Login to DockerHub to Publish new images 154 | # 155 | # Nota bene: you'll need to define the following secrets environment vars 156 | # in CircleCI interface: 157 | # 158 | # - DOCKER_USER 159 | # - DOCKER_PASS 160 | - run: 161 | name: Login to DockerHub 162 | command: echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin 163 | # Tag docker images with the same pattern used in Git (Semantic Versioning) 164 | # 165 | # Git tag: v1.0.1 166 | # Docker tag: 1.0.1(-ci) 167 | - run: 168 | name: Tag images 169 | command: | 170 | docker images fundocker/bbb-stress-test 171 | DOCKER_TAG=$([[ -z "$CIRCLE_TAG" ]] && echo $CIRCLE_BRANCH || echo ${CIRCLE_TAG} | sed 's/^v//') 172 | RELEASE_TYPE=$([[ -z "$CIRCLE_TAG" ]] && echo "branch" || echo "tag ") 173 | # Display either: 174 | # - DOCKER_TAG: master (Git branch) 175 | # or 176 | # - DOCKER_TAG: 1.1.2 (Git tag v1.1.2) 177 | echo "DOCKER_TAG: ${DOCKER_TAG} (Git ${RELEASE_TYPE}${CIRCLE_TAG})" 178 | docker tag bbb-stress-test:${CIRCLE_SHA1} fundocker/bbb-stress-test:${DOCKER_TAG} 179 | if [[ -n "$CIRCLE_TAG" ]]; then 180 | docker tag bbb-stress-test:${CIRCLE_SHA1} fundocker/bbb-stress-test:latest 181 | fi 182 | docker images | grep -E "^fundocker/bbb-stress-test\s*(${DOCKER_TAG}.*|latest|master)" 183 | 184 | # Publish images to DockerHub 185 | # 186 | # Nota bene: logged user (see "Login to DockerHub" step) must have write 187 | # permission for the project's repository; this also implies that the 188 | # DockerHub repository already exists. 189 | - run: 190 | name: Publish images 191 | command: | 192 | DOCKER_TAG=$([[ -z "$CIRCLE_TAG" ]] && echo $CIRCLE_BRANCH || echo ${CIRCLE_TAG} | sed 's/^v//') 193 | RELEASE_TYPE=$([[ -z "$CIRCLE_TAG" ]] && echo "branch" || echo "tag ") 194 | # Display either: 195 | # - DOCKER_TAG: master (Git branch) 196 | # or 197 | # - DOCKER_TAG: 1.1.2 (Git tag v1.1.2) 198 | echo "DOCKER_TAG: ${DOCKER_TAG} (Git ${RELEASE_TYPE}${CIRCLE_TAG})" 199 | docker push fundocker/bbb-stress-test:${DOCKER_TAG} 200 | if [[ -n "$CIRCLE_TAG" ]]; then 201 | docker push fundocker/bbb-stress-test:latest 202 | fi 203 | 204 | workflows: 205 | version: 2 206 | 207 | bbb-stress-test: 208 | jobs: 209 | # Git jobs 210 | # 211 | # Check validity of git history 212 | - lint-git: 213 | filters: 214 | tags: 215 | only: /.*/ 216 | # Check CHANGELOG update 217 | - check-changelog: 218 | filters: 219 | branches: 220 | ignore: master 221 | tags: 222 | only: /(?!^v).*/ 223 | - lint-changelog: 224 | filters: 225 | branches: 226 | ignore: master 227 | tags: 228 | only: /.*/ 229 | 230 | # Docker jobs 231 | # 232 | # Build images 233 | - build-docker: 234 | filters: 235 | tags: 236 | only: /.*/ 237 | 238 | # Backend jobs 239 | # 240 | # Build, lint and test production and development Docker images 241 | # (debian-based) 242 | - build: 243 | filters: 244 | tags: 245 | only: /.*/ 246 | - lint: 247 | requires: 248 | - build 249 | filters: 250 | tags: 251 | only: /.*/ 252 | 253 | # DockerHub publication. 254 | # 255 | # Publish docker images only if all build, lint and test jobs succeed 256 | # and it has been tagged with a tag starting with the letter v or is on 257 | # the master branch 258 | - hub: 259 | requires: 260 | - lint 261 | - build-docker 262 | filters: 263 | branches: 264 | only: master 265 | tags: 266 | only: /^v.*/ 267 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # JS 2 | *.md 3 | *.log 4 | **/node_modules 5 | 6 | # System-specific files 7 | .DS_Store 8 | **/.DS_Store 9 | 10 | # Docker 11 | docker-compose.* 12 | env.d 13 | .env* 14 | 15 | # Docs 16 | docs 17 | *.md 18 | *.log 19 | 20 | # Development/test cache & configurations 21 | .cache 22 | .circleci 23 | .git 24 | .vscode 25 | .idea 26 | .mypy_cache 27 | 28 | -------------------------------------------------------------------------------- /.env.default: -------------------------------------------------------------------------------- 1 | # BBB URL and secret can be retrieved by running bbb-conf --secret on the BBB server. 2 | BBB_URL= 3 | BBB_SECRET= 4 | 5 | # Meeting ID can be retrieved by running `make list-meetings` 6 | BBB_MEETING_ID= 7 | 8 | # Test parameters 9 | BBB_TEST_DURATION=60 10 | BBB_CLIENTS_LISTEN_ONLY=3 11 | BBB_CLIENTS_MIC=2 12 | BBB_CLIENTS_WEBCAM=3 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # Environments 5 | .env 6 | 7 | # Logs 8 | *.log 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic 7 | Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | ## [Unreleased] 10 | 11 | ### Added 12 | 13 | - `cli.js list-meeting` command to list the meetings running on a BBB instance 14 | - `cli.js stress` command to start a stress test on a BBB server 15 | 16 | ### Fixed 17 | 18 | - handle echo test dialog when microphone is active 19 | - invalid mute status when microphone is active 20 | 21 | [Unreleased]: https://github.com/openfun/bbb-stress-test 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10-slim as core 2 | 3 | # Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others) 4 | # Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer 5 | # installs, work. 6 | RUN apt-get update \ 7 | && apt-get install -y wget gnupg \ 8 | && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ 9 | && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ 10 | && apt-get update \ 11 | && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \ 12 | --no-install-recommends \ 13 | && rm -rf /var/lib/apt/lists/* 14 | 15 | COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint 16 | 17 | # Give the "root" group the same permissions as the "root" user on /etc/passwd 18 | # to allow a user belonging to the root group to add new users; typically the 19 | # docker user (see entrypoint). 20 | RUN chmod g=u /etc/passwd 21 | 22 | # We wrap commands run in this container by the following entrypoint that 23 | # creates a user on-the-fly with the container user ID (see USER) and root group 24 | # ID. 25 | ENTRYPOINT [ "/usr/local/bin/entrypoint" ] 26 | 27 | # Un-privileged user running the application 28 | ARG DOCKER_USER=1000 29 | USER ${DOCKER_USER} 30 | 31 | CMD ["google-chrome-unstable"] 32 | 33 | # ---- Development image ---- 34 | 35 | FROM core as development 36 | 37 | CMD ["/bin/bash"] 38 | 39 | # ---- Image to publish ---- 40 | FROM core as dist 41 | 42 | # Switch back to the root user to install dependencies 43 | USER root:root 44 | 45 | COPY . /app/ 46 | WORKDIR /app/ 47 | 48 | # Do not download the chromium version bundled with puppeteer 49 | # We are using google-chrome-unstable instead 50 | ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true 51 | 52 | RUN yarn install --frozen-lockfile 53 | 54 | ARG DOCKER_USER=1000 55 | USER ${DOCKER_USER} 56 | 57 | CMD ["./cli.js", "stress"] 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present GIP FUN MOOC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # /!\ /!\ /!\ /!\ /!\ /!\ /!\ DISCLAIMER /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ 2 | # 3 | # This Makefile is only meant to be used for DEVELOPMENT purpose as we are 4 | # changing the user id that will run in the container. 5 | # 6 | # PLEASE DO NOT USE IT FOR YOUR CI/PRODUCTION/WHATEVER... 7 | # 8 | # /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ 9 | # 10 | # Note to developpers: 11 | # 12 | # While editing this file, please respect the following statements: 13 | # 14 | # 1. Every variable should be defined in the ad hoc VARIABLES section with a 15 | # relevant subsection 16 | # 2. Every new rule should be defined in the ad hoc RULES section with a 17 | # relevant subsection depending on the targeted service 18 | # 3. Rules should be sorted alphabetically within their section 19 | # 4. When a rule has multiple dependencies, you should: 20 | # - duplicate the rule name to add the help string (if required) 21 | # - write one dependency per line to increase readability and diffs 22 | # 5. .PHONY rule statement should be written after the corresponding rule 23 | # ============================================================================== 24 | # VARIABLES 25 | 26 | # -- Docker 27 | # Get the current user ID to use for docker run and docker exec commands 28 | DOCKER_UID = $(shell id -u) 29 | DOCKER_GID = $(shell id -g) 30 | DOCKER_USER = $(DOCKER_UID):$(DOCKER_GID) 31 | 32 | COMPOSE = DOCKER_USER=$(DOCKER_USER) docker-compose 33 | COMPOSE_RUN = $(COMPOSE) run --rm 34 | COMPOSE_RUN_APP = $(COMPOSE_RUN) app 35 | COMPOSE_RUN_NODE = $(COMPOSE_RUN) -e HOME="/tmp" app 36 | 37 | YARN = $(COMPOSE_RUN_NODE) yarn 38 | 39 | # ============================================================================== 40 | # RULES 41 | 42 | default: help 43 | 44 | # -- Test suite 45 | 46 | stress: ## Run stress test 47 | @$(COMPOSE_RUN_APP) ./cli.js stress -v 48 | .PHONY: stress 49 | 50 | list-meetings: ## List meetings running on the BBB server 51 | @$(COMPOSE_RUN_APP) ./cli.js list-meetings 52 | .PHONY: list-meetings 53 | 54 | 55 | # -- Project bootstrap 56 | 57 | .env: 58 | cp .env.default .env 59 | @echo ".env file generated successfully. Please edit it to set BBB_URL, BBB_SECRET and BBB_MEETING_ID" 60 | 61 | 62 | bootstrap: ## Prepare Docker images for the project 63 | bootstrap: \ 64 | .env \ 65 | build 66 | .PHONY: bootstrap 67 | 68 | # -- Build tools 69 | 70 | build: ## Build front-end application 71 | build: \ 72 | build-image \ 73 | install 74 | .PHONY: build 75 | 76 | build-image: ## Build the docker image 77 | docker-compose build app 78 | .PHONY: build-image 79 | 80 | install: ## Install dependencies 81 | @$(YARN) install 82 | .PHONY: install 83 | 84 | # -- Node 85 | 86 | lint: ## Run linters 87 | lint: \ 88 | lint-prettier 89 | .PHONY: lint 90 | 91 | lint-prettier: ## Run prettier over js/jsx/json/ts/tsx files -- beware! overwrites files 92 | @$(YARN) prettier-write 93 | .PHONY: lint-prettier 94 | 95 | node-console: # Run a terminal inside the node docker image 96 | $(COMPOSE_RUN_NODE) bash 97 | .PHONY: node-console 98 | 99 | 100 | # -- Misc 101 | help: 102 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 103 | .PHONY: help 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A stress testing tool for BigBlueButton 2 | 3 | ## Overview 4 | 5 | This is a stress testing tool for [BigBlueButton](https://bigbluebutton.org/). 6 | 7 | It simulates client activity in a BBB conference thanks to [Puppeteer](https://pptr.dev/). 8 | 9 | ## Getting Started 10 | 11 | ### Preparation 12 | 13 | 1) Clone this repository 14 | 15 | 2) Run `make bootstrap` 16 | 17 | 3) Update the generated `.env` file to specify `BBB_URL` and `BBB_SECRET`. \ 18 | You can get these values by running `bbb-conf --secret` on your BBB server. 19 | 20 | ### Ready to launch your test? 21 | 22 | 1) Manually start a meeting on your BBB server. 23 | 24 | 2) Get the meeting ID by running `make list-meetings` 25 | 26 | 3) Update your `.env` file to set the following variables : 27 | - `BBB_MEETING_ID` : the meeting ID 28 | - `BBB_CLIENTS_LISTEN_ONLY`: the number of simultaneous clients to connect in "Listen only" mode 29 | - `BBB_CLIENTS_MIC` : the number of simultaneous clients to connect with an active microphone 30 | - `BBB_CLIENTS_WEBCAM` : the number of simultaneous clients to connect with an active webcam and microphone 31 | - `BBB_TEST_DURATION` : the duration of the test in seconds 32 | 33 | 4) Run `make stress` to launch the test suite 34 | 35 | ## Contributing 36 | 37 | This project is intended to be community-driven, so please, do not hesitate to 38 | get in touch if you have any question related to our implementation or design 39 | decisions. 40 | 41 | We try to raise our code quality standards and expect contributors to follow 42 | the recommandations from our 43 | [handbook](https://openfun.gitbooks.io/handbook/content). 44 | 45 | ## License 46 | 47 | This work is released under the MIT License (see [LICENSE](./LICENSE)). 48 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { getEnv } = require("./lib/command-helpers"); 4 | 5 | const yargs = require("yargs"); 6 | 7 | const listMeetings = require("./lib/commands/list-meetings"); 8 | const stress = require("./lib/commands/stress"); 9 | 10 | const defaultBuilder = (yargs) => yargs; 11 | 12 | const argv = yargs 13 | .command( 14 | listMeetings.name, 15 | listMeetings.description, 16 | listMeetings.options || defaultBuilder, 17 | listMeetings.handler 18 | ) 19 | .command( 20 | stress.name, 21 | stress.description, 22 | stress.options || defaultBuilder, 23 | stress.handler 24 | ) 25 | .option("bbb-url", { 26 | alias: "u", 27 | type: "string", 28 | description: "BBB API url", 29 | }) 30 | .option("bbb-secret", { 31 | alias: "s", 32 | type: "string", 33 | description: "BBB secret", 34 | }) 35 | .option("verbose", { 36 | alias: "v", 37 | type: "boolean", 38 | description: "Run with verbose logging", 39 | }) 40 | .default("bbb-url", getEnv("BBB_URL")) 41 | .default("bbb-secret", getEnv("BBB_SECRET")) 42 | .demandOption( 43 | ["bbb-url", "bbb-secret"], 44 | "Please provide bbb-secret and bbb-url options. You can find the values by running bbb-conf --secret on your BBB server" 45 | ) 46 | .demandCommand(1, "") 47 | .strict().argv; 48 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | target: development 8 | image: bbb-stress-test:development 9 | working_dir: /app/ 10 | user: ${DOCKER_USER:-1000} 11 | cap_add: 12 | - SYS_ADMIN 13 | env_file: 14 | - .env 15 | volumes: 16 | - .:/app 17 | -------------------------------------------------------------------------------- /docker/files/usr/local/bin/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # The container user (see USER in the Dockerfile) is an un-privileged user that 4 | # does not exists and is not created during the build phase (see Dockerfile). 5 | # Hence, we use this entrypoint to wrap commands that will be run in the 6 | # container to create an entry for this user in the /etc/passwd file. 7 | # 8 | # The following environment variables may be passed to the container to 9 | # customize running user account: 10 | # 11 | # * USER_NAME: container user name (default: default) 12 | # * HOME : container user home directory (default: none) 13 | # 14 | # To pass environment variables, you can either use the -e option of the docker run command: 15 | # 16 | # docker run --rm -e USER_NAME=foo -e HOME='/home/foo' ashley:latest python manage.py migrate 17 | # 18 | # or define new variables in an environment file to use with docker or docker-compose: 19 | # 20 | # # env.d/production 21 | # USER_NAME=foo 22 | # HOME=/home/foo 23 | # 24 | # docker run --rm --env-file env.d/production ashley:latest python manage.py migrate 25 | # 26 | 27 | echo "🐳(entrypoint) creating user running in the container..." 28 | if ! whoami > /dev/null 2>&1; then 29 | if [ -w /etc/passwd ]; then 30 | echo "${USER_NAME:-default}:x:$(id -u):$(id -g):${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd 31 | fi 32 | fi 33 | 34 | echo "🐳(entrypoint) running your command: ${*}" 35 | exec "$@" 36 | -------------------------------------------------------------------------------- /lib/BBB.js: -------------------------------------------------------------------------------- 1 | const bbb = require("bigbluebutton-js"); 2 | const _ = require("lodash/fp"); 3 | 4 | class BBB { 5 | constructor(url, secret) { 6 | this.url = url; 7 | this.secret = secret; 8 | this.api = bbb.api(url, secret); 9 | } 10 | 11 | async bbbApiQuery(url) { 12 | const response = await bbb.http(url); 13 | if (response.returncode !== "SUCCESS") { 14 | return Promise.reject(`${response.messageKey} : ${response.message}`); 15 | } 16 | return Promise.resolve(response); 17 | } 18 | 19 | async getMeetings() { 20 | return ( 21 | this.bbbApiQuery(this.api.monitoring.getMeetings()) 22 | .then(_.getOr([], "meetings.meeting")) 23 | // The returned value can be a single element or an array, so we convert it to an array in all cases 24 | .then((meetings) => [].concat(meetings)) 25 | ); 26 | } 27 | 28 | async getMeetingInfo(meetingId) { 29 | return this.bbbApiQuery(this.api.monitoring.getMeetingInfo(meetingId)); 30 | } 31 | 32 | async getAttendeePassword(meetingId) { 33 | return this.getMeetingInfo(meetingId).then(_.get("attendeePW")); 34 | } 35 | 36 | async getModeratorPassword(meetingId) { 37 | return this.getMeetingInfo(meetingId).then(_.get("moderatorPW")); 38 | } 39 | 40 | getJoinUrl(username, meetingID, password) { 41 | return this.api.administration.join(username, meetingID, password); 42 | } 43 | } 44 | 45 | module.exports = BBB; 46 | -------------------------------------------------------------------------------- /lib/command-helpers.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash/fp"); 2 | const winston = require("winston"); 3 | 4 | module.exports = { 5 | getEnv: (envName, defaultValue = undefined) => 6 | _.getOr(defaultValue, envName)(process.env), 7 | 8 | getLogger: (verbose = false) => { 9 | return winston.createLogger({ 10 | level: verbose ? "debug" : "info", 11 | format: winston.format.cli(), 12 | transports: [new winston.transports.Console()], 13 | }); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /lib/commands/list-meetings.js: -------------------------------------------------------------------------------- 1 | const BBB = require("../BBB"); 2 | const { getLogger } = require("../command-helpers"); 3 | const _ = require("lodash/fp"); 4 | 5 | module.exports = { 6 | name: "list-meetings", 7 | 8 | description: "List meetings currently running on BBB", 9 | 10 | handler: (argv) => { 11 | const logger = getLogger(argv.verbose); 12 | const bbbClient = new BBB(argv.u, argv.s); 13 | 14 | bbbClient 15 | .getMeetings() 16 | .then((meetings) => { 17 | if (meetings.length === 0) { 18 | logger.info("No meeting running on the BBB server"); 19 | return; 20 | } 21 | logger.info("Currently running meetings:"); 22 | for (const meeting of meetings) { 23 | logger.info( 24 | ` - ${meeting.meetingID} (${meeting.meetingName}) – participants : ${meeting.participantCount}` 25 | ); 26 | const attendees = [].concat(_.get("attendees.attendee")(meeting)); 27 | if (attendees.length > 0) { 28 | const attendeesNames = _.map("fullName")(attendees); 29 | logger.debug(` [${attendeesNames.join(", ")}]`); 30 | } 31 | } 32 | }) 33 | .catch(logger.error); 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /lib/commands/stress.js: -------------------------------------------------------------------------------- 1 | const { getEnv, getLogger } = require("../command-helpers"); 2 | const BBB = require("../BBB"); 3 | const _ = require("lodash/fp"); 4 | const stressTest = require("../stress-test"); 5 | 6 | const DEFAULT_BBB_TEST_DURATION = 60; 7 | const DEFAULT_BBB_CLIENTS_WEBCAM = 1; 8 | const DEFAULT_BBB_CLIENTS_MIC = 1; 9 | const DEFAULT_BBB_CLIENTS_LISTEN_ONLY = 2; 10 | 11 | module.exports = { 12 | name: "stress [meeting] [options]", 13 | description: "start the stress test", 14 | options: (yargs) => { 15 | yargs 16 | .option("webcams", { 17 | alias: "w", 18 | type: "number", 19 | description: "Number of clients to connect with an active webcam", 20 | }) 21 | .positional("meeting", { 22 | description: "The meeting ID", 23 | type: "string", 24 | }) 25 | .option("microphones", { 26 | alias: "m", 27 | type: "number", 28 | description: "Number of clients to connect with an active microphone", 29 | }) 30 | .option("listening", { 31 | alias: "l", 32 | type: "number", 33 | description: "Number of clients to connect in listen-only mode", 34 | }) 35 | .option("duration", { 36 | alias: "d", 37 | type: "number", 38 | description: 39 | "Duration of the stress test in seconds (after all clients are connected)", 40 | }) 41 | .default("meeting", getEnv("BBB_MEETING_ID")) 42 | .default( 43 | "d", 44 | Number(getEnv("BBB_TEST_DURATION", DEFAULT_BBB_TEST_DURATION)) 45 | ) 46 | .default( 47 | "w", 48 | Number(getEnv("BBB_CLIENTS_WEBCAM", DEFAULT_BBB_CLIENTS_WEBCAM)) 49 | ) 50 | .default("m", Number(getEnv("BBB_CLIENTS_MIC", DEFAULT_BBB_CLIENTS_MIC))) 51 | .default( 52 | "l", 53 | Number( 54 | getEnv("BBB_CLIENTS_LISTEN_ONLY", DEFAULT_BBB_CLIENTS_LISTEN_ONLY) 55 | ) 56 | ) 57 | .demandOption("meeting"); 58 | }, 59 | 60 | handler: (argv) => { 61 | const logger = getLogger(argv.verbose); 62 | const bbbClient = new BBB(argv.u, argv.s); 63 | logger.info(`Starting stress test on meeting ${argv.meeting}`); 64 | logger.info("Test parameters :"); 65 | logger.info(` - webcams : ${argv.webcams}`); 66 | logger.info(` - microphones : ${argv.microphones}`); 67 | logger.info(` - listening : ${argv.listening}`); 68 | logger.info(` - duration : ${argv.duration}s`); 69 | 70 | stressTest 71 | .start( 72 | bbbClient, 73 | logger, 74 | argv.meeting, 75 | argv.duration, 76 | argv.webcams, 77 | argv.microphones, 78 | argv.listening 79 | ) 80 | .catch(console.error); 81 | }, 82 | }; 83 | -------------------------------------------------------------------------------- /lib/stress-test.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require("puppeteer"); 2 | const _ = require("lodash/fp"); 3 | const username = require("./username"); 4 | 5 | const initClient = async ( 6 | browser, 7 | logger, 8 | joinUrl, 9 | webcam = false, 10 | microphone = false 11 | ) => { 12 | const page = await browser.newPage(); 13 | await page.goto(joinUrl); 14 | const audioAction = microphone ? "Microphone" : "Listen only"; 15 | logger.debug(`waiting for audio prompt ([aria-label="${audioAction}"])`); 16 | await page.waitForSelector(`[aria-label="${audioAction}"]`); 17 | logger.debug(`click on ${audioAction}`); 18 | await page.click(`[aria-label="${audioAction}"]`); 19 | if (microphone) { 20 | logger.debug("waiting for the echo test dialog"); 21 | try { 22 | await page.waitForSelector(`[aria-label="Echo is audible"]`); 23 | logger.debug( 24 | 'echo test dialog detected. clicking on "Echo is audible" button.' 25 | ); 26 | await page.click(`[aria-label="Echo is audible"]`); 27 | } catch (err) { 28 | logger.debug( 29 | "unable to detect the echo test dialog. Maybe echo test is disabled." 30 | ); 31 | } 32 | } 33 | await page.waitForSelector(".ReactModal__Overlay", { hidden: true }); 34 | if (microphone) { 35 | logger.debug("Ensure that we are not muted..."); 36 | // Wait for the toolbar to appear 37 | await page.waitForSelector('[aria-label="Mute"],[aria-label="Unmute"]'); 38 | // If we are muted, click on Unmute 39 | const unmuteButton = await page.$('[aria-label="Unmute"]'); 40 | if (unmuteButton !== null) { 41 | logger.debug("clicking on unmute button"); 42 | await unmuteButton.click(); 43 | } 44 | } 45 | if (webcam) { 46 | await page.waitForSelector('[aria-label="Share webcam"]'); 47 | await page.click('[aria-label="Share webcam"]'); 48 | logger.debug("clicked on sharing webcam"); 49 | await new Promise((resolve) => setTimeout(resolve, 2000)); 50 | await page.waitForSelector("#setCam > option"); 51 | await page.waitForSelector('[aria-label="Start sharing"]'); 52 | logger.debug("clicking on start sharing"); 53 | await page.click('[aria-label="Start sharing"]'); 54 | } 55 | return Promise.resolve(page); 56 | }; 57 | 58 | const generateClientConfig = (webcam = false, microphone = false) => { 59 | return { 60 | username: username.getRandom(), 61 | webcam, 62 | microphone, 63 | }; 64 | }; 65 | 66 | async function start( 67 | bbbClient, 68 | logger, 69 | meetingID, 70 | testDuration, 71 | clientWithCamera, 72 | clientWithMicrophone, 73 | clientListening 74 | ) { 75 | const [browser, meetingPassword] = await Promise.all([ 76 | puppeteer.launch({ 77 | executablePath: "google-chrome-unstable", 78 | args: [ 79 | "--use-fake-device-for-media-stream", 80 | "--use-fake-ui-for-media-stream", 81 | "--mute-audio", 82 | ], 83 | }), 84 | bbbClient.getModeratorPassword(meetingID), 85 | ]); 86 | 87 | const clientsConfig = [ 88 | ...[...Array(clientWithCamera)].map(() => generateClientConfig(true, true)), 89 | ...[...Array(clientWithMicrophone)].map(() => 90 | generateClientConfig(false, true) 91 | ), 92 | ...[...Array(clientListening)].map(() => 93 | generateClientConfig(false, false) 94 | ), 95 | ]; 96 | 97 | for (let idx = 0; idx < clientsConfig.length; idx++) { 98 | logger.info(`${clientsConfig[idx].username} join the conference`); 99 | await initClient( 100 | browser, 101 | logger, 102 | bbbClient.getJoinUrl( 103 | clientsConfig[idx].username, 104 | meetingID, 105 | meetingPassword 106 | ), 107 | clientsConfig[idx].webcam, 108 | clientsConfig[idx].microphone 109 | ).catch((err) => { 110 | logger.error( 111 | `Unable to initialize client ${clientsConfig[idx].username} : ${err}` 112 | ); 113 | Promise.resolve(null); 114 | }); 115 | } 116 | 117 | logger.info("All user joined the conference"); 118 | logger.info(`Sleeping ${testDuration}s`); 119 | await new Promise((resolve) => setTimeout(resolve, testDuration * 1000)); 120 | logger.info("Test finished"); 121 | return browser.close(); 122 | } 123 | 124 | module.exports = { 125 | start, 126 | }; 127 | -------------------------------------------------------------------------------- /lib/username.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | 3 | const names = [ 4 | "AlDelvecchio", 5 | "Arnold", 6 | "Ashley", 7 | "Buffalo Bob", 8 | "Chachi", 9 | "Chuck", 10 | "Clarabelle", 11 | "Doris", 12 | "Fonzie", 13 | "Frank", 14 | "Gloria", 15 | "Heather", 16 | "Howard", 17 | "Jenny", 18 | "Joanie", 19 | "K.C.", 20 | "Kirk", 21 | "Leather", 22 | "Lieutenant Colonel Binicky", 23 | "Lieutenant Nurse Quinlan", 24 | "Lori Beth", 25 | "Louisa", 26 | "Marion", 27 | "Marsha", 28 | "Mary Lou", 29 | "Maureen", 30 | "Mork", 31 | "Mother Kelp", 32 | "Nancy", 33 | "Pinky", 34 | "Pizza Delivery girl", 35 | "Potsie", 36 | "Ralph", 37 | "Richie", 38 | "Roger", 39 | "Sergeant", 40 | "Spike", 41 | "Susie", 42 | "Vito", 43 | "Wendy", 44 | ]; 45 | 46 | module.exports = { 47 | getRandom: () => { 48 | return _.sample(names) + _.random(1, 4242); 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bbb-stress-test", 3 | "version": "1.0.0", 4 | "description": "BigBlueButton stress test", 5 | "scripts": { 6 | "prettier": "prettier --list-different '**/*.+(ts|tsx|json|js|jsx)' --ignore-path '../.prettierignore'", 7 | "prettier-write": "prettier --write '**/*.+(ts|tsx|json|js|jsx)' --ignore-path '../.prettierignore'" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/openfun/bbb-stress-test.git" 12 | }, 13 | "keywords": [ 14 | "bigbluebutton", 15 | "load", 16 | "stress", 17 | "testing" 18 | ], 19 | "author": "OpenFun", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/openfun/bbb-stress-test/issues" 23 | }, 24 | "homepage": "https://github.com/openfun/bbb-stress-test#readme", 25 | "dependencies": { 26 | "bigbluebutton-js": "0.0.7", 27 | "lodash": "4.17.15", 28 | "puppeteer": "4.0.0", 29 | "winston": "3.2.1", 30 | "yargs": "15.3.1" 31 | }, 32 | "devDependencies": { 33 | "prettier": "^2.0.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/color-name@^1.1.1": 6 | version "1.1.1" 7 | resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" 8 | integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== 9 | 10 | "@types/node@*": 11 | version "14.0.13" 12 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.13.tgz#ee1128e881b874c371374c1f72201893616417c9" 13 | integrity sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA== 14 | 15 | "@types/yauzl@^2.9.1": 16 | version "2.9.1" 17 | resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" 18 | integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== 19 | dependencies: 20 | "@types/node" "*" 21 | 22 | agent-base@5: 23 | version "5.1.1" 24 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" 25 | integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== 26 | 27 | ansi-regex@^5.0.0: 28 | version "5.0.0" 29 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" 30 | integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== 31 | 32 | ansi-styles@^4.0.0: 33 | version "4.2.1" 34 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" 35 | integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== 36 | dependencies: 37 | "@types/color-name" "^1.1.1" 38 | color-convert "^2.0.1" 39 | 40 | async@^2.6.1: 41 | version "2.6.3" 42 | resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" 43 | integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== 44 | dependencies: 45 | lodash "^4.17.14" 46 | 47 | axios@^0.19.2: 48 | version "0.19.2" 49 | resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" 50 | integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== 51 | dependencies: 52 | follow-redirects "1.5.10" 53 | 54 | balanced-match@^1.0.0: 55 | version "1.0.0" 56 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 57 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 58 | 59 | base64-js@^1.0.2: 60 | version "1.3.1" 61 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" 62 | integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== 63 | 64 | bigbluebutton-js@0.0.7: 65 | version "0.0.7" 66 | resolved "https://registry.yarnpkg.com/bigbluebutton-js/-/bigbluebutton-js-0.0.7.tgz#689c2ee82391ea9acf4f2de6163fd0f78d018580" 67 | integrity sha512-z7vWJaCAeWgoM95RFCsvODHSE0bDkYrTHjXHCQU5+zWqpces7B8xi3DDvJDo3WqsGkrocvSsB+iX0b94BhgtOA== 68 | dependencies: 69 | axios "^0.19.2" 70 | fast-xml-parser "^3.17.2" 71 | 72 | bl@^4.0.1: 73 | version "4.0.2" 74 | resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" 75 | integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== 76 | dependencies: 77 | buffer "^5.5.0" 78 | inherits "^2.0.4" 79 | readable-stream "^3.4.0" 80 | 81 | brace-expansion@^1.1.7: 82 | version "1.1.11" 83 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 84 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 85 | dependencies: 86 | balanced-match "^1.0.0" 87 | concat-map "0.0.1" 88 | 89 | buffer-crc32@~0.2.3: 90 | version "0.2.13" 91 | resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" 92 | integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= 93 | 94 | buffer@^5.2.1, buffer@^5.5.0: 95 | version "5.6.0" 96 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" 97 | integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== 98 | dependencies: 99 | base64-js "^1.0.2" 100 | ieee754 "^1.1.4" 101 | 102 | camelcase@^5.0.0: 103 | version "5.3.1" 104 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 105 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== 106 | 107 | chownr@^1.1.1: 108 | version "1.1.4" 109 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" 110 | integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== 111 | 112 | cliui@^6.0.0: 113 | version "6.0.0" 114 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" 115 | integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== 116 | dependencies: 117 | string-width "^4.2.0" 118 | strip-ansi "^6.0.0" 119 | wrap-ansi "^6.2.0" 120 | 121 | color-convert@^1.9.1: 122 | version "1.9.3" 123 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 124 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 125 | dependencies: 126 | color-name "1.1.3" 127 | 128 | color-convert@^2.0.1: 129 | version "2.0.1" 130 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 131 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 132 | dependencies: 133 | color-name "~1.1.4" 134 | 135 | color-name@1.1.3: 136 | version "1.1.3" 137 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 138 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 139 | 140 | color-name@^1.0.0, color-name@~1.1.4: 141 | version "1.1.4" 142 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 143 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 144 | 145 | color-string@^1.5.2: 146 | version "1.5.3" 147 | resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" 148 | integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== 149 | dependencies: 150 | color-name "^1.0.0" 151 | simple-swizzle "^0.2.2" 152 | 153 | color@3.0.x: 154 | version "3.0.0" 155 | resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" 156 | integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== 157 | dependencies: 158 | color-convert "^1.9.1" 159 | color-string "^1.5.2" 160 | 161 | colornames@^1.1.1: 162 | version "1.1.1" 163 | resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" 164 | integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= 165 | 166 | colors@^1.2.1: 167 | version "1.4.0" 168 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" 169 | integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== 170 | 171 | colorspace@1.1.x: 172 | version "1.1.2" 173 | resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" 174 | integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ== 175 | dependencies: 176 | color "3.0.x" 177 | text-hex "1.0.x" 178 | 179 | concat-map@0.0.1: 180 | version "0.0.1" 181 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 182 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 183 | 184 | core-util-is@~1.0.0: 185 | version "1.0.2" 186 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 187 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 188 | 189 | debug@4, debug@^4.1.0, debug@^4.1.1: 190 | version "4.1.1" 191 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" 192 | integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== 193 | dependencies: 194 | ms "^2.1.1" 195 | 196 | debug@=3.1.0: 197 | version "3.1.0" 198 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 199 | integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== 200 | dependencies: 201 | ms "2.0.0" 202 | 203 | decamelize@^1.2.0: 204 | version "1.2.0" 205 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 206 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 207 | 208 | diagnostics@^1.1.1: 209 | version "1.1.1" 210 | resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" 211 | integrity sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ== 212 | dependencies: 213 | colorspace "1.1.x" 214 | enabled "1.0.x" 215 | kuler "1.0.x" 216 | 217 | emoji-regex@^8.0.0: 218 | version "8.0.0" 219 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 220 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 221 | 222 | enabled@1.0.x: 223 | version "1.0.2" 224 | resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" 225 | integrity sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M= 226 | dependencies: 227 | env-variable "0.0.x" 228 | 229 | end-of-stream@^1.1.0, end-of-stream@^1.4.1: 230 | version "1.4.4" 231 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 232 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 233 | dependencies: 234 | once "^1.4.0" 235 | 236 | env-variable@0.0.x: 237 | version "0.0.6" 238 | resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.6.tgz#74ab20b3786c545b62b4a4813ab8cf22726c9808" 239 | integrity sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg== 240 | 241 | extract-zip@^2.0.0: 242 | version "2.0.1" 243 | resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" 244 | integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== 245 | dependencies: 246 | debug "^4.1.1" 247 | get-stream "^5.1.0" 248 | yauzl "^2.10.0" 249 | optionalDependencies: 250 | "@types/yauzl" "^2.9.1" 251 | 252 | fast-safe-stringify@^2.0.4: 253 | version "2.0.7" 254 | resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" 255 | integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== 256 | 257 | fast-xml-parser@^3.17.2: 258 | version "3.17.4" 259 | resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.17.4.tgz#d668495fb3e4bbcf7970f3c24ac0019d82e76477" 260 | integrity sha512-qudnQuyYBgnvzf5Lj/yxMcf4L9NcVWihXJg7CiU1L+oUCq8MUnFEfH2/nXR/W5uq+yvUN1h7z6s7vs2v1WkL1A== 261 | 262 | fd-slicer@~1.1.0: 263 | version "1.1.0" 264 | resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" 265 | integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= 266 | dependencies: 267 | pend "~1.2.0" 268 | 269 | fecha@^2.3.3: 270 | version "2.3.3" 271 | resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" 272 | integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg== 273 | 274 | find-up@^4.1.0: 275 | version "4.1.0" 276 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" 277 | integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== 278 | dependencies: 279 | locate-path "^5.0.0" 280 | path-exists "^4.0.0" 281 | 282 | follow-redirects@1.5.10: 283 | version "1.5.10" 284 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" 285 | integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== 286 | dependencies: 287 | debug "=3.1.0" 288 | 289 | fs-constants@^1.0.0: 290 | version "1.0.0" 291 | resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" 292 | integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== 293 | 294 | fs.realpath@^1.0.0: 295 | version "1.0.0" 296 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 297 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 298 | 299 | get-caller-file@^2.0.1: 300 | version "2.0.5" 301 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 302 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 303 | 304 | get-stream@^5.1.0: 305 | version "5.1.0" 306 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" 307 | integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== 308 | dependencies: 309 | pump "^3.0.0" 310 | 311 | glob@^7.1.3: 312 | version "7.1.6" 313 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 314 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 315 | dependencies: 316 | fs.realpath "^1.0.0" 317 | inflight "^1.0.4" 318 | inherits "2" 319 | minimatch "^3.0.4" 320 | once "^1.3.0" 321 | path-is-absolute "^1.0.0" 322 | 323 | https-proxy-agent@^4.0.0: 324 | version "4.0.0" 325 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" 326 | integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== 327 | dependencies: 328 | agent-base "5" 329 | debug "4" 330 | 331 | ieee754@^1.1.4: 332 | version "1.1.13" 333 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" 334 | integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== 335 | 336 | inflight@^1.0.4: 337 | version "1.0.6" 338 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 339 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 340 | dependencies: 341 | once "^1.3.0" 342 | wrappy "1" 343 | 344 | inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: 345 | version "2.0.4" 346 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 347 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 348 | 349 | is-arrayish@^0.3.1: 350 | version "0.3.2" 351 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" 352 | integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== 353 | 354 | is-fullwidth-code-point@^3.0.0: 355 | version "3.0.0" 356 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 357 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 358 | 359 | is-stream@^1.1.0: 360 | version "1.1.0" 361 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 362 | integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= 363 | 364 | isarray@~1.0.0: 365 | version "1.0.0" 366 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 367 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 368 | 369 | kuler@1.0.x: 370 | version "1.0.1" 371 | resolved "https://registry.yarnpkg.com/kuler/-/kuler-1.0.1.tgz#ef7c784f36c9fb6e16dd3150d152677b2b0228a6" 372 | integrity sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ== 373 | dependencies: 374 | colornames "^1.1.1" 375 | 376 | locate-path@^5.0.0: 377 | version "5.0.0" 378 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" 379 | integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== 380 | dependencies: 381 | p-locate "^4.1.0" 382 | 383 | lodash@4.17.15, lodash@^4.17.14: 384 | version "4.17.15" 385 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" 386 | integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== 387 | 388 | logform@^2.1.1: 389 | version "2.1.2" 390 | resolved "https://registry.yarnpkg.com/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360" 391 | integrity sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ== 392 | dependencies: 393 | colors "^1.2.1" 394 | fast-safe-stringify "^2.0.4" 395 | fecha "^2.3.3" 396 | ms "^2.1.1" 397 | triple-beam "^1.3.0" 398 | 399 | mime@^2.0.3: 400 | version "2.4.6" 401 | resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" 402 | integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== 403 | 404 | minimatch@^3.0.4: 405 | version "3.0.4" 406 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 407 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 408 | dependencies: 409 | brace-expansion "^1.1.7" 410 | 411 | mitt@^2.0.1: 412 | version "2.0.1" 413 | resolved "https://registry.yarnpkg.com/mitt/-/mitt-2.0.1.tgz#9e8a075b4daae82dd91aac155a0ece40ca7cb393" 414 | integrity sha512-FhuJY+tYHLnPcBHQhbUFzscD5512HumCPE4URXZUgPi3IvOJi4Xva5IIgy3xX56GqCmw++MAm5UURG6kDBYTdg== 415 | 416 | mkdirp-classic@^0.5.2: 417 | version "0.5.3" 418 | resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" 419 | integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== 420 | 421 | ms@2.0.0: 422 | version "2.0.0" 423 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 424 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 425 | 426 | ms@^2.1.1: 427 | version "2.1.2" 428 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 429 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 430 | 431 | once@^1.3.0, once@^1.3.1, once@^1.4.0: 432 | version "1.4.0" 433 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 434 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 435 | dependencies: 436 | wrappy "1" 437 | 438 | one-time@0.0.4: 439 | version "0.0.4" 440 | resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" 441 | integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4= 442 | 443 | p-limit@^2.2.0: 444 | version "2.3.0" 445 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" 446 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== 447 | dependencies: 448 | p-try "^2.0.0" 449 | 450 | p-locate@^4.1.0: 451 | version "4.1.0" 452 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" 453 | integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== 454 | dependencies: 455 | p-limit "^2.2.0" 456 | 457 | p-try@^2.0.0: 458 | version "2.2.0" 459 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 460 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 461 | 462 | path-exists@^4.0.0: 463 | version "4.0.0" 464 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 465 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 466 | 467 | path-is-absolute@^1.0.0: 468 | version "1.0.1" 469 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 470 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 471 | 472 | pend@~1.2.0: 473 | version "1.2.0" 474 | resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" 475 | integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= 476 | 477 | prettier@^2.0.5: 478 | version "2.0.5" 479 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" 480 | integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== 481 | 482 | process-nextick-args@~2.0.0: 483 | version "2.0.1" 484 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 485 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 486 | 487 | progress@^2.0.1: 488 | version "2.0.3" 489 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" 490 | integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== 491 | 492 | proxy-from-env@^1.0.0: 493 | version "1.1.0" 494 | resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" 495 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 496 | 497 | pump@^3.0.0: 498 | version "3.0.0" 499 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 500 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 501 | dependencies: 502 | end-of-stream "^1.1.0" 503 | once "^1.3.1" 504 | 505 | puppeteer@4.0.0: 506 | version "4.0.0" 507 | resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-4.0.0.tgz#96d647c3546119f8e670493bcebf9ddb044a2367" 508 | integrity sha512-yNshT0M5DWfZ8DQoduZuGYpcwqXxKOZdgt5G0IF5VEKbydaDbWP/f5pQRfzQ4e+a4w0Rge3uzcogHeUPQM8nCA== 509 | dependencies: 510 | debug "^4.1.0" 511 | extract-zip "^2.0.0" 512 | https-proxy-agent "^4.0.0" 513 | mime "^2.0.3" 514 | mitt "^2.0.1" 515 | progress "^2.0.1" 516 | proxy-from-env "^1.0.0" 517 | rimraf "^3.0.2" 518 | tar-fs "^2.0.0" 519 | unbzip2-stream "^1.3.3" 520 | ws "^7.2.3" 521 | 522 | readable-stream@^2.3.6: 523 | version "2.3.7" 524 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 525 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 526 | dependencies: 527 | core-util-is "~1.0.0" 528 | inherits "~2.0.3" 529 | isarray "~1.0.0" 530 | process-nextick-args "~2.0.0" 531 | safe-buffer "~5.1.1" 532 | string_decoder "~1.1.1" 533 | util-deprecate "~1.0.1" 534 | 535 | readable-stream@^3.1.1, readable-stream@^3.4.0: 536 | version "3.6.0" 537 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 538 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 539 | dependencies: 540 | inherits "^2.0.3" 541 | string_decoder "^1.1.1" 542 | util-deprecate "^1.0.1" 543 | 544 | require-directory@^2.1.1: 545 | version "2.1.1" 546 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 547 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 548 | 549 | require-main-filename@^2.0.0: 550 | version "2.0.0" 551 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 552 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 553 | 554 | rimraf@^3.0.2: 555 | version "3.0.2" 556 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 557 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 558 | dependencies: 559 | glob "^7.1.3" 560 | 561 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 562 | version "5.1.2" 563 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 564 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 565 | 566 | safe-buffer@~5.2.0: 567 | version "5.2.1" 568 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 569 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 570 | 571 | set-blocking@^2.0.0: 572 | version "2.0.0" 573 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 574 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 575 | 576 | simple-swizzle@^0.2.2: 577 | version "0.2.2" 578 | resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" 579 | integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= 580 | dependencies: 581 | is-arrayish "^0.3.1" 582 | 583 | stack-trace@0.0.x: 584 | version "0.0.10" 585 | resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" 586 | integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= 587 | 588 | string-width@^4.1.0, string-width@^4.2.0: 589 | version "4.2.0" 590 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" 591 | integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== 592 | dependencies: 593 | emoji-regex "^8.0.0" 594 | is-fullwidth-code-point "^3.0.0" 595 | strip-ansi "^6.0.0" 596 | 597 | string_decoder@^1.1.1: 598 | version "1.3.0" 599 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 600 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 601 | dependencies: 602 | safe-buffer "~5.2.0" 603 | 604 | string_decoder@~1.1.1: 605 | version "1.1.1" 606 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 607 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 608 | dependencies: 609 | safe-buffer "~5.1.0" 610 | 611 | strip-ansi@^6.0.0: 612 | version "6.0.0" 613 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" 614 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== 615 | dependencies: 616 | ansi-regex "^5.0.0" 617 | 618 | tar-fs@^2.0.0: 619 | version "2.1.0" 620 | resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5" 621 | integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg== 622 | dependencies: 623 | chownr "^1.1.1" 624 | mkdirp-classic "^0.5.2" 625 | pump "^3.0.0" 626 | tar-stream "^2.0.0" 627 | 628 | tar-stream@^2.0.0: 629 | version "2.1.2" 630 | resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" 631 | integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q== 632 | dependencies: 633 | bl "^4.0.1" 634 | end-of-stream "^1.4.1" 635 | fs-constants "^1.0.0" 636 | inherits "^2.0.3" 637 | readable-stream "^3.1.1" 638 | 639 | text-hex@1.0.x: 640 | version "1.0.0" 641 | resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" 642 | integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== 643 | 644 | through@^2.3.8: 645 | version "2.3.8" 646 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 647 | integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 648 | 649 | triple-beam@^1.2.0, triple-beam@^1.3.0: 650 | version "1.3.0" 651 | resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" 652 | integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== 653 | 654 | unbzip2-stream@^1.3.3: 655 | version "1.4.3" 656 | resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" 657 | integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== 658 | dependencies: 659 | buffer "^5.2.1" 660 | through "^2.3.8" 661 | 662 | util-deprecate@^1.0.1, util-deprecate@~1.0.1: 663 | version "1.0.2" 664 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 665 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 666 | 667 | which-module@^2.0.0: 668 | version "2.0.0" 669 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 670 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 671 | 672 | winston-transport@^4.3.0: 673 | version "4.3.0" 674 | resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.3.0.tgz#df68c0c202482c448d9b47313c07304c2d7c2c66" 675 | integrity sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A== 676 | dependencies: 677 | readable-stream "^2.3.6" 678 | triple-beam "^1.2.0" 679 | 680 | winston@3.2.1: 681 | version "3.2.1" 682 | resolved "https://registry.yarnpkg.com/winston/-/winston-3.2.1.tgz#63061377976c73584028be2490a1846055f77f07" 683 | integrity sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw== 684 | dependencies: 685 | async "^2.6.1" 686 | diagnostics "^1.1.1" 687 | is-stream "^1.1.0" 688 | logform "^2.1.1" 689 | one-time "0.0.4" 690 | readable-stream "^3.1.1" 691 | stack-trace "0.0.x" 692 | triple-beam "^1.3.0" 693 | winston-transport "^4.3.0" 694 | 695 | wrap-ansi@^6.2.0: 696 | version "6.2.0" 697 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" 698 | integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== 699 | dependencies: 700 | ansi-styles "^4.0.0" 701 | string-width "^4.1.0" 702 | strip-ansi "^6.0.0" 703 | 704 | wrappy@1: 705 | version "1.0.2" 706 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 707 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 708 | 709 | ws@^7.2.3: 710 | version "7.3.0" 711 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" 712 | integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w== 713 | 714 | y18n@^4.0.0: 715 | version "4.0.0" 716 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" 717 | integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== 718 | 719 | yargs-parser@^18.1.1: 720 | version "18.1.3" 721 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" 722 | integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== 723 | dependencies: 724 | camelcase "^5.0.0" 725 | decamelize "^1.2.0" 726 | 727 | yargs@15.3.1: 728 | version "15.3.1" 729 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" 730 | integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== 731 | dependencies: 732 | cliui "^6.0.0" 733 | decamelize "^1.2.0" 734 | find-up "^4.1.0" 735 | get-caller-file "^2.0.1" 736 | require-directory "^2.1.1" 737 | require-main-filename "^2.0.0" 738 | set-blocking "^2.0.0" 739 | string-width "^4.2.0" 740 | which-module "^2.0.0" 741 | y18n "^4.0.0" 742 | yargs-parser "^18.1.1" 743 | 744 | yauzl@^2.10.0: 745 | version "2.10.0" 746 | resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" 747 | integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= 748 | dependencies: 749 | buffer-crc32 "~0.2.3" 750 | fd-slicer "~1.1.0" 751 | --------------------------------------------------------------------------------