diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..92bd33894e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +packages/server/node_modules +packages/builder +packages/frontend-core +packages/backend-core +packages/worker/node_modules +packages/cli +packages/client +packages/bbui +packages/string-templates diff --git a/.eslintignore b/.eslintignore index 91f5433596..54824be5c7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,4 +6,5 @@ packages/server/coverage packages/server/client packages/builder/.routify packages/builder/cypress/support/queryLevelTransformerFunction.js -packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js \ No newline at end of file +packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js +packages/builder/cypress/reports \ No newline at end of file diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 1be855e3fa..0000000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at community@budibase.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 120000 index 0000000000..d28e66efdc --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +../docs/CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 2a57d6f388..0000000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,206 +0,0 @@ -# Contributing - -From opening a bug report to creating a pull request: every contribution is appreciated and welcome. If you're planning to implement a new feature or change the api please create an issue first. This way we can ensure that your precious work is not in vain. - -## Not Sure Where to Start? - -Budibase is a low-code web application builder that creates svelte based web applications. - -Budibase is a monorepo managed by [lerna](https://github.com/lerna/lerna). Lerna manages the building and publishing of the budibase packages. At a high level, here are the packages that make up budibase. - -- **packages/builder** - contains code for the budibase builder client side svelte application. - -- **packages/client** - A module that runs in the browser responsible for reading JSON definition and creating living, breathing web apps from it. - -- **packages/server** - The budibase server. This [Koa](https://koajs.com/) app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system. - -- **packages/worker** - This [Koa](https://koajs.com/) app is responsible for providing global apis for managing your budibase installation. Authentication, Users, Email, Org and Auth configs are all provided by the worker. - -## Contributor License Agreement (CLA) - -In order to accept your pull request, we need you to submit a CLA. You only need to do this once. If you are submitting a pull request for the first time, just submit a Pull Request and our CLA Bot will give you instructions on how to sign the CLA before merging your Pull Request. - -All contributors must sign an [Individual Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/individual-cla.md). - -If contributing on behalf of your company, your company must sign a [Corporate Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/corporate-cla.md). If so, please contact us via community@budibase.com. - -## Glossary of Terms - -To understand the budibase API, it can be helpful to understand the top level entities that make up Budibase. - -### Client - -A client represents a single budibase customer. Each budibase client will have 1 or more budibase servers. Every client is assigned a unique ID. - -### App - -A client can have one or more budibase applications. Budibase applications would be things like "Developer Inventory Management" or "Goat Herder CRM". Think of a budibase application as a tree. - -### Database - -An App can have one or more databases. Keeping with our [dendrology](https://en.wikipedia.org/wiki/Dendrology) analogy - think of an database as a branch on the tree. Databases are used to keep data separate for different instances of your app. For example, if you had a CRM app, you may create a database for your US office, and a database for your Australian office. Databases allow us to support [multitenancy](https://www.gartner.com/en/information-technology/glossary/multitenancy) in budibase applications. - -### Table - -Tables in budibase are almost akin to tables in relational databases. A table may be a "Car" or an "Employee". They are the main building blocks for the creation and management of backend data in budibase. - -### View - -A View is an advanced feature in budibase that allows you to write a custom query using [MapReduce](https://pouchdb.com/guides/queries.html) queries. Views enable powerful query functionality and calculations, allowing you to do more with your data. - -### Page - -A page in budibase is actually a single, self contained svelte web app. There are only 2 pages in budibase. The **login** page and the **main** page. - -### Screen - -A screen is a component within a single page. Generally, screens represent client side routes, and can be switched without refreshing the page. - -### Component - -A component is the basic frontend building block of a budibase app. - -### Component Library - -Component libraries are collections of components as well as the definition of their props contained in a file called `components.json`. - -## Contributing to Budibase - -* Please maintain the existing code style. - -* Please try to keep your commits small and focused. - -* Please write tests. - -* If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read. - -* Once your work is completed, please raise a PR against the `develop` branch with some information about what has changed and why. - -### Getting Started For Contributors -#### 1. Prerequisites - -NodeJS Version `14.x.x` - -*yarn -* `npm install -g yarn` - -*jest* - `npm install -g jest` - -#### 2. Clone this repository - -`git clone https://github.com/Budibase/budibase.git` - -then `cd ` into your local copy. - -#### 3. Install and Build - -To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed. - -##### Quick method - -`yarn setup` will check that all necessary components are installed and setup the repo for usage. - -##### Manual method - -The following commands can be executed to manually get Budibase up and running (assuming Docker/Docker Compose has been installed). - -`yarn` to install project dependencies - -`yarn bootstrap` will install all budibase modules and symlink them together using lerna. - -`yarn build` will build all budibase packages. - -#### 4. Running - -To run the budibase server and builder in dev mode (i.e. with live reloading): - -1. Open a new console -2. `yarn dev` (from root) -3. Access the builder on http://localhost:10000/builder - -This will enable watch mode for both the builder app, server, client library and any component libraries. - -#### 5. Debugging using VS Code - -To debug the budibase server and worker a VS Code launch configuration has been provided. - -Visit the debug window and select `Budibase Server` or `Budibase Worker` to debug the respective component. -Alternatively to start both components simultaneously select `Start Budibase`. - -In addition to the above, the remaining budibase components may be ran in dev mode using: `yarn dev:noserver`. - -#### 6. Cleanup - -If you wish to delete all the apps created in development and reset the environment then run the following: - -1. `yarn nuke:docker` will wipe all the Budibase services -2. `yarn dev` will restart all the services - -### Backend - -For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apache.org/), [MinIO](https://min.io/) and [NGINX](https://www.nginx.com/) in Docker compose. This means that to develop Budibase you will need Docker and Docker compose installed. The backend services are then ran separately as Node services with nodemon so that they can be debugged outside of Docker. - -### Data Storage - -When you are running locally, budibase stores data on disk using docker volumes. The volumes and the types of data associated with each are: - -- `redis_data` - - Sessions, email tokens -- `couchdb3_data` - - Global and app databases -- `minio_data` - - App manifest, budibase client, static assets - -### Devlopment Modes - -A combination of environment variables controls the mode that budibase runs in. -Yarn commands can be used to mimic the different modes that budibase can be ran in - -#### Self Hosted -The default mode. A single tenant installation with no usage restrictions. - -To enable this mode, use: -``` -yarn mode:self -``` - -#### Cloud -The cloud mode, with account portal turned off. - -To enable this mode, use: -``` -yarn mode:cloud -``` -#### Cloud & Account -The cloud mode, with account portal turned on. This is a replica of the mode that runs at https://budibase.app - - -To enable this mode, use: -``` -yarn mode:account -``` -### CI - An overview of the CI pipelines can be found [here](./workflows/README.md) -### Troubleshooting - -Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above. You should have a fresh Budibase installation. -### Running tests - -#### End-to-end Tests - -Budibase uses Cypress to run a number of E2E tests. To run the tests execute the following command in the root folder: - -``` -yarn test:e2e -``` - -Or if you are in the builder you can run `yarn cy:test`. - - -### Other Useful Information - -* The contributors are listed in [AUTHORS.md](https://github.com/Budibase/budibase/blob/master/.github/AUTHORS.md) (add yourself). - -* This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE). - -* We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions. - Please read this if you are unfamiliar with it. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 120000 index 0000000000..c81d5e88a2 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1 @@ +../docs/CONTRIBUTING.md \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d1ba2bc046..457d2c1451 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,15 @@ assignees: '' --- +**Hosting** + +- Self + - Method: + - Budibase Version: + - App Version: +- Cloud + - Tenant ID: + **Describe the bug** A clear and concise description of what the bug is. @@ -22,6 +31,9 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. + +**App Export** +If possible - please attach an export of your budibase application for debugging/reproduction purposes. **Desktop (please complete the following information):** - OS: [e.g. iOS] diff --git a/.github/stale.yml b/.github/stale.yml index 5875ed1282..2a2c10cb7d 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -14,7 +14,6 @@ staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. + recent activity. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false diff --git a/.github/workflows/README.md b/.github/workflows/README.md index d2fcd16bb0..c33665c964 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -6,7 +6,7 @@ Welcome to the budibase CI pipelines directory. This document details what each ## All CI Pipelines ### Note -- When running workflow dispatch jobs, ensure you always run them off the `master` branch. It defaults to `develop`, so double check before running any jobs. +- When running workflow dispatch jobs, ensure you always run them off the `master` branch. It defaults to `develop`, so double check before running any jobs. The exception to this case is the `deploy-release` job which requires the develop branch. ### Standard CI Build Job (budibase_ci.yml) Triggers: @@ -24,14 +24,14 @@ The standard CI Build job is what runs when you raise a PR to develop or master. Triggers: - Push to develop -The job responsible for building, tagging and pushing docker images out to the test and staging environments. +The job responsible for building, tagging and pushing docker images out to the test and release environments. - Installs all dependencies - builds the project - run the unit tests - publish the budibase JS packages under a prerelease tag to NPM - build, tag and push docker images under the `develop` tag to docker hub -These images will then be pulled by the test and staging environments, updating the latest automatically. Discord notifications are sent to the #infra channel when this occurs. +These images will then be pulled by the test and release environments, updating the latest automatically. Discord notifications are sent to the #infra channel when this occurs. ### Release Job (release.yml) Triggers: @@ -57,8 +57,33 @@ This job relies on the release job to have run first, so the latest image is pus - Build and release the budibase helm chart for kubernetes users - Perform a github release with the latest version. You can see previous releases here (https://github.com/Budibase/budibase/releases) +### Deploy Release (deploy-release.yml) +Triggers: +- Manual Workflow Dispatch Trigger -### Cloud Deploy (deploy-cloud.yml) +This job is responsible for deploying to our release, cloud kubernetes environment. You must run the release job first, to ensure that the latest images have been built and pushed to docker hub. After kicking off this job, the following will occur: + +- Checks out the release branch +- Pulls the latest `values.yaml` from budibase infra, a private repo containing budibases infrastructure configuration +- Gets the latest budibase version from `lerna.json`, if it hasn't been specified in the workflow when you kicked it off +- Configures AWS Credentials +- Deploys the helm chart in the budibase repo to our preproduction EKS cluster, injecting the `values.yaml` we pulled from budibase-infra +- Fires off a discord webhook in the #infra channel to show that the deployment completely successfully. + +### Deploy Preprod (deploy-preprod.yml) +Triggers: +- Manual Workflow Dispatch Trigger + +This job is responsible for deploying to our preprod, cloud kubernetes environment. You must run the release job first, to ensure that the latest images have been built and pushed to docker hub. After kicking off this job, the following will occur: + +- Checks out the master branch +- Pulls the latest `values.yaml` from budibase infra, a private repo containing budibases infrastructure configuration +- Gets the latest budibase version from `lerna.json`, if it hasn't been specified in the workflow when you kicked it off +- Configures AWS Credentials +- Deploys the helm chart in the budibase repo to our preprod EKS cluster, injecting the `values.yaml` we pulled from budibase-infra +- Fires off a discord webhook in the #infra channel to show that the deployment completely successfully. + +### Deploy Production (deploy-cloud.yml) Triggers: - Manual Workflow Dispatch Trigger @@ -90,4 +115,75 @@ This job is responsible for deploying to our production, cloud kubernetes enviro ### Rollback A Bad Cloud Deployment - Kick off cloud deploy job - Ensure you are running off master -- Enter the version number of the last known good version of budibase. For example `1.0.0` \ No newline at end of file +- Enter the version number of the last known good version of budibase. For example `1.0.0` + +## Pro + +### Installing Pro + +The pro package is always installed from source in our CI jobs. + +This is done to prevent pro needing to be published prior to CI runs in budiabse. This is required for two reasons: +- To reduce developer need to manually bump versions, i.e: + - release pro, bump pro dep in budibase, now ci can run successfully +- The cyclic dependency on backend-core, i.e: + - pro depends on backend-core + - server depends on pro + - backend-core lives in the monorepo, so it can't be released independently to be used in pro + - therefore the only option is to pull pro from source and release it as a part of the monorepo release, as if it were a mono package + +The install is performed using the same steps as local development, via the `yarn bootstrap` command, see the [Contributing Guide#Pro](../CONTRIBUTING.md#pro) + +The branch to install pro from can vary depending on ref of the commit that triggered the budibase CI job. This is done to enable branches which have changes in both the monorepo and the pro repo to have their CI pass successfully. + +This is done using the [pro/install.sh](../../scripts/pro/install.sh) script. The script will: +- Clone pro to it's default branch (`develop`) +- Check if the clone worked, on forked versions of budibase this will fail due to no access + - This is fine as the `yarn` command will install the version from NPM + - Community PRs should never touch pro so this will always work +- Checkout the `BRANCH` argument, if this fails fallback to `BASE_BRANCH` + - This enables the more complex case of a feature branch being merged to another feature branch, e.g. + - I am working on a branch `epic/stonks` which exists on budibase and pro. + - I want to merge a change to this branch in budibase from `feature/stonks-ui`, which only exists in budibase + - The base branch ensures that `epic/stonks` in pro will still be checked out for the CI run, rather than falling back to `develop` +- Run `yarn setup` to build and install dependencies + - `yarn` + - `yarn bootstrap` + - `yarn build` + - The will build .ts files, and also update the `main` and `types` of `package.json` to point to `dist` rather than src + - The build command will only ever work in CI, it is prevented in local dev + +#### `BRANCH` and `BASE_BRANCH` arguments +These arguments are supplied by the various budibase build and release pipelines +- `budibase_ci` + - `BRANCH: ${{ github.event.pull_request.head.ref }}` -> The branch being merged + - `BASE_BRANCH: ${{ github.event.pull_request.base.ref}}` -> The base branch +- `release-develop` + - `BRANCH: develop` -> always use the `develop` branch in pro +- `release` + - `BRANCH: master` -> always use the `master` branch in pro + + +### Releasing Pro +After budibase dependencies have been released we will release the new version of pro to match the release version of budibase dependencies. This is to ensure that we are always keeping the version of `backend-core` in sync in the pro package and in budibase packages. Without this we could run into scenarios where different versions are being used when installed via `yarn` inside the docker images, creating very difficult to debug cases. + +Pro is released using the [pro/release.sh](../../scripts/pro/release.sh) script. The script will: +- Inspect the `VERSION` from the `lerna.json` file in budibase +- Determine whether to use the `latest` or `develop` tag based on the command argument +- Go to pro directory + - install npm creds + - update the version of `backend-core` to be `VERSION`, the version just released by lerna + - publish to npm. Uses a `lerna publish` command, pro itself is a mono repo. + - force the version to be the same as `VERSION` to keep pro and budibase in sync + - reverts the changes to `main` and `types` in `package.json` that were made by the build step, to point back to source + - commit & push: `Prep next development iteration` +- Go to budibase + - Update to the new version of pro in `server` and `worker` so the latest pro version is used in the docker builds + - commit & push: `Update pro version to $VERSION` + + +#### `COMMAND` argument +This argument is supplied by the existing `release` and `release:develop` budibase commands, which invoke the pro release +- `release` will supply no command and default to use `latest` +- `release:develop` will supply `develop` + diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index cb27b30f3f..e940e6fa10 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -11,6 +11,13 @@ on: branches: - master - develop + - release + workflow_dispatch: + +env: + BRANCH: ${{ github.event.pull_request.head.ref }} + BASE_BRANCH: ${{ github.event.pull_request.base.ref}} + PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }} jobs: build: @@ -27,6 +34,10 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} + + - name: Install Pro + run: yarn install:pro $BRANCH $BASE_BRANCH + - run: yarn - run: yarn bootstrap - run: yarn lint diff --git a/.github/workflows/deploy-cloud.yaml b/.github/workflows/deploy-cloud.yaml index db03165a91..869a88a5b3 100644 --- a/.github/workflows/deploy-cloud.yaml +++ b/.github/workflows/deploy-cloud.yaml @@ -1,4 +1,4 @@ -name: Budibase Cloud Deploy +name: Budibase Deploy Production on: workflow_dispatch: @@ -66,7 +66,7 @@ jobs: config-files: values.production.yaml chart-path: charts/budibase namespace: budibase - values: globals.appVersion=v${{ env.RELEASE_VERSION }} + values: globals.appVersion=v${{ env.RELEASE_VERSION }},services.couchdb.url=${{ secrets.PRODUCTION_COUCHDB_URL }},services.couchdb.password=${{ secrets.PRODUCTION_COUCHDB_PASSWORD }} name: budibase-prod - name: Discord Webhook Action diff --git a/.github/workflows/deploy-preprod.yml b/.github/workflows/deploy-preprod.yml index ac76e9bed8..c3f690f568 100644 --- a/.github/workflows/deploy-preprod.yml +++ b/.github/workflows/deploy-preprod.yml @@ -1,12 +1,10 @@ -name: Budibase Release Preprod +name: Budibase Deploy Preprod on: workflow_dispatch: env: - POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} - POSTHOG_URL: ${{ secrets.POSTHOG_URL }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }} jobs: diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml new file mode 100644 index 0000000000..0fb8a5fea0 --- /dev/null +++ b/.github/workflows/deploy-release.yml @@ -0,0 +1,77 @@ +name: Budibase Deploy Release + +on: + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + + - name: Fail if branch is not develop + if: github.ref != 'refs/heads/develop' + run: | + echo "Ref is not develop, you must run this job from develop." + exit 1 + + - name: Get the latest budibase release version + id: version + run: | + release_version=$(cat lerna.json | jq -r '.version') + echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + + - name: Tag and release Proxy service docker image + run: | + docker login -u $DOCKER_USER -p $DOCKER_PASSWORD + yarn build:docker:proxy:release + docker tag proxy-service budibase/proxy:$RELEASE_TAG + docker push budibase/proxy:$RELEASE_TAG + env: + DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} + RELEASE_TAG: k8s-release + + - name: Pull values.yaml from budibase-infra + run: | + curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \ + -H 'Accept: application/vnd.github.v3.raw' \ + -o values.release.yaml \ + -L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/budibase-release/values.yaml + wc -l values.release.yaml + + - name: Deploy to Release Environment + uses: glopezep/helm@v1.7.1 + with: + release: budibase-release + namespace: budibase + chart: charts/budibase + token: ${{ github.token }} + helm: helm3 + values: | + globals: + appVersion: develop + ingress: + enabled: true + nginx: true + value-files: >- + [ + "values.release.yaml" + ] + env: + KUBECONFIG_FILE: '${{ secrets.RELEASE_KUBECONFIG }}' + + - name: Discord Webhook Action + uses: tsickert/discord-webhook@v4.0.0 + with: + webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} + content: "Release Env Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Release Env." + embed-title: ${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/deploy-single-image.yml b/.github/workflows/deploy-single-image.yml new file mode 100644 index 0000000000..8bf8f232c5 --- /dev/null +++ b/.github/workflows/deploy-single-image.yml @@ -0,0 +1,68 @@ +name: Deploy Budibase Single Container Image to DockerHub + +on: + workflow_dispatch: + +env: + BASE_BRANCH: ${{ github.event.pull_request.base.ref}} + BRANCH: ${{ github.event.pull_request.head.ref }} + CI: true + PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }} + REGISTRY_URL: registry.hub.docker.com +jobs: + build: + name: "build" + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x] + steps: + - name: "Checkout" + uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Setup QEMU + uses: docker/setup-qemu-action@v1 + - name: Setup Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + - name: Install Pro + run: yarn install:pro $BRANCH $BASE_BRANCH + - name: Run Yarn + run: yarn + - name: Run Yarn Bootstrap + run: yarn bootstrap + - name: Runt Yarn Lint + run: yarn lint + - name: Run Yarn Build + run: yarn build:docker:pre + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_API_KEY }} + - name: Get the latest release version + id: version + run: | + release_version=$(cat lerna.json | jq -r '.version') + echo $release_version + echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + - name: Tag and release Budibase service docker image + uses: docker/build-push-action@v2 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: budibase/budibase,budibase/budibase:v${{ env.RELEASE_VERSION }} + file: ./hosting/single/Dockerfile + - name: Tag and release Budibase Azure App Service docker image + uses: docker/build-push-action@v2 + with: + context: . + push: true + platforms: linux/amd64 + build-args: TARGETBUILD=aas + tags: budibase/budibase-aas,budibase/budibase-aas:v${{ env.RELEASE_VERSION }} + file: ./hosting/single/Dockerfile diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index 4467cd6c81..631308d945 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -1,4 +1,5 @@ -name: Budibase Release Staging +name: Budibase Prerelease +concurrency: release-prerelease on: push: @@ -14,21 +15,33 @@ on: - 'yarn.lock' - 'package.json' - 'yarn.lock' + workflow_dispatch: env: - POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} + # Posthog token used by ui at build time + POSTHOG_TOKEN: phc_uDYOfnFt6wAbBAXkC6STjcrTpAFiWIhqgFcsC1UVO5F INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} - POSTHOG_URL: ${{ secrets.POSTHOG_URL }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + FEATURE_PREVIEW_URL: https://budirelease.live jobs: release: runs-on: ubuntu-latest steps: + - name: Fail if branch is not develop + if: github.ref != 'refs/heads/develop' + run: | + echo "Ref is not develop, you must run this job from develop." + exit 1 - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: 14.x + + - name: Install Pro + run: yarn install:pro develop + - run: yarn - run: yarn bootstrap - run: yarn lint @@ -46,9 +59,9 @@ jobs: env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: | - # setup the username and email. I tend to use 'GitHub Actions Bot' with no email by default - git config user.name "Budibase Staging Release Bot" - git config user.email "<>" + # setup the username and email. + git config --global user.name "Budibase Staging Release Bot" + git config --global user.email "<>" echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc yarn release:develop @@ -60,3 +73,56 @@ jobs: env: DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} + + - name: Get the latest budibase release version + id: version + run: | + release_version=$(cat lerna.json | jq -r '.version') + echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + + - name: Tag and release Proxy service docker image + run: | + docker login -u $DOCKER_USER -p $DOCKER_PASSWORD + yarn build:docker:proxy:release + docker tag proxy-service budibase/proxy:$RELEASE_TAG + docker push budibase/proxy:$RELEASE_TAG + env: + DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} + RELEASE_TAG: k8s-release + + - name: Pull values.yaml from budibase-infra + run: | + curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \ + -H 'Accept: application/vnd.github.v3.raw' \ + -o values.release.yaml \ + -L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/budibase-release/values.yaml + wc -l values.release.yaml + + - name: Deploy to Release Environment + uses: glopezep/helm@v1.7.1 + with: + release: budibase-release + namespace: budibase + chart: charts/budibase + token: ${{ github.token }} + helm: helm3 + values: | + globals: + appVersion: develop + ingress: + enabled: true + nginx: true + value-files: >- + [ + "values.release.yaml" + ] + env: + KUBECONFIG_FILE: '${{ secrets.RELEASE_KUBECONFIG }}' + + - name: Discord Webhook Action + uses: tsickert/discord-webhook@v4.0.0 + with: + webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} + content: "Release Env Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Release Env." + embed-title: ${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml index 9f42a9cc5d..fc2b7b0cca 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -87,3 +87,10 @@ jobs: packages/cli/build/cli-macos packages/server/specs/openapi.yaml packages/server/specs/openapi.json + + - name: Discord Webhook Action + uses: tsickert/discord-webhook@v4.0.0 + with: + webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} + content: "Self Host Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Self Host." + embed-title: ${{ env.RELEASE_VERSION }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82848c78e4..348b600f90 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,5 @@ name: Budibase Release +concurrency: release on: push: @@ -14,22 +15,43 @@ on: - 'yarn.lock' - 'package.json' - 'yarn.lock' + workflow_dispatch: + inputs: + versioning: + type: choice + description: "Versioning type: patch, minor, major" + default: patch + options: + - patch + - minor + - major + required: true env: - POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }} + # Posthog token used by ui at build time + POSTHOG_TOKEN: phc_fg5I3nDOf6oJVMHSaycEhpPdlgS8rzXG2r6F2IpxCHS INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} - POSTHOG_URL: ${{ secrets.POSTHOG_URL }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }} jobs: release: runs-on: ubuntu-latest steps: + - name: Fail if branch is not master + if: github.ref != 'refs/heads/master' + run: | + echo "Ref is not master, you must run this job from master." + exit 1 - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: 14.x + + - name: Install Pro + run: yarn install:pro master + - run: yarn - run: yarn bootstrap - run: yarn lint @@ -46,10 +68,11 @@ jobs: - name: Publish budibase packages to NPM env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + RELEASE_VERSION_TYPE: ${{ github.event.inputs.versioning }} run: | # setup the username and email. I tend to use 'GitHub Actions Bot' with no email by default - git config user.name "Budibase Release Bot" - git config user.email "<>" + git config --global user.name "Budibase Release Bot" + git config --global user.email "<>" echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc yarn release @@ -66,3 +89,57 @@ jobs: DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }} + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + + - name: Tag and release Proxy service docker image + run: | + docker login -u $DOCKER_USER -p $DOCKER_PASSWORD + yarn build:docker:proxy:preprod + docker tag proxy-service budibase/proxy:$PREPROD_TAG + docker push budibase/proxy:$PREPROD_TAG + env: + DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} + PREPROD_TAG: k8s-preprod + + - name: Pull values.yaml from budibase-infra + run: | + curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \ + -H 'Accept: application/vnd.github.v3.raw' \ + -o values.preprod.yaml \ + -L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/budibase-preprod/values.yaml + wc -l values.preprod.yaml + + - name: Deploy to Preprod Environment + uses: glopezep/helm@v1.7.1 + with: + release: budibase-preprod + namespace: budibase + chart: charts/budibase + token: ${{ github.token }} + helm: helm3 + values: | + globals: + appVersion: ${{ steps.previoustag.outputs.tag }} + ingress: + enabled: true + nginx: true + value-files: >- + [ + "values.preprod.yaml" + ] + env: + KUBECONFIG_FILE: '${{ secrets.PREPROD_KUBECONFIG }}' + + - name: Discord Webhook Action + uses: tsickert/discord-webhook@v4.0.0 + with: + webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} + content: "Preprod Deployment Complete: ${{ steps.previoustag.outputs.tag }} deployed to Budibase Pre-prod." + embed-title: ${{ steps.previoustag.outputs.tag }} diff --git a/.github/workflows/smoke_test.yaml b/.github/workflows/smoke_test.yaml index 04c0c7b5e3..7002c8335b 100644 --- a/.github/workflows/smoke_test.yaml +++ b/.github/workflows/smoke_test.yaml @@ -28,24 +28,25 @@ jobs: - name: Cypress run id: cypress + continue-on-error: true uses: cypress-io/github-action@v2 with: + record: true install: false - command: yarn test:e2e:ci + tag: nightly + command: yarn test:e2e:ci:record + env: + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - # TODO: upload recordings to s3 - # - name: Configure AWS Credentials - # uses: aws-actions/configure-aws-credentials@v1 - # with: - # aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - # aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - # aws-region: eu-west-1 - - - name: Discord Webhook Action - uses: tsickert/discord-webhook@v4.0.0 + - uses: actions/upload-artifact@v3 with: - webhook-url: ${{ secrets.BUDI_QA_WEBHOOK }} - content: "Smoke test run completed with ${{ steps.cypress.outcome }}. See results at ${{ steps.cypress.dashboardUrl }}" - embed-title: ${{ steps.cypress.outcome }} - embed-color: ${{ steps.cypress.outcome == 'success' && '3066993' || '15548997' }} + name: Test Reports + path: packages/builder/cypress/reports/testReport.html + - name: Cypress Discord Notify + run: yarn test:e2e:ci:notify + env: + CYPRESS_WEBHOOK_URL: ${{ secrets.BUDI_QA_WEBHOOK }} + CYPRESS_OUTCOME: ${{ steps.cypress.outcome }} + CYPRESS_DASHBOARD_URL: ${{ steps.cypress.outputs.dashboardUrl }} + GITHUB_RUN_URL: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID diff --git a/.gitignore b/.gitignore index 7d09f0a2ba..f063e2224f 100644 --- a/.gitignore +++ b/.gitignore @@ -97,5 +97,9 @@ hosting/proxy/.generated-nginx.prod.conf bin/ hosting/.generated* -packages/builder/cypress.env.json -stats.html +packages/builder/cypress.env.json +packages/builder/cypress/reports +stats.html + +# TypeScript cache +*.tsbuildinfo \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index d471924fe0..4838a4fd89 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,17 @@ "editor.codeActionsOnSave": { "source.fixAll": true }, - "editor.defaultFormatter": "svelte.svelte-vscode" + "editor.defaultFormatter": "svelte.svelte-vscode", + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "debug.javascript.terminalOptions": { + "skipFiles": [ + "${workspaceFolder}/packages/backend-core/node_modules/**", + "/**" + ] + }, } diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 0000000000..21fa517e23 --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +network-timeout 100000 diff --git a/README.md b/README.md index 17a3ab1ef2..e8c6475d90 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ Budibase is dedicated to providing a welcoming, diverse, and harrassment-free ex ## 🙌 Contributing to Budibase From opening a bug report to creating a pull request: every contribution is appreciated and welcomed. If you're planning to implement a new feature or change the API please create an issue first. This way we can ensure your work is not in vain. +Environment setup instructions are available for [Debian](https://github.com/Budibase/budibase/tree/HEAD/docs/DEV-SETUP-DEBIAN.md) and [MacOSX](https://github.com/Budibase/budibase/tree/HEAD/docs/DEV-SETUP-MACOSX.md) ### Not Sure Where to Start? A good place to start contributing, is the [First time issues project](https://github.com/Budibase/budibase/projects/22). @@ -187,7 +188,7 @@ Budibase is a monorepo managed by lerna. Lerna manages the building and publishi - [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - The budibase server. This Koa app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system. -For more information, see [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md) +For more information, see [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/docs/CONTRIBUTING.md)

@@ -202,7 +203,7 @@ Budibase is open-source, licensed as [GPL v3](https://www.gnu.org/licenses/gpl-3 [![Stargazers over time](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase) -If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting) to clear down your environment. +If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/HEAD/docs/CONTRIBUTING.md#troubleshooting) to clear down your environment.

diff --git a/charts/budibase/Chart.yaml b/charts/budibase/Chart.yaml index 134d29441f..570aa04d8e 100644 --- a/charts/budibase/Chart.yaml +++ b/charts/budibase/Chart.yaml @@ -11,8 +11,8 @@ sources: - https://github.com/Budibase/budibase - https://budibase.com type: application -version: 0.2.8 -appVersion: 1.0.48 +version: 0.2.11 +appVersion: 1.0.214 dependencies: - name: couchdb version: 3.6.1 diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml index 86e255d331..7a2c483cc8 100644 --- a/charts/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -28,12 +28,15 @@ spec: - env: - name: BUDIBASE_ENVIRONMENT value: {{ .Values.globals.budibaseEnv }} + - name: DEPLOYMENT_ENVIRONMENT + value: "kubernetes" - name: COUCH_DB_URL {{ if .Values.services.couchdb.url }} value: {{ .Values.services.couchdb.url }} {{ else }} value: http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }} {{ end }} + {{ if .Values.services.couchdb.enabled }} - name: COUCH_DB_USER valueFrom: secretKeyRef: @@ -44,6 +47,7 @@ spec: secretKeyRef: name: {{ template "couchdb.fullname" . }} key: adminPassword + {{ end }} - name: ENABLE_ANALYTICS value: {{ .Values.globals.enableAnalytics | quote }} - name: INTERNAL_API_KEY @@ -76,6 +80,10 @@ spec: value: {{ .Values.services.objectStore.url }} - name: PORT value: {{ .Values.services.apps.port | quote }} + {{ if .Values.services.worker.publicApiRateLimitPerSecond }} + - name: API_REQ_LIMIT_PER_SEC + value: {{ .Values.globals.apps.publicApiRateLimitPerSecond | quote }} + {{ end }} - name: MULTI_TENANCY value: {{ .Values.globals.multiTenancy | quote }} - name: LOG_LEVEL @@ -98,10 +106,6 @@ spec: value: http://worker-service:{{ .Values.services.worker.port }} - name: PLATFORM_URL value: {{ .Values.globals.platformUrl | quote }} - - name: USE_QUOTAS - value: {{ .Values.globals.useQuotas | quote }} - - name: EXCLUDE_QUOTAS_TENANTS - value: {{ .Values.globals.excludeQuotasTenants | quote }} - name: ACCOUNT_PORTAL_URL value: {{ .Values.globals.accountPortalUrl | quote }} - name: ACCOUNT_PORTAL_API_KEY @@ -114,12 +118,39 @@ spec: value: {{ .Values.globals.google.clientId | quote }} - name: GOOGLE_CLIENT_SECRET value: {{ .Values.globals.google.secret | quote }} + - name: AUTOMATION_MAX_ITERATIONS + value: {{ .Values.globals.automationMaxIterations | quote }} + - name: TENANT_FEATURE_FLAGS + value: {{ .Values.globals.tenantFeatureFlags | quote }} + {{ if .Values.globals.bbAdminUserEmail }} + - name: BB_ADMIN_USER_EMAIL + value: { { .Values.globals.bbAdminUserEmail | quote } } + {{ end }} + {{ if .Values.globals.bbAdminUserPassword }} + - name: BB_ADMIN_USER_PASSWORD + value: { { .Values.globals.bbAdminUserPassword | quote } } + {{ end }} + image: budibase/apps:{{ .Values.globals.appVersion }} imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health + port: {{ .Values.services.apps.port }} + initialDelaySeconds: 5 + periodSeconds: 5 name: bbapps ports: - containerPort: {{ .Values.services.apps.port }} resources: {} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} restartPolicy: Always serviceAccountName: "" status: {} diff --git a/charts/budibase/templates/couchdb-backup.yaml b/charts/budibase/templates/couchdb-backup.yaml index 1072046c8c..ae062475ce 100644 --- a/charts/budibase/templates/couchdb-backup.yaml +++ b/charts/budibase/templates/couchdb-backup.yaml @@ -39,5 +39,13 @@ spec: imagePullPolicy: Always name: couchdb-backup resources: {} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} status: {} {{- end }} diff --git a/charts/budibase/templates/minio-data-persistentvolumeclaim.yaml b/charts/budibase/templates/minio-data-persistentvolumeclaim.yaml index d122ad0a3e..abcf341bc5 100644 --- a/charts/budibase/templates/minio-data-persistentvolumeclaim.yaml +++ b/charts/budibase/templates/minio-data-persistentvolumeclaim.yaml @@ -12,5 +12,8 @@ spec: resources: requests: storage: {{ .Values.services.objectStore.storage }} + {{ if .Values.services.objectStore.storageClass }} + storageClassName: {{ .Values.services.objectStore.storageClass }} + {{- end }} status: {} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/budibase/templates/minio-service-deployment.yaml b/charts/budibase/templates/minio-service-deployment.yaml index a23d0c1d89..901fb61ad9 100644 --- a/charts/budibase/templates/minio-service-deployment.yaml +++ b/charts/budibase/templates/minio-service-deployment.yaml @@ -60,6 +60,14 @@ spec: volumeMounts: - mountPath: /data name: minio-data + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} restartPolicy: Always serviceAccountName: "" volumes: diff --git a/charts/budibase/templates/proxy-service-deployment.yaml b/charts/budibase/templates/proxy-service-deployment.yaml index 05bf4a7f1a..bd6a5e311f 100644 --- a/charts/budibase/templates/proxy-service-deployment.yaml +++ b/charts/budibase/templates/proxy-service-deployment.yaml @@ -32,6 +32,14 @@ spec: - containerPort: {{ .Values.services.proxy.port }} resources: {} volumeMounts: + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} restartPolicy: Always serviceAccountName: "" volumes: diff --git a/charts/budibase/templates/redis-data-persistentvolumeclaim.yaml b/charts/budibase/templates/redis-data-persistentvolumeclaim.yaml index 2cb5ee8eab..6f11492b58 100644 --- a/charts/budibase/templates/redis-data-persistentvolumeclaim.yaml +++ b/charts/budibase/templates/redis-data-persistentvolumeclaim.yaml @@ -12,5 +12,8 @@ spec: resources: requests: storage: {{ .Values.services.redis.storage }} + {{ if .Values.services.redis.storageClass }} + storageClassName: {{ .Values.services.redis.storageClass }} + {{ end }} status: {} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/budibase/templates/redis-service-deployment.yaml b/charts/budibase/templates/redis-service-deployment.yaml index 9235b0b11d..0b6cb12562 100644 --- a/charts/budibase/templates/redis-service-deployment.yaml +++ b/charts/budibase/templates/redis-service-deployment.yaml @@ -39,6 +39,14 @@ spec: volumeMounts: - mountPath: /data name: redis-data + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} restartPolicy: Always serviceAccountName: "" volumes: diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml index b6c757cb9f..a7f05f3137 100644 --- a/charts/budibase/templates/worker-service-deployment.yaml +++ b/charts/budibase/templates/worker-service-deployment.yaml @@ -27,8 +27,11 @@ spec: spec: containers: - env: + - name: DEPLOYMENT_ENVIRONMENT + value: "kubernetes" - name: CLUSTER_PORT value: {{ .Values.services.worker.port | quote }} + {{ if .Values.services.couchdb.enabled }} - name: COUCH_DB_USER valueFrom: secretKeyRef: @@ -39,6 +42,7 @@ spec: secretKeyRef: name: {{ template "couchdb.fullname" . }} key: adminPassword + {{ end }} - name: COUCH_DB_URL {{ if .Values.services.couchdb.url }} value: {{ .Values.services.couchdb.url }} @@ -89,6 +93,10 @@ spec: value: {{ .Values.globals.selfHosted | quote }} - name: SENTRY_DSN value: {{ .Values.globals.sentryDSN }} + - name: ENABLE_ANALYTICS + value: {{ .Values.globals.enableAnalytics | quote }} + - name: POSTHOG_TOKEN + value: {{ .Values.globals.posthogToken }} - name: ACCOUNT_PORTAL_URL value: {{ .Values.globals.accountPortalUrl | quote }} - name: ACCOUNT_PORTAL_API_KEY @@ -115,12 +123,28 @@ spec: value: {{ .Values.globals.google.clientId | quote }} - name: GOOGLE_CLIENT_SECRET value: {{ .Values.globals.google.secret | quote }} + - name: TENANT_FEATURE_FLAGS + value: {{ .Values.globals.tenantFeatureFlags | quote }} image: budibase/worker:{{ .Values.globals.appVersion }} imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health + port: {{ .Values.services.worker.port }} + initialDelaySeconds: 5 + periodSeconds: 5 name: bbworker ports: - containerPort: {{ .Values.services.worker.port }} resources: {} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} restartPolicy: Always serviceAccountName: "" status: {} diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 648b1d2dee..2734202fff 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -47,6 +47,8 @@ ingress: className: "" annotations: kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/client-max-body-size: 150M + nginx.ingress.kubernetes.io/proxy-body-size: 50m hosts: - host: # change if using custom domain paths: @@ -87,22 +89,21 @@ affinity: {} globals: appVersion: "latest" budibaseEnv: PRODUCTION - enableAnalytics: true + enableAnalytics: "1" sentryDSN: "" - posthogToken: "" + posthogToken: "phc_fg5I3nDOf6oJVMHSaycEhpPdlgS8rzXG2r6F2IpxCHS" logLevel: info selfHosted: "1" # set to 0 for budibase cloud environment, set to 1 for self-hosted setup multiTenancy: "0" # set to 0 to disable multiple orgs, set to 1 to enable multiple orgs - useQuotas: "0" - excludeQuotasTenants: "" # comma seperated list of tenants to exclude from quotas accountPortalUrl: "" accountPortalApiKey: "" cookieDomain: "" platformUrl: "" httpMigrations: "0" google: - clientId: "" + clientId: "" secret: "" + automationMaxIterations: "200" createSecrets: true # creates an internal API key, JWT secrets and redis password for you @@ -150,6 +151,11 @@ services: url: "" # only change if pointing to existing redis cluster and enabled: false password: "budibase" # recommended to override if using built-in redis storage: 100Mi + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. + storageClass: "" objectStore: minio: true @@ -161,6 +167,11 @@ services: region: "" # AWS_REGION if using S3 or existing minio secret url: "http://minio-service:9000" # only change if pointing to existing minio cluster or S3 and minio: false storage: 100Mi + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. + storageClass: "" # Override values in couchDB subchart couchdb: @@ -204,7 +215,7 @@ couchdb: ## The CouchDB image image: repository: couchdb - tag: 3.1.0 + tag: 3.2.1 pullPolicy: IfNotPresent ## Experimental integration with Lucene-powered fulltext search @@ -230,6 +241,8 @@ couchdb: ## Optional tolerations tolerations: [] + affinity: {} + service: # annotations: enabled: true diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..1be855e3fa --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at community@budibase.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000000..531ed05749 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,231 @@ +# Contributing + +From opening a bug report to creating a pull request: every contribution is appreciated and welcome. If you're planning to implement a new feature or change the api please [create an issue](https://github.com/Budibase/budibase/issues/new/choose) first. This way we can ensure that your precious work is not in vain. + +## Table of contents + +- [Quick start](#quick-start) +- [Status](#status) +- [What's included](#whats-included) +- [Bugs and feature requests](#bugs-and-feature-requests) + + +## Not Sure Where to Start? + +Budibase is a low-code web application builder that creates svelte-based web applications. + +Budibase is a monorepo managed by [lerna](https://github.com/lerna/lerna). Lerna manages the building and publishing of the budibase packages. At a high level, here are the packages that make up budibase. + +- **packages/builder** - contains code for the budibase builder client side svelte application. + +- **packages/client** - A module that runs in the browser responsible for reading JSON definition and creating living, breathing web apps from it. + +- **packages/server** - The budibase server. This [Koa](https://koajs.com/) app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system. + +- **packages/worker** - This [Koa](https://koajs.com/) app is responsible for providing global apis for managing your budibase installation. Authentication, Users, Email, Org and Auth configs are all provided by the worker. + +## Contributor License Agreement (CLA) + +In order to accept your pull request, we need you to submit a CLA. You only need to do this once. If you are submitting a pull request for the first time, just submit a Pull Request and our CLA Bot will give you instructions on how to sign the CLA before merging your Pull Request. + +All contributors must sign an [Individual Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/individual-cla.md). + +If contributing on behalf of your company, your company must sign a [Corporate Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/corporate-cla.md). If so, please contact us via community@budibase.com. + +## Glossary of Terms + +To understand the budibase API, it can be helpful to understand the top level entities that make up Budibase. + +### Client + +A client represents a single budibase customer. Each budibase client will have 1 or more budibase servers. Every client is assigned a unique ID. + +### App + +A client can have one or more budibase applications. Budibase applications would be things like "Developer Inventory Management" or "Goat Herder CRM". Think of a budibase application as a tree. + +### Database + +An App can have one or more databases. Keeping with our [dendrology](https://en.wikipedia.org/wiki/Dendrology) analogy - think of an database as a branch on the tree. Databases are used to keep data separate for different instances of your app. For example, if you had a CRM app, you may create a database for your US office, and a database for your Australian office. Databases allow us to support [multitenancy](https://www.gartner.com/en/information-technology/glossary/multitenancy) in budibase applications. + +### Table + +Tables in budibase are almost akin to tables in relational databases. A table may be a "Car" or an "Employee". They are the main building blocks for the creation and management of backend data in budibase. + +### View + +A View is an advanced feature in budibase that allows you to write a custom query using [MapReduce](https://pouchdb.com/guides/queries.html) queries. Views enable powerful query functionality and calculations, allowing you to do more with your data. + +### Page + +A page in budibase is actually a single, self contained svelte web app. There are only 2 pages in budibase. The **login** page and the **main** page. + +### Screen + +A screen is a component within a single page. Generally, screens represent client side routes, and can be switched without refreshing the page. + +### Component + +A component is the basic frontend building block of a budibase app. + +### Component Library + +Component libraries are collections of components as well as the definition of their props contained in a file called `components.json`. + +## Contributing to Budibase + +* Please maintain the existing code style. + +* Please try to keep your commits small and focused. + +* Please write tests. + +* If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read. + +* Once your work is completed, please raise a PR against the `develop` branch with some information about what has changed and why. + +### Getting Started For Contributors +#### 1. Prerequisites + +NodeJS Version `14.x.x` + +*yarn -* `npm install -g yarn` + +*jest* - `npm install -g jest` + +#### 2. Clone this repository + +`git clone https://github.com/Budibase/budibase.git` + +then `cd ` into your local copy. + +#### 3. Install and Build + +| **NOTE**: On Windows, all yarn commands must be executed on a bash shell (e.g. git bash) + +To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed. + +##### Quick method + +`yarn setup` will check that all necessary components are installed and setup the repo for usage. + +##### Manual method + +The following commands can be executed to manually get Budibase up and running (assuming Docker/Docker Compose has been installed). + +`yarn` to install project dependencies + +`yarn bootstrap` will install all budibase modules and symlink them together using lerna. + +`yarn build` will build all budibase packages. + +#### 4. Running + +To run the budibase server and builder in dev mode (i.e. with live reloading): + +1. Open a new console +2. `yarn dev` (from root) +3. Access the builder on http://localhost:10000/builder + +This will enable watch mode for both the builder app, server, client library and any component libraries. + +#### 5. Debugging using VS Code + +To debug the budibase server and worker a VS Code launch configuration has been provided. + +Visit the debug window and select `Budibase Server` or `Budibase Worker` to debug the respective component. +Alternatively to start both components simultaneously select `Start Budibase`. + +In addition to the above, the remaining budibase components may be run in dev mode using: `yarn dev:noserver`. + +#### 6. Cleanup + +If you wish to delete all the apps created in development and reset the environment then run the following: + +1. `yarn nuke:docker` will wipe all the Budibase services +2. `yarn dev` will restart all the services + +### Backend + +For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apache.org/), [MinIO](https://min.io/) and [NGINX](https://www.nginx.com/) in Docker compose. This means that to develop Budibase you will need Docker and Docker compose installed. The backend services are then run separately as Node services with nodemon so that they can be debugged outside of Docker. + +### Data Storage + +When you are running locally, budibase stores data on disk using docker volumes. The volumes and the types of data associated with each are: + +- `redis_data` + - Sessions, email tokens +- `couchdb3_data` + - Global and app databases +- `minio_data` + - App manifest, budibase client, static assets + +### Development Modes + +A combination of environment variables controls the mode budibase runs in. +Yarn commands can be used to mimic the different modes as described in the sections below: + +#### Self Hosted +The default mode. A single tenant installation with no usage restrictions. + +To enable this mode, use: +``` +yarn mode:self +``` + +#### Cloud +The cloud mode, with account portal turned off. + +To enable this mode, use: +``` +yarn mode:cloud +``` +#### Cloud & Account +The cloud mode, with account portal turned on. This is a replica of the mode that runs at https://budibase.app + + +To enable this mode, use: +``` +yarn mode:account +``` +### CI + An overview of the CI pipelines can be found [here](./workflows/README.md) + +### Pro + +@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you require to update code inside the pro package it can be cloned to the same root level as budibase, e.g. + +``` +. +|_ budibase +|_ budibase-pro +``` + +Note that only budibase maintainers will be able to access the pro repo. + +The `yarn bootstrap` command can be used to replace the NPM supplied dependency with the local source aware version. This is achieved using the `yarn link` command. To see specifically how dependencies are linked see [scripts/link-dependencies.sh](../scripts/link-dependencies.sh). The same link script is used to link dependencies to account-portal in local dev. + +### Troubleshooting + +Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above to create a fresh Budibase installation. +### Running tests + +#### End-to-end Tests + +Budibase uses Cypress to run a number of E2E tests. To run the tests execute the following command in the root folder: + +``` +yarn test:e2e +``` + +Or if you are in the builder you can run `yarn cy:test`. + + +### Other Useful Information + +* The contributors are listed in [AUTHORS.md](https://github.com/Budibase/budibase/blob/master/.github/AUTHORS.md) (add yourself). + +* This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE). + +* We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions. + Please read this if you are unfamiliar with it. diff --git a/docs/DEV-SETUP-DEBIAN.md b/docs/DEV-SETUP-DEBIAN.md new file mode 100644 index 0000000000..88a124708c --- /dev/null +++ b/docs/DEV-SETUP-DEBIAN.md @@ -0,0 +1,52 @@ +## Dev Environment on Debian 11 + +### Install Node + +Budibase requires a recent version of node (14+): +``` +curl -sL https://deb.nodesource.com/setup_16.x | sudo bash - +apt -y install nodejs +node -v +``` + +### Install npm requirements + +``` +npm install -g yarn jest lerna +``` +### Install Docker and Docker Compose + +``` +apt install docker.io +pip3 install docker-compose +``` +### Clone the repo +``` +git clone https://github.com/Budibase/budibase.git +``` + +### Check Versions + +This setup process was tested on Debian 11 (bullseye) with version numbers show below. Your mileage may vary using anything else. + +- Docker: 20.10.5 +- Docker-Compose: 1.29.2 +- Node: v16.15.1 +- Yarn: 1.22.19 +- Lerna: 5.1.4 + +### Build + +``` +cd budibase +yarn setup +``` +The yarn setup command runs several build steps i.e. +``` +node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev +``` +So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose. + +The dev version will be available on port 10000 i.e. + +http://127.0.0.1:10000/builder/admin \ No newline at end of file diff --git a/docs/DEV-SETUP-MACOSX.md b/docs/DEV-SETUP-MACOSX.md new file mode 100644 index 0000000000..5606fd0d10 --- /dev/null +++ b/docs/DEV-SETUP-MACOSX.md @@ -0,0 +1,54 @@ +## Dev Environment on MAC OSX 12 (Monterey) + +### Install Homebrew + +Install instructions [here](https://brew.sh/) + +### Install Node + +Budibase requires a recent version of node (14+): +``` +brew install node npm +node -v +``` + +### Install npm requirements + +``` +npm install -g yarn jest lerna +``` +### Install Docker and Docker Compose + +``` +brew install docker docker-compose +``` +### Clone the repo +``` +git clone https://github.com/Budibase/budibase.git +``` + +### Check Versions + +This setup process was tested on Mac OSX 12 (Monterey) with version numbers shown below. Your mileage may vary using anything else. + +- Docker: 20.10.14 +- Docker-Compose: 2.6.0 +- Node: 18.3.0 +- Yarn: 1.22.19 +- Lerna: 5.1.4 + +### Build + +``` +cd budibase +yarn setup +``` +The yarn setup command runs several build steps i.e. +``` +node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev +``` +So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose. + +The dev version will be available on port 10000 i.e. + +http://127.0.0.1:10000/builder/admin \ No newline at end of file diff --git a/examples/nextjs-api-sales/yarn.lock b/examples/nextjs-api-sales/yarn.lock index 3f32417ba8..52c89967b2 100644 --- a/examples/nextjs-api-sales/yarn.lock +++ b/examples/nextjs-api-sales/yarn.lock @@ -1894,9 +1894,9 @@ minimist-options@4.1.0: kind-of "^6.0.3" minimist@^1.2.0: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== minipass-collect@^1.0.2: version "1.0.2" diff --git a/hosting/.env b/hosting/.env index 39df76d01e..11dd661bf1 100644 --- a/hosting/.env +++ b/hosting/.env @@ -18,4 +18,8 @@ MINIO_PORT=4004 COUCH_DB_PORT=4005 REDIS_PORT=6379 WATCHTOWER_PORT=6161 -BUDIBASE_ENVIRONMENT=PRODUCTION \ No newline at end of file +BUDIBASE_ENVIRONMENT=PRODUCTION + +# An admin user can be automatically created initially if these are set +BB_ADMIN_USER_EMAIL= +BB_ADMIN_USER_PASSWORD= \ No newline at end of file diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml index 43b8526e9e..be0bc74a26 100644 --- a/hosting/docker-compose.dev.yaml +++ b/hosting/docker-compose.dev.yaml @@ -27,6 +27,7 @@ services: image: nginx:latest volumes: - ./.generated-nginx.dev.conf:/etc/nginx/nginx.conf + - ./proxy/error.html:/usr/share/nginx/html/error.html ports: - "${MAIN_PORT}:10000" depends_on: diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index f3055b19fa..57cbf33709 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -23,6 +23,8 @@ services: ENABLE_ANALYTICS: "true" REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} + BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL} + BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD} depends_on: - worker-service - redis-service @@ -117,7 +119,6 @@ services: labels: - "com.centurylinklabs.watchtower.enable=false" - volumes: couchdb3_data: driver: local diff --git a/hosting/hosting.properties b/hosting/hosting.properties index c8e2f5c606..11dd661bf1 100644 --- a/hosting/hosting.properties +++ b/hosting/hosting.properties @@ -19,3 +19,7 @@ COUCH_DB_PORT=4005 REDIS_PORT=6379 WATCHTOWER_PORT=6161 BUDIBASE_ENVIRONMENT=PRODUCTION + +# An admin user can be automatically created initially if these are set +BB_ADMIN_USER_EMAIL= +BB_ADMIN_USER_PASSWORD= \ No newline at end of file diff --git a/hosting/letsencrypt/certificate-renew.sh b/hosting/letsencrypt/certificate-renew.sh new file mode 100644 index 0000000000..df88b44322 --- /dev/null +++ b/hosting/letsencrypt/certificate-renew.sh @@ -0,0 +1,13 @@ +#!/bin/bash +CUSTOM_DOMAIN="$1" + +if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then + certbot certonly --webroot --webroot-path="/var/www/html" \ + --register-unsafely-without-email \ + --domains $CUSTOM_DOMAIN \ + --rsa-key-size 4096 \ + --agree-tos \ + --force-renewal + + nginx -s reload +fi diff --git a/hosting/letsencrypt/certificate-request.sh b/hosting/letsencrypt/certificate-request.sh new file mode 100644 index 0000000000..83f314fc88 --- /dev/null +++ b/hosting/letsencrypt/certificate-request.sh @@ -0,0 +1,23 @@ +#!/bin/bash +CUSTOM_DOMAIN="$1" +# Request from Lets Encrypt +certbot certonly --webroot --webroot-path="/var/www/html" \ + --register-unsafely-without-email \ + --domains $CUSTOM_DOMAIN \ + --rsa-key-size 4096 \ + --agree-tos \ + --force-renewal + +if (($? != 0)); then + echo "ERROR: certbot request failed for $CUSTOM_DOMAIN use http on port 80 - exiting" + exit 1 +else + cp /app/letsencrypt/options-ssl-nginx.conf /etc/letsencrypt/options-ssl-nginx.conf + cp /app/letsencrypt/ssl-dhparams.pem /etc/letsencrypt/ssl-dhparams.pem + cp /app/letsencrypt/nginx-ssl.conf /etc/nginx/sites-available/nginx-ssl.conf + sed -i "s/CUSTOM_DOMAIN/$CUSTOM_DOMAIN/g" /etc/nginx/sites-available/nginx-ssl.conf + ln -s /etc/nginx/sites-available/nginx-ssl.conf /etc/nginx/sites-enabled/nginx-ssl.conf + + echo "INFO: restart nginx after certbot request" + /etc/init.d/nginx restart +fi diff --git a/hosting/letsencrypt/nginx-ssl.conf b/hosting/letsencrypt/nginx-ssl.conf new file mode 100644 index 0000000000..50c5e0198a --- /dev/null +++ b/hosting/letsencrypt/nginx-ssl.conf @@ -0,0 +1,96 @@ +server { + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + server_name _; + ssl_certificate /etc/letsencrypt/live/CUSTOM_DOMAIN/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/CUSTOM_DOMAIN/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + client_max_body_size 1000m; + ignore_invalid_headers off; + proxy_buffering off; + # port_in_redirect off; + + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /var/www/html; + break; + } + location = /.well-known/acme-challenge/ { + return 404; + } + + location /app { + proxy_pass http://127.0.0.1:4001; + } + + location = / { + proxy_pass http://127.0.0.1:4001; + } + + location ~ ^/(builder|app_) { + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:4001; + } + + location ~ ^/api/(system|admin|global)/ { + proxy_pass http://127.0.0.1:4002; + } + + location /worker/ { + proxy_pass http://127.0.0.1:4002; + rewrite ^/worker/(.*)$ /$1 break; + } + + location /api/ { + # calls to the API are rate limited with bursting + limit_req zone=ratelimit burst=20 nodelay; + + # 120s timeout on API requests + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://127.0.0.1:4001; + } + + location /db/ { + proxy_pass http://127.0.0.1:5984; + rewrite ^/db/(.*)$ /$1 break; + } + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + proxy_pass http://127.0.0.1:9000; + } + + client_header_timeout 60; + client_body_timeout 60; + keepalive_timeout 60; + + # gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + +} diff --git a/hosting/letsencrypt/options-ssl-nginx.conf b/hosting/letsencrypt/options-ssl-nginx.conf new file mode 100644 index 0000000000..52fdfde245 --- /dev/null +++ b/hosting/letsencrypt/options-ssl-nginx.conf @@ -0,0 +1,13 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; diff --git a/hosting/letsencrypt/ssl-dhparams.pem b/hosting/letsencrypt/ssl-dhparams.pem new file mode 100644 index 0000000000..088f9673dc --- /dev/null +++ b/hosting/letsencrypt/ssl-dhparams.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== +-----END DH PARAMETERS----- \ No newline at end of file diff --git a/hosting/nginx.dev.conf.hbs b/hosting/nginx.dev.conf.hbs index 9fc2345fb2..9398b7e719 100644 --- a/hosting/nginx.dev.conf.hbs +++ b/hosting/nginx.dev.conf.hbs @@ -28,6 +28,12 @@ http { ignore_invalid_headers off; proxy_buffering off; + error_page 502 503 504 /error.html; + location = /error.html { + root /usr/share/nginx/html; + internal; + } + location /db/ { proxy_pass http://couchdb-service:5984; rewrite ^/db/(.*)$ /$1 break; diff --git a/hosting/nginx.prod.conf.hbs b/hosting/nginx.prod.conf.hbs index 22ca3f2f3e..ac35a2020d 100644 --- a/hosting/nginx.prod.conf.hbs +++ b/hosting/nginx.prod.conf.hbs @@ -30,7 +30,7 @@ http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; - + map $http_upgrade $connection_upgrade { default "upgrade"; } @@ -42,13 +42,13 @@ http { client_max_body_size 1000m; ignore_invalid_headers off; proxy_buffering off; - - set $csp_default "default-src 'self'" + + set $csp_default "default-src 'self'"; set $csp_script "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io"; set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com"; set $csp_object "object-src 'none'"; set $csp_base_uri "base-uri 'self'"; - set $csp_connect "connect-src 'self' https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com"; + set $csp_connect "connect-src 'self' https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.amazonaws.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com"; set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com"; set $csp_frame "frame-src 'self' https:"; set $csp_img "img-src http: https: data: blob:"; @@ -56,11 +56,17 @@ http { set $csp_media "media-src 'self' https://js.intercomcdn.com"; set $csp_worker "worker-src 'none'"; + error_page 502 503 504 /error.html; + location = /error.html { + root /usr/share/nginx/html; + internal; + } + # Security Headers add_header X-Frame-Options SAMEORIGIN always; add_header X-Content-Type-Options nosniff always; add_header X-XSS-Protection "1; mode=block" always; - add_header Content-Security-Policy ${csp_default}; ${csp_script}; ${csp_style}; ${csp_object}; ${csp_base_uri}; ${csp_connect}; ${csp_font}; ${csp_frame}; ${csp_img}; ${csp_manifest}; ${csp_media}; ${csp_worker};" always; + add_header Content-Security-Policy "${csp_default}; ${csp_script}; ${csp_style}; ${csp_object}; ${csp_base_uri}; ${csp_connect}; ${csp_font}; ${csp_frame}; ${csp_img}; ${csp_manifest}; ${csp_media}; ${csp_worker};" always; # upstreams set $apps {{ apps }}; @@ -148,4 +154,4 @@ http { gzip_comp_level 6; gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; } -} \ No newline at end of file +} diff --git a/hosting/portainer/template.json b/hosting/portainer/template.json new file mode 100644 index 0000000000..29107b674e --- /dev/null +++ b/hosting/portainer/template.json @@ -0,0 +1,94 @@ +{ + "version": "2", + "templates": [ + { + "type": 3, + "title": "Budibase", + "categories": ["Tools"], + "description": "Build modern business apps in minutes", + "logo": "https://budibase.com/favicon.ico", + "platform": "linux", + "repository": { + "url": "https://github.com/Budibase/budibase", + "stackfile": "hosting/docker-compose.yaml" + }, + "env": [ + { + "name": "MAIN_PORT", + "label": "Main port", + "default": "10000" + }, + { + "name": "JWT_SECRET", + "label": "JWT secret", + "default": "change-me" + }, + { + "name": "MINIO_ACCESS_KEY", + "label": "MinIO access key", + "default": "change-me" + }, + { + "name": "MINIO_SECRET_KEY", + "label": "MinIO secret key", + "default": "change-me" + }, + { + "name": "COUCH_DB_USER", + "default": "budibase", + "preset": true + }, + { + "name": "COUCH_DB_PASSWORD", + "label": "Couch DB password", + "default": "change-me" + }, + { + "name": "REDIS_PASSWORD", + "label": "Redis password", + "default": "change-me" + }, + { + "name": "INTERNAL_API_KEY", + "label": "Internal API key", + "default": "change-me" + }, + { + "name": "APP_PORT", + "default": "4002", + "preset": true + }, + { + "name": "WORKER_PORT", + "default": "4003", + "preset": true + }, + { + "name": "MINIO_PORT", + "default": "4004", + "preset": true + }, + { + "name": "COUCH_DB_PORT", + "default": "4005", + "preset": true + }, + { + "name": "REDIS_PORT", + "default": "6379", + "preset": true + }, + { + "name": "WATCHTOWER_PORT", + "default": "6161", + "preset": true + }, + { + "name": "BUDIBASE_ENVIRONMENT", + "default": "PRODUCTION", + "preset": true + } + ] + } + ] +} diff --git a/hosting/proxy/Dockerfile b/hosting/proxy/Dockerfile index b577e3e40f..a2b17d3333 100644 --- a/hosting/proxy/Dockerfile +++ b/hosting/proxy/Dockerfile @@ -1,2 +1,3 @@ FROM nginx:latest -COPY .generated-nginx.prod.conf /etc/nginx/nginx.conf \ No newline at end of file +COPY .generated-nginx.prod.conf /etc/nginx/nginx.conf +COPY error.html /usr/share/nginx/html/error.html \ No newline at end of file diff --git a/hosting/proxy/error.html b/hosting/proxy/error.html new file mode 100644 index 0000000000..023c1ebaff --- /dev/null +++ b/hosting/proxy/error.html @@ -0,0 +1,175 @@ + + + + + Budibase + + + + + + + + + + +
+
+
+ Budibase Logo +
+
+
+

+

+ Houston we have a problem! +

+

+

+
+
+ + +
+
+
+
+ + + \ No newline at end of file diff --git a/hosting/scripts/build-target-paths.sh b/hosting/scripts/build-target-paths.sh new file mode 100644 index 0000000000..4c165d12e7 --- /dev/null +++ b/hosting/scripts/build-target-paths.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo ${TARGETBUILD} > /buildtarget.txt +if [[ "${TARGETBUILD}" = "aas" ]]; then + # Azure AppService uses /home for persisent data & SSH on port 2222 + mkdir -p /home/{search,minio,couch} + mkdir -p /home/couch/{dbs,views} + chown -R couchdb:couchdb /home/couch/ + apt update + apt-get install -y openssh-server + sed -i 's#dir=/opt/couchdb/data/search#dir=/home/search#' /opt/clouseau/clouseau.ini + sed -i 's#/minio/minio server /minio &#/minio/minio server /home/minio &#' /runner.sh + sed -i 's#database_dir = ./data#database_dir = /home/couch/dbs#' /opt/couchdb/etc/default.ini + sed -i 's#view_index_dir = ./data#view_index_dir = /home/couch/views#' /opt/couchdb/etc/default.ini + sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config + /etc/init.d/ssh restart +fi diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile new file mode 100644 index 0000000000..b5bf17adde --- /dev/null +++ b/hosting/single/Dockerfile @@ -0,0 +1,148 @@ +FROM node:14-slim as build + +# install node-gyp dependencies +RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends apt-utils cron g++ make python + +# add pin script +WORKDIR / +ADD scripts/pinVersions.js scripts/cleanup.sh ./ +RUN chmod +x /cleanup.sh + +# build server +WORKDIR /app +ADD packages/server . +RUN node /pinVersions.js && yarn && yarn build && /cleanup.sh + +# build worker +WORKDIR /worker +ADD packages/worker . +RUN node /pinVersions.js && yarn && yarn build && /cleanup.sh + +FROM couchdb:3.2.1 +# TARGETARCH can be amd64 or arm e.g. docker build --build-arg TARGETARCH=amd64 +ARG TARGETARCH amd64 +#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service) +# e.g. docker build --build-arg TARGETBUILD=aas .... +ARG TARGETBUILD single +ENV TARGETBUILD $TARGETBUILD + +COPY --from=build /app /app +COPY --from=build /worker /worker + +ENV \ + APP_PORT=4001 \ + ARCHITECTURE=amd \ + BUDIBASE_ENVIRONMENT=PRODUCTION \ + CLUSTER_PORT=80 \ + # CUSTOM_DOMAIN=budi001.custom.com \ + DEPLOYMENT_ENVIRONMENT=docker \ + MINIO_URL=http://localhost:9000 \ + POSTHOG_TOKEN=phc_fg5I3nDOf6oJVMHSaycEhpPdlgS8rzXG2r6F2IpxCHS \ + REDIS_URL=localhost:6379 \ + SELF_HOSTED=1 \ + TARGETBUILD=$TARGETBUILD \ + WORKER_PORT=4002 \ + WORKER_URL=http://localhost:4002 \ + APPS_URL=http://localhost:4001 + +# These secret env variables are generated by the runner at startup +# their values can be overriden by the user, they will be written +# to the .env file in the /data directory for use later on +# REDIS_PASSWORD=budibase \ +# COUCHDB_PASSWORD=budibase \ +# COUCHDB_USER=budibase \ +# COUCH_DB_URL=http://budibase:budibase@localhost:5984 \ +# INTERNAL_API_KEY=budibase \ +# JWT_SECRET=testsecret \ +# MINIO_ACCESS_KEY=budibase \ +# MINIO_SECRET_KEY=budibase \ + +# install base dependencies +RUN apt-get update && \ + apt-get install -y software-properties-common wget nginx uuid-runtime && \ + apt-add-repository 'deb http://security.debian.org/debian-security stretch/updates main' && \ + apt-get update + +# install other dependencies, nodejs, oracle requirements, jdk8, redis, nginx +WORKDIR /nodejs +RUN curl -sL https://deb.nodesource.com/setup_16.x -o /tmp/nodesource_setup.sh && \ + bash /tmp/nodesource_setup.sh && \ + apt-get install -y libaio1 nodejs nginx openjdk-8-jdk redis-server unzip && \ + npm install --global yarn pm2 + +# setup nginx +ADD hosting/single/nginx/nginx.conf /etc/nginx +ADD hosting/single/nginx/nginx-default-site.conf /etc/nginx/sites-enabled/default +RUN mkdir -p /var/log/nginx && \ + touch /var/log/nginx/error.log && \ + touch /var/run/nginx.pid + +WORKDIR / +RUN mkdir -p scripts/integrations/oracle +ADD packages/server/scripts/integrations/oracle scripts/integrations/oracle +RUN /bin/bash -e ./scripts/integrations/oracle/instantclient/linux/install.sh + +# setup clouseau +WORKDIR / +RUN wget https://github.com/cloudant-labs/clouseau/releases/download/2.21.0/clouseau-2.21.0-dist.zip && \ + unzip clouseau-2.21.0-dist.zip && \ + mv clouseau-2.21.0 /opt/clouseau && \ + rm clouseau-2.21.0-dist.zip + +WORKDIR /opt/clouseau +RUN mkdir ./bin +ADD hosting/single/clouseau/clouseau ./bin/ +ADD hosting/single/clouseau/log4j.properties hosting/single/clouseau/clouseau.ini ./ +RUN chmod +x ./bin/clouseau + +# setup CouchDB +WORKDIR /opt/couchdb +ADD hosting/single/couch/vm.args hosting/single/couch/local.ini ./etc/ + +# setup minio +WORKDIR /minio +ADD scripts/install-minio.sh ./install.sh +RUN chmod +x install.sh && ./install.sh + +# setup runner file +WORKDIR / +ADD hosting/single/runner.sh . +RUN chmod +x ./runner.sh +ADD hosting/single/healthcheck.sh . +RUN chmod +x ./healthcheck.sh + +ADD hosting/scripts/build-target-paths.sh . +RUN chmod +x ./build-target-paths.sh + +# For Azure App Service install SSH & point data locations to /home +RUN /build-target-paths.sh + +# cleanup cache +RUN yarn cache clean -f + +EXPOSE 80 +EXPOSE 443 +VOLUME /data + +# setup letsencrypt certificate +RUN apt-get install -y certbot python3-certbot-nginx +ADD hosting/letsencrypt /app/letsencrypt +RUN chmod +x /app/letsencrypt/certificate-request.sh /app/letsencrypt/certificate-renew.sh +# Remove cached files +RUN rm -rf \ + /root/.cache \ + /root/.npm \ + /root/.pip \ + /usr/local/share/doc \ + /usr/share/doc \ + /usr/share/man \ + /var/lib/apt/lists/* \ + /tmp/* + +HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh" + +# must set this just before running +ENV NODE_ENV=production +WORKDIR / + +CMD ["./runner.sh"] diff --git a/hosting/single/README.md b/hosting/single/README.md new file mode 100644 index 0000000000..1147d55c89 --- /dev/null +++ b/hosting/single/README.md @@ -0,0 +1,112 @@ +# Docker Single Image for Budibase + +## Overview +As an alternative to running several docker containers via docker-compose, the files under ./hosting/single can be used to build a docker image containing all of the Budibase components (minio, couch, clouseau etc). +We call this the 'single image' container as the Dockerfile adds all the components to a single docker image. + +## Usage + +- Amend Environment Variables +- Build Requirements +- Build the Image +- Run the Container + +### Amend Environment Variables + +Edit the Dockerfile in this directory amending the environment variables to suit your usage. Pay particular attention to changing passwords. +The CUSTOM_DOMAIN variable will be used to request a certificate from LetsEncrypt and if successful you can point traffic to port 443. If you choose to use the CUSTOM_DOMAIN variable ensure that the DNS for your custom domain points to the public IP address where you are running Budibase - otherwise the certificate issuance will fail. +If you have other arrangements for a proxy in front of the single image container you can omit the CUSTOM_DOMAIN environment variable and the request to LetsEncrypt will be skipped. You can then point traffic to port 80. + +### Build Requirements +We would suggest building the image with 6GB of RAM and 20GB of free disk space for build artifacts. The resulting image size will use approx 2GB of disk space. + +### Build the Image +The guidance below is based on building the Budibase single image on Debian 11 and AlmaLinux 8. If you use another distro or OS you will need to amend the commands to suit. +#### Install Node +Budibase requires a more recent version of node (14+) than is available in the base Debian repos so: + +``` +curl -sL https://deb.nodesource.com/setup_16.x | sudo bash - +apt install -y nodejs +node -v +``` +Install yarn and lerna: +``` +npm install -g yarn jest lerna +``` +#### Install Docker + +``` +apt install -y docker.io +``` + +Check the versions of each installed version. This process was tested with the version numbers below so YMMV using anything else: + +- Docker: 20.10.5 +- node: 16.15.1 +- yarn: 1.22.19 +- lerna: 5.1.4 + +#### Get the Code +Clone the Budibase repo +``` +git clone https://github.com/Budibase/budibase.git +cd budibase +``` +#### Setup Node +Node setup: +``` +node ./hosting/scripts/setup.js +yarn +yarn bootstrap +yarn build +``` +#### Build Image +The following yarn command does some prep and then runs the docker build command: +``` +yarn build:docker:single +``` +If the docker build step fails try running that step again manually with: +``` +docker build --build-arg TARGETARCH=amd --no-cache -t budibase:latest -f ./hosting/single/Dockerfile . +``` + +#### Azure App Services +Azure have some specific requirements for running a container in their App Service. Specifically, installation of SSH to port 2222 and data storage under /home. If you would like to build a budibase container for Azure App Service add the build argument shown below setting it to 'aas'. You can remove the CUSTOM_DOMAIN env variable from the Dockerfile too as Azure terminate SSL before requests reach the container. +``` +docker build --build-arg TARGETARCH=amd --build-arg TARGETBUILD=aas -t budibase:latest -f ./hosting/single/Dockerfile . +``` + +### Run the Container +``` +docker run -d -p 80:80 -p 443:443 --name budibase budibase:latest +``` +Where: +- -d runs the container in detached mode +- -p forwards ports from your host to the ports inside the container. If you are already using port 80 on your host for something else you can try running with an alternative port e.g. `-p 8080:80` +- --name is the name for the container as shown in `docker ps` and can be used with other docker commands e.g. `docker restart budibase` + +When the container runs you should be able to access the container over http at your host address e.g. http://1.2.3.4/ or using your custom domain e.g. https://my.custom.domain/ + +When the Budibase UI appears you will be prompted to create an account to get started. + +### Podman +The single image container builds fine when using podman in place of docker. You may be prompted for the registry to use for the CouchDB image and the HEALTHCHECK parameter is not OCI compliant so is ignored. + +### Check +There are many things that could go wrong so if your container is not building or running as expected please check the following before opening a support issue. +Verify the healthcheck status of the container: +``` +docker ps +``` +Check the container logs: +``` +docker logs budibase +``` + +### Support +This single image build is still a work-in-progress so if you open an issue please provide the following information: +- The OS and OS version you are building on +- The versions you are using of docker, docker-compose, yarn, node, lerna +- For build errors please provide zipped output +- For container errors please provide zipped container logs diff --git a/hosting/single/clouseau/clouseau b/hosting/single/clouseau/clouseau new file mode 100644 index 0000000000..1095ea24cb --- /dev/null +++ b/hosting/single/clouseau/clouseau @@ -0,0 +1,12 @@ +#!/bin/sh +/usr/bin/java -server \ + -Xmx2G \ + -Dsun.net.inetaddr.ttl=30 \ + -Dsun.net.inetaddr.negative.ttl=30 \ + -Dlog4j.configuration=file:/opt/clouseau/log4j.properties \ + -XX:OnOutOfMemoryError="kill -9 %p" \ + -XX:+UseConcMarkSweepGC \ + -XX:+CMSParallelRemarkEnabled \ + -classpath '/opt/clouseau/*' \ + com.cloudant.clouseau.Main \ + /opt/clouseau/clouseau.ini \ No newline at end of file diff --git a/hosting/single/clouseau/clouseau.ini b/hosting/single/clouseau/clouseau.ini new file mode 100644 index 0000000000..78e43744e5 --- /dev/null +++ b/hosting/single/clouseau/clouseau.ini @@ -0,0 +1,13 @@ +[clouseau] + +; the name of the Erlang node created by the service, leave this unchanged +name=clouseau@127.0.0.1 + +; set this to the same distributed Erlang cookie used by the CouchDB nodes +cookie=monster + +; the path where you would like to store the search index files +dir=/data/search + +; the number of search indexes that can be open simultaneously +max_indexes_open=500 diff --git a/hosting/single/clouseau/log4j.properties b/hosting/single/clouseau/log4j.properties new file mode 100644 index 0000000000..9d4d9311bc --- /dev/null +++ b/hosting/single/clouseau/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=debug, CONSOLE +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %c [%p] %m%n \ No newline at end of file diff --git a/hosting/single/couch/local.ini b/hosting/single/couch/local.ini new file mode 100644 index 0000000000..72872a60e1 --- /dev/null +++ b/hosting/single/couch/local.ini @@ -0,0 +1,5 @@ +; CouchDB Configuration Settings + +[couchdb] +database_dir = /data/couch/dbs +view_index_dir = /data/couch/views diff --git a/hosting/single/couch/vm.args b/hosting/single/couch/vm.args new file mode 100644 index 0000000000..e9e4416863 --- /dev/null +++ b/hosting/single/couch/vm.args @@ -0,0 +1,32 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# erlang cookie for clouseau security +-name couchdb@127.0.0.1 +-setcookie monster + +# Ensure that the Erlang VM listens on a known port +-kernel inet_dist_listen_min 9100 +-kernel inet_dist_listen_max 9100 + +# Tell kernel and SASL not to log anything +-kernel error_logger silent +-sasl sasl_error_logger false + +# Use kernel poll functionality if supported by emulator ++K true + +# Start a pool of asynchronous IO threads ++A 16 + +# Comment this line out to enable the interactive Erlang shell on startup ++Bd -noinput diff --git a/hosting/single/healthcheck.sh b/hosting/single/healthcheck.sh new file mode 100644 index 0000000000..b92cd153a3 --- /dev/null +++ b/hosting/single/healthcheck.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +healthy=true + +if [ -f "/data/.env" ]; then + export $(cat /data/.env | xargs) +fi + +if [[ $(curl -Lfk -s -w "%{http_code}\n" http://localhost/ -o /dev/null) -ne 200 ]]; then + echo 'ERROR: Budibase is not running'; + healthy=false +fi + +if [[ $(curl -s -w "%{http_code}\n" http://localhost:4001/health -o /dev/null) -ne 200 ]]; then + echo 'ERROR: Budibase backend is not running'; + healthy=false +fi + +if [[ $(curl -s -w "%{http_code}\n" http://localhost:4002/health -o /dev/null) -ne 200 ]]; then + echo 'ERROR: Budibase worker is not running'; + healthy=false +fi + +if [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/ -o /dev/null) -ne 200 ]]; then + echo 'ERROR: CouchDB is not running'; + healthy=false +fi +if [[ $(redis-cli -a $REDIS_PASSWORD --no-auth-warning ping) != 'PONG' ]]; then + echo 'ERROR: Redis is down'; + healthy=false +fi +# mino, clouseau, +nginx -t -q +NGINX_STATUS=$? + +if [[ $NGINX_STATUS -gt 0 ]]; then + echo 'ERROR: Nginx config problem'; + healthy=false +fi + +if [ $healthy == true ]; then + exit 0 +else + exit 1 +fi diff --git a/hosting/single/nginx/nginx-default-site.conf b/hosting/single/nginx/nginx-default-site.conf new file mode 100644 index 0000000000..c0d80a0185 --- /dev/null +++ b/hosting/single/nginx/nginx-default-site.conf @@ -0,0 +1,91 @@ +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + + client_max_body_size 1000m; + ignore_invalid_headers off; + proxy_buffering off; + # port_in_redirect off; + + location ^~ /.well-known/acme-challenge/ { + default_type "text/plain"; + root /var/www/html; + break; + } + location = /.well-known/acme-challenge/ { + return 404; + } + + location /app { + proxy_pass http://127.0.0.1:4001; + } + + location = / { + proxy_pass http://127.0.0.1:4001; + } + + location ~ ^/(builder|app_) { + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:4001; + } + + location ~ ^/api/(system|admin|global)/ { + proxy_pass http://127.0.0.1:4002; + } + + location /worker/ { + proxy_pass http://127.0.0.1:4002; + rewrite ^/worker/(.*)$ /$1 break; + } + + location /api/ { + # calls to the API are rate limited with bursting + limit_req zone=ratelimit burst=20 nodelay; + + # 120s timeout on API requests + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://127.0.0.1:4001; + } + + location /db/ { + proxy_pass http://127.0.0.1:5984; + rewrite ^/db/(.*)$ /$1 break; + } + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + proxy_pass http://127.0.0.1:9000; + } + + client_header_timeout 60; + client_body_timeout 60; + keepalive_timeout 60; + + # gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; +} diff --git a/hosting/single/nginx/nginx.conf b/hosting/single/nginx/nginx.conf new file mode 100644 index 0000000000..1e5d1c20d2 --- /dev/null +++ b/hosting/single/nginx/nginx.conf @@ -0,0 +1,37 @@ +user www-data www-data; +error_log /var/log/nginx/error.log; +pid /var/run/nginx.pid; +worker_processes auto; +worker_rlimit_nofile 8192; + +events { + worker_connections 1024; +} + +http { + limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=20r/s; + proxy_set_header Host $host; + charset utf-8; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + server_tokens off; + types_hash_max_size 2048; + + # buffering + client_header_buffer_size 1k; + client_max_body_size 20M; + ignore_invalid_headers off; + proxy_buffering off; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + map $http_upgrade $connection_upgrade { + default "upgrade"; + } + + include /etc/nginx/sites-enabled/*; + +} diff --git a/hosting/single/runner.sh b/hosting/single/runner.sh new file mode 100644 index 0000000000..9abb2fd093 --- /dev/null +++ b/hosting/single/runner.sh @@ -0,0 +1,52 @@ +#!/bin/bash +declare -a ENV_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "MINIO_ACCESS_KEY" "MINIO_SECRET_KEY" "INTERNAL_API_KEY" "JWT_SECRET" "REDIS_PASSWORD") +if [ -f "/data/.env" ]; then + export $(cat /data/.env | xargs) +fi +# first randomise any unset environment variables +for ENV_VAR in "${ENV_VARS[@]}" +do + temp=$(eval "echo \$$ENV_VAR") + if [[ -z "${temp}" ]]; then + eval "export $ENV_VAR=$(uuidgen | sed -e 's/-//g')" + fi +done +if [[ -z "${COUCH_DB_URL}" ]]; then + export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984 +fi +if [ ! -f "/data/.env" ]; then + touch /data/.env + for ENV_VAR in "${ENV_VARS[@]}" + do + temp=$(eval "echo \$$ENV_VAR") + echo "$ENV_VAR=$temp" >> /data/.env + done +fi + +# make these directories in runner, incase of mount +mkdir -p /data/couch/{dbs,views} /home/couch/{dbs,views} +chown -R couchdb:couchdb /data/couch /home/couch +redis-server --requirepass $REDIS_PASSWORD & +/opt/clouseau/bin/clouseau & +/minio/minio server /data/minio & +/docker-entrypoint.sh /opt/couchdb/bin/couchdb & +/etc/init.d/nginx restart +if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then + # Add monthly cron job to renew certbot certificate + echo -n "* * 2 * * root exec /app/letsencrypt/certificate-renew.sh ${CUSTOM_DOMAIN}" >> /etc/cron.d/certificate-renew + chmod +x /etc/cron.d/certificate-renew + # Request the certbot certificate + /app/letsencrypt/certificate-request.sh ${CUSTOM_DOMAIN} +fi + +/etc/init.d/nginx restart +pushd app +pm2 start --name app "yarn run:docker" +popd +pushd worker +pm2 start --name worker "yarn run:docker" +popd +sleep 10 +curl -X PUT ${COUCH_DB_URL}/_users +curl -X PUT ${COUCH_DB_URL}/_replicator +sleep infinity diff --git a/hosting/single/test.sh b/hosting/single/test.sh new file mode 100755 index 0000000000..8830426a47 --- /dev/null +++ b/hosting/single/test.sh @@ -0,0 +1,4 @@ +#!/bin/bash +id=$(docker run -t -d -p 8080:80 budibase:latest) +docker exec -it $id bash +docker kill $id diff --git a/lerna.json b/lerna.json index 0fe2bc8a82..a54e5ff8aa 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.0.104-alpha.0", + "version": "1.1.22", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index 88aa681ad3..0c7d3989a2 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "private": true, "devDependencies": { "@rollup/plugin-json": "^4.0.2", + "@types/mongodb": "3.6.3", + "@typescript-eslint/parser": "4.28.0", "babel-eslint": "^10.0.3", "eslint": "^7.28.0", "eslint-plugin-cypress": "^2.11.3", @@ -16,30 +18,30 @@ "rimraf": "^3.0.2", "rollup-plugin-replace": "^2.2.0", "svelte": "^3.38.2", - "@typescript-eslint/parser": "4.28.0", "typescript": "4.5.5" }, "scripts": { "setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev", - "bootstrap": "lerna link && lerna bootstrap", + "bootstrap": "lerna bootstrap && lerna link && ./scripts/link-dependencies.sh", "build": "lerna run build", - "publishdev": "lerna run publishdev", - "publishnpm": "yarn build && lerna publish --force-publish", - "release": "lerna publish patch --yes --force-publish", - "release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop", + "build:dev": "lerna run prebuild && tsc --build --watch --preserveWatchOutput", + "release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish && yarn release:pro", + "release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop && yarn release:pro:develop", + "release:pro": "bash scripts/pro/release.sh", + "release:pro:develop": "bash scripts/pro/release.sh develop", "restore": "yarn run clean && yarn run bootstrap && yarn run build", "nuke": "yarn run nuke:packages && yarn run nuke:docker", "nuke:packages": "yarn run restore", "nuke:docker": "lerna run --parallel dev:stack:nuke", "clean": "lerna clean", - "kill-port": "kill-port 4001", "kill-builder": "kill-port 3000", "kill-server": "kill-port 4001 4002", "kill-all": "yarn run kill-builder && yarn run kill-server", "dev": "yarn run kill-all && lerna link && lerna run --parallel dev:builder --concurrency 1", - "dev:noserver": "yarn run kill-builder && lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/server --ignore @budibase/worker", - "dev:server": "yarn run kill-server && lerna run --parallel dev:builder --concurrency 1 --scope @budibase/worker --scope @budibase/server", - "test": "lerna run test", + "dev:noserver": "yarn run kill-builder && lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker", + "dev:server": "yarn run kill-server && lerna run --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server", + "test": "lerna run test && yarn test:pro", + "test:pro": "bash scripts/pro/test.sh", "lint:eslint": "eslint packages", "lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\"", "lint": "yarn run lint:eslint && yarn run lint:prettier", @@ -48,16 +50,23 @@ "lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint", "test:e2e": "lerna run cy:test --stream", "test:e2e:ci": "lerna run cy:ci --stream", + "test:e2e:ci:record": "lerna run cy:ci:record --stream", + "test:e2e:ci:notify": "lerna run cy:ci:notify", "build:specs": "lerna run specs", "build:docker": "lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -", + "build:docker:pre": "lerna run build && lerna run predocker", "build:docker:proxy": "docker build hosting/proxy -t proxy-service", "build:docker:proxy:compose": "node scripts/proxy/generateProxyConfig compose && npm run build:docker:proxy", "build:docker:proxy:preprod": "node scripts/proxy/generateProxyConfig preprod && npm run build:docker:proxy", + "build:docker:proxy:release": "node scripts/proxy/generateProxyConfig release && npm run build:docker:proxy", "build:docker:proxy:prod": "node scripts/proxy/generateProxyConfig prod && npm run build:docker:proxy", "build:docker:selfhost": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -", "build:docker:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", "build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild", "build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -", + "build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .", + "build:docker:single:image": "docker build -f hosting/single/Dockerfile -t budibase:latest .", + "build:docker:single": "npm run build:docker:pre && npm run build:docker:single:image", "build:docs": "lerna run build:docs", "release:helm": "node scripts/releaseHelmChart", "env:multi:enable": "lerna run env:multi:enable", @@ -72,6 +81,8 @@ "mode:cloud": "yarn env:selfhost:disable && yarn env:multi:enable && yarn env:account:disable", "mode:account": "yarn mode:cloud && yarn env:account:enable", "security:audit": "node scripts/audit.js", - "postinstall": "husky install" + "postinstall": "husky install", + "install:pro": "bash scripts/pro/install.sh", + "dep:clean": "yarn clean && yarn bootstrap" } } diff --git a/packages/backend-core/.gitignore b/packages/backend-core/.gitignore index 2528ad91a4..3784b93408 100644 --- a/packages/backend-core/.gitignore +++ b/packages/backend-core/.gitignore @@ -44,9 +44,6 @@ jspm_packages/ # Snowpack dependency directory (https://snowpack.dev/) web_modules/ -# TypeScript cache -*.tsbuildinfo - # Optional npm cache directory .npm diff --git a/packages/backend-core/cache.js b/packages/backend-core/cache.js index 02344586a9..c8bd3c9b6f 100644 --- a/packages/backend-core/cache.js +++ b/packages/backend-core/cache.js @@ -1,4 +1,9 @@ +const generic = require("./src/cache/generic") + module.exports = { user: require("./src/cache/user"), app: require("./src/cache/appMetadata"), + writethrough: require("./src/cache/writethrough"), + ...generic, + cache: generic, } diff --git a/packages/backend-core/context.js b/packages/backend-core/context.js index 4bc100687d..aaa0f56f92 100644 --- a/packages/backend-core/context.js +++ b/packages/backend-core/context.js @@ -5,8 +5,11 @@ const { getAppId, updateAppId, doInAppContext, + doInTenant, } = require("./src/context") +const identity = require("./src/context/identity") + module.exports = { getAppDB, getDevAppDB, @@ -14,4 +17,6 @@ module.exports = { getAppId, updateAppId, doInAppContext, + doInTenant, + identity, } diff --git a/packages/backend-core/db.js b/packages/backend-core/db.js index d2adf6c092..0d2869d9f1 100644 --- a/packages/backend-core/db.js +++ b/packages/backend-core/db.js @@ -3,4 +3,5 @@ module.exports = { ...require("./src/db/constants"), ...require("./src/db"), ...require("./src/db/views"), + ...require("./src/db/pouch"), } diff --git a/packages/backend-core/logging.js b/packages/backend-core/logging.js new file mode 100644 index 0000000000..da40fe3100 --- /dev/null +++ b/packages/backend-core/logging.js @@ -0,0 +1 @@ +module.exports = require("./src/logging") diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 7f27ee0a35..6383e1f2d8 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,45 +1,83 @@ { "name": "@budibase/backend-core", - "version": "1.0.104-alpha.0", + "version": "1.1.22", "description": "Budibase backend core libraries used in server and worker", - "main": "src/index.js", + "main": "dist/src/index.js", + "types": "dist/src/index.d.ts", + "exports": { + ".": "./dist/src/index.js", + "./tests": "./dist/tests/index.js", + "./*": "./dist/*.js" + }, "author": "Budibase", "license": "GPL-3.0", "scripts": { + "prebuild": "rimraf dist/", + "prepack": "cp package.json dist", + "build": "tsc -p tsconfig.build.json", + "build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput", "test": "jest", "test:watch": "jest --watchAll" }, "dependencies": { - "@techpass/passport-openidconnect": "^0.3.0", - "aws-sdk": "^2.901.0", - "bcryptjs": "^2.4.3", - "cls-hooked": "^4.2.2", - "ioredis": "^4.27.1", - "jsonwebtoken": "^8.5.1", - "koa-passport": "^4.1.4", - "lodash": "^4.17.21", - "lodash.isarguments": "^3.1.0", - "node-fetch": "^2.6.1", - "passport-google-auth": "^1.0.2", - "passport-google-oauth": "^2.0.0", - "passport-jwt": "^4.0.0", - "passport-local": "^1.0.0", - "sanitize-s3-objectkey": "^0.0.1", - "tar-fs": "^2.1.1", - "uuid": "^8.3.2", - "zlib": "^1.0.5" + "@budibase/types": "^1.1.22", + "@techpass/passport-openidconnect": "0.3.2", + "aws-sdk": "2.1030.0", + "bcrypt": "5.0.1", + "dotenv": "16.0.1", + "emitter-listener": "1.1.2", + "ioredis": "4.28.0", + "jsonwebtoken": "8.5.1", + "koa-passport": "4.1.4", + "lodash": "4.17.21", + "lodash.isarguments": "3.1.0", + "node-fetch": "2.6.7", + "passport-google-auth": "1.0.2", + "passport-google-oauth": "2.0.0", + "passport-jwt": "4.0.0", + "passport-local": "1.0.0", + "passport-oauth2-refresh": "^2.1.0", + "posthog-node": "1.3.0", + "pouchdb": "7.3.0", + "pouchdb-find": "7.2.2", + "pouchdb-replication-stream": "1.2.9", + "redlock": "4.2.0", + "sanitize-s3-objectkey": "0.0.1", + "semver": "7.3.7", + "tar-fs": "2.1.1", + "uuid": "8.3.2", + "zlib": "1.0.5" }, "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "moduleNameMapper": { + "@budibase/types": "/../types/src" + }, "setupFiles": [ - "./scripts/jestSetup.js" + "./scripts/jestSetup.ts" ] }, "devDependencies": { - "ioredis-mock": "^5.5.5", - "jest": "^26.6.3", - "pouchdb": "^7.2.1", - "pouchdb-adapter-memory": "^7.2.2", - "pouchdb-all-dbs": "^1.0.2" + "@shopify/jest-koa-mocks": "3.1.5", + "@types/jest": "27.5.1", + "@types/koa": "2.0.52", + "@types/lodash": "4.14.180", + "@types/node": "14.18.20", + "@types/node-fetch": "2.6.1", + "@types/pouchdb": "6.4.0", + "@types/redlock": "4.0.3", + "@types/semver": "7.3.7", + "@types/tar-fs": "2.0.1", + "@types/uuid": "8.3.4", + "ioredis-mock": "5.8.0", + "jest": "27.5.1", + "koa": "2.7.0", + "nodemon": "2.0.16", + "pouchdb-adapter-memory": "7.2.2", + "timekeeper": "2.2.0", + "ts-jest": "27.1.5", + "typescript": "4.7.3" }, "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc" } diff --git a/packages/backend-core/redis.js b/packages/backend-core/redis.js index 0a9dc91881..1f7a48540a 100644 --- a/packages/backend-core/redis.js +++ b/packages/backend-core/redis.js @@ -1,4 +1,5 @@ module.exports = { Client: require("./src/redis"), utils: require("./src/redis/utils"), + clients: require("./src/redis/init"), } diff --git a/packages/backend-core/scripts/jestSetup.js b/packages/backend-core/scripts/jestSetup.js deleted file mode 100644 index 93dbf3fd5a..0000000000 --- a/packages/backend-core/scripts/jestSetup.js +++ /dev/null @@ -1,6 +0,0 @@ -const env = require("../src/environment") - -env._set("SELF_HOSTED", "1") -env._set("NODE_ENV", "jest") -env._set("JWT_SECRET", "test-jwtsecret") -env._set("LOG_LEVEL", "silent") diff --git a/packages/backend-core/scripts/jestSetup.ts b/packages/backend-core/scripts/jestSetup.ts new file mode 100644 index 0000000000..1e86649a24 --- /dev/null +++ b/packages/backend-core/scripts/jestSetup.ts @@ -0,0 +1,12 @@ +import env from "../src/environment" +import { mocks } from "../tests/utilities" + +// mock all dates to 2020-01-01T00:00:00.000Z +// use tk.reset() to use real dates in individual tests +import tk from "timekeeper" +tk.freeze(mocks.date.MOCK_DATE) + +env._set("SELF_HOSTED", "1") +env._set("NODE_ENV", "jest") +env._set("JWT_SECRET", "test-jwtsecret") +env._set("LOG_LEVEL", "silent") diff --git a/packages/backend-core/src/auth.js b/packages/backend-core/src/auth.js index f6d53522d5..b60144a0de 100644 --- a/packages/backend-core/src/auth.js +++ b/packages/backend-core/src/auth.js @@ -2,6 +2,9 @@ const passport = require("koa-passport") const LocalStrategy = require("passport-local").Strategy const JwtStrategy = require("passport-jwt").Strategy const { getGlobalDB } = require("./tenancy") +const refresh = require("passport-oauth2-refresh") +const { Configs } = require("./constants") +const { getScopedConfig } = require("./db/utils") const { jwt, local, @@ -12,10 +15,13 @@ const { tenancy, appTenancy, authError, + ssoCallbackUrl, csrf, internalApi, } = require("./middleware") +const { invalidateUser } = require("./cache/user") + // Strategies passport.use(new LocalStrategy(local.options, local.authenticate)) passport.use(new JwtStrategy(jwt.options, jwt.authenticate)) @@ -29,11 +35,129 @@ passport.deserializeUser(async (user, done) => { const user = await db.get(user._id) return done(null, user) } catch (err) { - console.error("User not found", err) + console.error(`User not found`, err) return done(null, false, { message: "User not found" }) } }) +async function refreshOIDCAccessToken(db, chosenConfig, refreshToken) { + const callbackUrl = await oidc.getCallbackUrl(db, chosenConfig) + let enrichedConfig + let strategy + + try { + enrichedConfig = await oidc.fetchStrategyConfig(chosenConfig, callbackUrl) + if (!enrichedConfig) { + throw new Error("OIDC Config contents invalid") + } + strategy = await oidc.strategyFactory(enrichedConfig) + } catch (err) { + console.error(err) + throw new Error("Could not refresh OAuth Token") + } + + refresh.use(strategy, { + setRefreshOAuth2() { + return strategy._getOAuth2Client(enrichedConfig) + }, + }) + + return new Promise(resolve => { + refresh.requestNewAccessToken( + Configs.OIDC, + refreshToken, + (err, accessToken, refreshToken, params) => { + resolve({ err, accessToken, refreshToken, params }) + } + ) + }) +} + +async function refreshGoogleAccessToken(db, config, refreshToken) { + let callbackUrl = await google.getCallbackUrl(db, config) + + let strategy + try { + strategy = await google.strategyFactory(config, callbackUrl) + } catch (err) { + console.error(err) + throw new Error("Error constructing OIDC refresh strategy", err) + } + + refresh.use(strategy) + + return new Promise(resolve => { + refresh.requestNewAccessToken( + Configs.GOOGLE, + refreshToken, + (err, accessToken, refreshToken, params) => { + resolve({ err, accessToken, refreshToken, params }) + } + ) + }) +} + +async function refreshOAuthToken(refreshToken, configType, configId) { + const db = getGlobalDB() + + const config = await getScopedConfig(db, { + type: configType, + group: {}, + }) + + let chosenConfig = {} + let refreshResponse + if (configType === Configs.OIDC) { + // configId - retrieved from cookie. + chosenConfig = config.configs.filter(c => c.uuid === configId)[0] + if (!chosenConfig) { + throw new Error("Invalid OIDC configuration") + } + refreshResponse = await refreshOIDCAccessToken( + db, + chosenConfig, + refreshToken + ) + } else { + chosenConfig = config + refreshResponse = await refreshGoogleAccessToken( + db, + chosenConfig, + refreshToken + ) + } + + return refreshResponse +} + +async function updateUserOAuth(userId, oAuthConfig) { + const details = { + accessToken: oAuthConfig.accessToken, + refreshToken: oAuthConfig.refreshToken, + } + + try { + const db = getGlobalDB() + const dbUser = await db.get(userId) + + //Do not overwrite the refresh token if a valid one is not provided. + if (typeof details.refreshToken !== "string") { + delete details.refreshToken + } + + dbUser.oauth2 = { + ...dbUser.oauth2, + ...details, + } + + await db.put(dbUser) + + await invalidateUser(userId) + } catch (e) { + console.error("Could not update OAuth details for current user", e) + } +} + module.exports = { buildAuthMiddleware: authenticated, passport, @@ -46,4 +170,7 @@ module.exports = { authError, buildCsrfMiddleware: csrf, internalApi, + refreshOAuthToken, + updateUserOAuth, + ssoCallbackUrl, } diff --git a/packages/backend-core/src/cache/appMetadata.js b/packages/backend-core/src/cache/appMetadata.js index 9e2317ae37..b0d9481cbd 100644 --- a/packages/backend-core/src/cache/appMetadata.js +++ b/packages/backend-core/src/cache/appMetadata.js @@ -1,5 +1,5 @@ -const redis = require("../redis/authRedis") -const { getCouch } = require("../db") +const redis = require("../redis/init") +const { doWithDB } = require("../db") const { DocumentTypes } = require("../db/constants") const AppState = { @@ -10,12 +10,14 @@ const EXPIRY_SECONDS = 3600 /** * The default populate app metadata function */ -const populateFromDB = async (appId, CouchDB = null) => { - if (!CouchDB) { - CouchDB = getCouch() - } - const db = new CouchDB(appId, { skip_setup: true }) - return db.get(DocumentTypes.APP_METADATA) +const populateFromDB = async appId => { + return doWithDB( + appId, + db => { + return db.get(DocumentTypes.APP_METADATA) + }, + { skip_setup: true } + ) } const isInvalid = metadata => { @@ -27,17 +29,16 @@ const isInvalid = metadata => { * Use redis cache to first read the app metadata. * If not present fallback to loading the app metadata directly and re-caching. * @param {string} appId the id of the app to get metadata from. - * @param {object} CouchDB the database being passed * @returns {object} the app metadata. */ -exports.getAppMetadata = async (appId, CouchDB = null) => { +exports.getAppMetadata = async appId => { const client = await redis.getAppClient() // try cache let metadata = await client.get(appId) if (!metadata) { let expiry = EXPIRY_SECONDS try { - metadata = await populateFromDB(appId, CouchDB) + metadata = await populateFromDB(appId) } catch (err) { // app DB left around, but no metadata, it is invalid if (err && err.status === 404) { diff --git a/packages/backend-core/src/cache/base/index.ts b/packages/backend-core/src/cache/base/index.ts new file mode 100644 index 0000000000..f3216531f4 --- /dev/null +++ b/packages/backend-core/src/cache/base/index.ts @@ -0,0 +1,92 @@ +import { getTenantId } from "../../context" +import redis from "../../redis/init" +import RedisWrapper from "../../redis" + +function generateTenantKey(key: string) { + const tenantId = getTenantId() + return `${key}:${tenantId}` +} + +export = class BaseCache { + client: RedisWrapper | undefined + + constructor(client: RedisWrapper | undefined = undefined) { + this.client = client + } + + async getClient() { + return !this.client ? await redis.getCacheClient() : this.client + } + + async keys(pattern: string) { + const client = await this.getClient() + return client.keys(pattern) + } + + /** + * Read only from the cache. + */ + async get(key: string, opts = { useTenancy: true }) { + key = opts.useTenancy ? generateTenantKey(key) : key + const client = await this.getClient() + return client.get(key) + } + + /** + * Write to the cache. + */ + async store( + key: string, + value: any, + ttl: number | null = null, + opts = { useTenancy: true } + ) { + key = opts.useTenancy ? generateTenantKey(key) : key + const client = await this.getClient() + await client.store(key, value, ttl) + } + + /** + * Remove from cache. + */ + async delete(key: string, opts = { useTenancy: true }) { + key = opts.useTenancy ? generateTenantKey(key) : key + const client = await this.getClient() + return client.delete(key) + } + + /** + * Read from the cache. Write to the cache if not exists. + */ + async withCache( + key: string, + ttl: number, + fetchFn: any, + opts = { useTenancy: true } + ) { + const cachedValue = await this.get(key, opts) + if (cachedValue) { + return cachedValue + } + + try { + const fetchedValue = await fetchFn() + + await this.store(key, fetchedValue, ttl, opts) + return fetchedValue + } catch (err) { + console.error("Error fetching before cache - ", err) + throw err + } + } + + async bustCache(key: string, opts = { client: null }) { + const client = await this.getClient() + try { + await client.delete(generateTenantKey(key)) + } catch (err) { + console.error("Error busting cache - ", err) + throw err + } + } +} diff --git a/packages/backend-core/src/cache/generic.js b/packages/backend-core/src/cache/generic.js new file mode 100644 index 0000000000..e2f3915339 --- /dev/null +++ b/packages/backend-core/src/cache/generic.js @@ -0,0 +1,29 @@ +const BaseCache = require("./base") + +const GENERIC = new BaseCache() + +exports.CacheKeys = { + CHECKLIST: "checklist", + INSTALLATION: "installation", + ANALYTICS_ENABLED: "analyticsEnabled", + UNIQUE_TENANT_ID: "uniqueTenantId", + EVENTS: "events", + BACKFILL_METADATA: "backfillMetadata", +} + +exports.TTL = { + ONE_MINUTE: 600, + ONE_HOUR: 3600, + ONE_DAY: 86400, +} + +function performExport(funcName) { + return (...args) => GENERIC[funcName](...args) +} + +exports.keys = performExport("keys") +exports.get = performExport("get") +exports.store = performExport("store") +exports.delete = performExport("delete") +exports.withCache = performExport("withCache") +exports.bustCache = performExport("bustCache") diff --git a/packages/backend-core/src/cache/tests/writethrough.spec.js b/packages/backend-core/src/cache/tests/writethrough.spec.js new file mode 100644 index 0000000000..68db24b325 --- /dev/null +++ b/packages/backend-core/src/cache/tests/writethrough.spec.js @@ -0,0 +1,59 @@ +require("../../../tests/utilities/TestConfiguration") +const { Writethrough } = require("../writethrough") +const { dangerousGetDB } = require("../../db") +const tk = require("timekeeper") + +const START_DATE = Date.now() +tk.freeze(START_DATE) + +const DELAY = 5000 + +const db = dangerousGetDB("test") +const db2 = dangerousGetDB("test2") +const writethrough = new Writethrough(db, DELAY), writethrough2 = new Writethrough(db2, DELAY) + +describe("writethrough", () => { + describe("put", () => { + let first + it("should be able to store, will go to DB", async () => { + const response = await writethrough.put({ _id: "test", value: 1 }) + const output = await db.get(response.id) + first = output + expect(output.value).toBe(1) + }) + + it("second put shouldn't update DB", async () => { + const response = await writethrough.put({ ...first, value: 2 }) + const output = await db.get(response.id) + expect(first._rev).toBe(output._rev) + expect(output.value).toBe(1) + }) + + it("should put it again after delay period", async () => { + tk.freeze(START_DATE + DELAY + 1) + const response = await writethrough.put({ ...first, value: 3 }) + const output = await db.get(response.id) + expect(response.rev).not.toBe(first._rev) + expect(output.value).toBe(3) + }) + }) + + describe("get", () => { + it("should be able to retrieve", async () => { + const response = await writethrough.get("test") + expect(response.value).toBe(3) + }) + }) + + describe("same doc, different databases (tenancy)", () => { + it("should be able to two different databases", async () => { + const resp1 = await writethrough.put({ _id: "db1", value: "first" }) + const resp2 = await writethrough2.put({ _id: "db1", value: "second" }) + expect(resp1.rev).toBeDefined() + expect(resp2.rev).toBeDefined() + expect((await db.get("db1")).value).toBe("first") + expect((await db2.get("db1")).value).toBe("second") + }) + }) +}) + diff --git a/packages/backend-core/src/cache/user.js b/packages/backend-core/src/cache/user.js index b10f854002..130da1915e 100644 --- a/packages/backend-core/src/cache/user.js +++ b/packages/backend-core/src/cache/user.js @@ -1,5 +1,5 @@ -const redis = require("../redis/authRedis") -const { getTenantId, lookupTenantId, getGlobalDB } = require("../tenancy") +const redis = require("../redis/init") +const { getTenantId, lookupTenantId, doWithGlobalDB } = require("../tenancy") const env = require("../environment") const accounts = require("../cloud/accounts") @@ -9,9 +9,8 @@ const EXPIRY_SECONDS = 3600 * The default populate user function */ const populateFromDB = async (userId, tenantId) => { - const user = await getGlobalDB(tenantId).get(userId) + const user = await doWithGlobalDB(tenantId, db => db.get(userId)) user.budibaseAccess = true - if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { const account = await accounts.getAccount(user.email) if (account) { diff --git a/packages/backend-core/src/cache/writethrough.ts b/packages/backend-core/src/cache/writethrough.ts new file mode 100644 index 0000000000..ec6b1604c8 --- /dev/null +++ b/packages/backend-core/src/cache/writethrough.ts @@ -0,0 +1,119 @@ +import BaseCache from "./base" +import { getWritethroughClient } from "../redis/init" +import { logWarn } from "../logging" + +const DEFAULT_WRITE_RATE_MS = 10000 +let CACHE: BaseCache | null = null + +interface CacheItem { + doc: any + lastWrite: number +} + +async function getCache() { + if (!CACHE) { + const client = await getWritethroughClient() + CACHE = new BaseCache(client) + } + return CACHE +} + +function makeCacheKey(db: PouchDB.Database, key: string) { + return db.name + key +} + +function makeCacheItem(doc: any, lastWrite: number | null = null): CacheItem { + return { doc, lastWrite: lastWrite || Date.now() } +} + +export async function put( + db: PouchDB.Database, + doc: any, + writeRateMs: number = DEFAULT_WRITE_RATE_MS +) { + const cache = await getCache() + const key = doc._id + let cacheItem: CacheItem | undefined = await cache.get(makeCacheKey(db, key)) + const updateDb = !cacheItem || cacheItem.lastWrite < Date.now() - writeRateMs + let output = doc + if (updateDb) { + const writeDb = async (toWrite: any) => { + // doc should contain the _id and _rev + const response = await db.put(toWrite) + output = { + ...doc, + _id: response.id, + _rev: response.rev, + } + } + try { + await writeDb(doc) + } catch (err: any) { + if (err.status !== 409) { + throw err + } else { + // Swallow 409s but log them + logWarn(`Ignoring conflict in write-through cache`) + } + } + } + // if we are updating the DB then need to set the lastWrite to now + cacheItem = makeCacheItem(output, updateDb ? null : cacheItem?.lastWrite) + await cache.store(makeCacheKey(db, key), cacheItem) + return { ok: true, id: output._id, rev: output._rev } +} + +export async function get(db: PouchDB.Database, id: string): Promise { + const cache = await getCache() + const cacheKey = makeCacheKey(db, id) + let cacheItem: CacheItem = await cache.get(cacheKey) + if (!cacheItem) { + const doc = await db.get(id) + cacheItem = makeCacheItem(doc) + await cache.store(cacheKey, cacheItem) + } + return cacheItem.doc +} + +export async function remove( + db: PouchDB.Database, + docOrId: any, + rev?: any +): Promise { + const cache = await getCache() + if (!docOrId) { + throw new Error("No ID/Rev provided.") + } + const id = typeof docOrId === "string" ? docOrId : docOrId._id + rev = typeof docOrId === "string" ? rev : docOrId._rev + try { + await cache.delete(makeCacheKey(db, id)) + } finally { + await db.remove(id, rev) + } +} + +export class Writethrough { + db: PouchDB.Database + writeRateMs: number + + constructor( + db: PouchDB.Database, + writeRateMs: number = DEFAULT_WRITE_RATE_MS + ) { + this.db = db + this.writeRateMs = writeRateMs + } + + async put(doc: any) { + return put(this.db, doc, this.writeRateMs) + } + + async get(id: string) { + return get(this.db, id) + } + + async remove(docOrId: any, rev?: any) { + return remove(this.db, docOrId, rev) + } +} diff --git a/packages/backend-core/src/cloud/accounts.js b/packages/backend-core/src/cloud/accounts.js deleted file mode 100644 index 5730bc67a5..0000000000 --- a/packages/backend-core/src/cloud/accounts.js +++ /dev/null @@ -1,39 +0,0 @@ -const API = require("./api") -const env = require("../environment") -const { Headers } = require("../constants") - -const api = new API(env.ACCOUNT_PORTAL_URL) - -exports.getAccount = async email => { - const payload = { - email, - } - const response = await api.post(`/api/accounts/search`, { - body: payload, - headers: { - [Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, - }, - }) - const json = await response.json() - - if (response.status !== 200) { - throw new Error(`Error getting account by email ${email}`, json) - } - - return json[0] -} - -exports.getStatus = async () => { - const response = await api.get(`/api/status`, { - headers: { - [Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, - }, - }) - const json = await response.json() - - if (response.status !== 200) { - throw new Error(`Error getting status`) - } - - return json -} diff --git a/packages/backend-core/src/cloud/accounts.ts b/packages/backend-core/src/cloud/accounts.ts new file mode 100644 index 0000000000..cca7469060 --- /dev/null +++ b/packages/backend-core/src/cloud/accounts.ts @@ -0,0 +1,63 @@ +import API from "./api" +import env from "../environment" +import { Headers } from "../constants" +import { CloudAccount } from "@budibase/types" + +const api = new API(env.ACCOUNT_PORTAL_URL) + +export const getAccount = async ( + email: string +): Promise => { + const payload = { + email, + } + const response = await api.post(`/api/accounts/search`, { + body: payload, + headers: { + [Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, + }, + }) + + if (response.status !== 200) { + throw new Error(`Error getting account by email ${email}`) + } + + const json: CloudAccount[] = await response.json() + return json[0] +} + +export const getAccountByTenantId = async ( + tenantId: string +): Promise => { + const payload = { + tenantId, + } + const response = await api.post(`/api/accounts/search`, { + body: payload, + headers: { + [Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, + }, + }) + + if (response.status !== 200) { + throw new Error(`Error getting account by tenantId ${tenantId}`) + } + + const json: CloudAccount[] = await response.json() + return json[0] +} + +export const getStatus = async () => { + const response = await api.get(`/api/status`, { + headers: { + [Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, + }, + }) + const json = await response.json() + + if (response.status !== 200) { + throw new Error(`Error getting status`) + } + + return json +} diff --git a/packages/backend-core/src/cloud/api.js b/packages/backend-core/src/cloud/api.js index ffa785d02a..d4d4b6c8bb 100644 --- a/packages/backend-core/src/cloud/api.js +++ b/packages/backend-core/src/cloud/api.js @@ -29,9 +29,7 @@ class API { credentials: "include", } - const resp = await fetch(`${this.host}${url}`, requestOptions) - - return resp + return await fetch(`${this.host}${url}`, requestOptions) } post = this.apiCall("POST") diff --git a/packages/backend-core/src/clshooked/index.js b/packages/backend-core/src/clshooked/index.js new file mode 100644 index 0000000000..d69ffdd914 --- /dev/null +++ b/packages/backend-core/src/clshooked/index.js @@ -0,0 +1,650 @@ +const util = require("util") +const assert = require("assert") +const wrapEmitter = require("emitter-listener") +const async_hooks = require("async_hooks") + +const CONTEXTS_SYMBOL = "cls@contexts" +const ERROR_SYMBOL = "error@context" + +const DEBUG_CLS_HOOKED = process.env.DEBUG_CLS_HOOKED + +let currentUid = -1 + +module.exports = { + getNamespace: getNamespace, + createNamespace: createNamespace, + destroyNamespace: destroyNamespace, + reset: reset, + ERROR_SYMBOL: ERROR_SYMBOL, +} + +function Namespace(name) { + this.name = name + // changed in 2.7: no default context + this.active = null + this._set = [] + this.id = null + this._contexts = new Map() + this._indent = 0 + this._hook = null +} + +Namespace.prototype.set = function set(key, value) { + if (!this.active) { + throw new Error( + "No context available. ns.run() or ns.bind() must be called first." + ) + } + + this.active[key] = value + + if (DEBUG_CLS_HOOKED) { + const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent) + debug2( + indentStr + + "CONTEXT-SET KEY:" + + key + + "=" + + value + + " in ns:" + + this.name + + " currentUid:" + + currentUid + + " active:" + + util.inspect(this.active, { showHidden: true, depth: 2, colors: true }) + ) + } + + return value +} + +Namespace.prototype.get = function get(key) { + if (!this.active) { + if (DEBUG_CLS_HOOKED) { + const asyncHooksCurrentId = async_hooks.currentId() + const triggerId = async_hooks.triggerAsyncId() + const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent) + debug2( + `${indentStr}CONTEXT-GETTING KEY NO ACTIVE NS: (${this.name}) ${key}=undefined currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${this._set.length}` + ) + } + return undefined + } + if (DEBUG_CLS_HOOKED) { + const asyncHooksCurrentId = async_hooks.executionAsyncId() + const triggerId = async_hooks.triggerAsyncId() + const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent) + debug2( + indentStr + + "CONTEXT-GETTING KEY:" + + key + + "=" + + this.active[key] + + " (" + + this.name + + ") currentUid:" + + currentUid + + " active:" + + util.inspect(this.active, { showHidden: true, depth: 2, colors: true }) + ) + debug2( + `${indentStr}CONTEXT-GETTING KEY: (${this.name}) ${key}=${ + this.active[key] + } currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${ + this._set.length + } active:${util.inspect(this.active)}` + ) + } + return this.active[key] +} + +Namespace.prototype.createContext = function createContext() { + // Prototype inherit existing context if created a new child context within existing context. + let context = Object.create(this.active ? this.active : Object.prototype) + context._ns_name = this.name + context.id = currentUid + + if (DEBUG_CLS_HOOKED) { + const asyncHooksCurrentId = async_hooks.executionAsyncId() + const triggerId = async_hooks.triggerAsyncId() + const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent) + debug2( + `${indentStr}CONTEXT-CREATED Context: (${ + this.name + }) currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${ + this._set.length + } context:${util.inspect(context, { + showHidden: true, + depth: 2, + colors: true, + })}` + ) + } + + return context +} + +Namespace.prototype.run = function run(fn) { + let context = this.createContext() + this.enter(context) + + try { + if (DEBUG_CLS_HOOKED) { + const triggerId = async_hooks.triggerAsyncId() + const asyncHooksCurrentId = async_hooks.executionAsyncId() + const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent) + debug2( + `${indentStr}CONTEXT-RUN BEGIN: (${ + this.name + }) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${ + this._set.length + } context:${util.inspect(context)}` + ) + } + fn(context) + return context + } catch (exception) { + if (exception) { + exception[ERROR_SYMBOL] = context + } + throw exception + } finally { + if (DEBUG_CLS_HOOKED) { + const triggerId = async_hooks.triggerAsyncId() + const asyncHooksCurrentId = async_hooks.executionAsyncId() + const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent) + debug2( + `${indentStr}CONTEXT-RUN END: (${ + this.name + }) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${ + this._set.length + } ${util.inspect(context)}` + ) + } + this.exit(context) + } +} + +Namespace.prototype.runAndReturn = function runAndReturn(fn) { + let value + this.run(function (context) { + value = fn(context) + }) + return value +} + +/** + * Uses global Promise and assumes Promise is cls friendly or wrapped already. + * @param {function} fn + * @returns {*} + */ +Namespace.prototype.runPromise = function runPromise(fn) { + let context = this.createContext() + this.enter(context) + + let promise = fn(context) + if (!promise || !promise.then || !promise.catch) { + throw new Error("fn must return a promise.") + } + + if (DEBUG_CLS_HOOKED) { + debug2( + "CONTEXT-runPromise BEFORE: (" + + this.name + + ") currentUid:" + + currentUid + + " len:" + + this._set.length + + " " + + util.inspect(context) + ) + } + + return promise + .then(result => { + if (DEBUG_CLS_HOOKED) { + debug2( + "CONTEXT-runPromise AFTER then: (" + + this.name + + ") currentUid:" + + currentUid + + " len:" + + this._set.length + + " " + + util.inspect(context) + ) + } + this.exit(context) + return result + }) + .catch(err => { + err[ERROR_SYMBOL] = context + if (DEBUG_CLS_HOOKED) { + debug2( + "CONTEXT-runPromise AFTER catch: (" + + this.name + + ") currentUid:" + + currentUid + + " len:" + + this._set.length + + " " + + util.inspect(context) + ) + } + this.exit(context) + throw err + }) +} + +Namespace.prototype.bind = function bindFactory(fn, context) { + if (!context) { + if (!this.active) { + context = this.createContext() + } else { + context = this.active + } + } + + let self = this + return function clsBind() { + self.enter(context) + try { + return fn.apply(this, arguments) + } catch (exception) { + if (exception) { + exception[ERROR_SYMBOL] = context + } + throw exception + } finally { + self.exit(context) + } + } +} + +Namespace.prototype.enter = function enter(context) { + assert.ok(context, "context must be provided for entering") + if (DEBUG_CLS_HOOKED) { + const asyncHooksCurrentId = async_hooks.executionAsyncId() + const triggerId = async_hooks.triggerAsyncId() + const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent) + debug2( + `${indentStr}CONTEXT-ENTER: (${ + this.name + }) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${ + this._set.length + } ${util.inspect(context)}` + ) + } + + this._set.push(this.active) + this.active = context +} + +Namespace.prototype.exit = function exit(context) { + assert.ok(context, "context must be provided for exiting") + if (DEBUG_CLS_HOOKED) { + const asyncHooksCurrentId = async_hooks.executionAsyncId() + const triggerId = async_hooks.triggerAsyncId() + const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent) + debug2( + `${indentStr}CONTEXT-EXIT: (${ + this.name + }) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${ + this._set.length + } ${util.inspect(context)}` + ) + } + + // Fast path for most exits that are at the top of the stack + if (this.active === context) { + assert.ok(this._set.length, "can't remove top context") + this.active = this._set.pop() + return + } + + // Fast search in the stack using lastIndexOf + let index = this._set.lastIndexOf(context) + + if (index < 0) { + if (DEBUG_CLS_HOOKED) { + debug2( + "??ERROR?? context exiting but not entered - ignoring: " + + util.inspect(context) + ) + } + assert.ok( + index >= 0, + "context not currently entered; can't exit. \n" + + util.inspect(this) + + "\n" + + util.inspect(context) + ) + } else { + assert.ok(index, "can't remove top context") + this._set.splice(index, 1) + } +} + +Namespace.prototype.bindEmitter = function bindEmitter(emitter) { + assert.ok( + emitter.on && emitter.addListener && emitter.emit, + "can only bind real EEs" + ) + + let namespace = this + let thisSymbol = "context@" + this.name + + // Capture the context active at the time the emitter is bound. + function attach(listener) { + if (!listener) { + return + } + if (!listener[CONTEXTS_SYMBOL]) { + listener[CONTEXTS_SYMBOL] = Object.create(null) + } + + listener[CONTEXTS_SYMBOL][thisSymbol] = { + namespace: namespace, + context: namespace.active, + } + } + + // At emit time, bind the listener within the correct context. + function bind(unwrapped) { + if (!(unwrapped && unwrapped[CONTEXTS_SYMBOL])) { + return unwrapped + } + + let wrapped = unwrapped + let unwrappedContexts = unwrapped[CONTEXTS_SYMBOL] + Object.keys(unwrappedContexts).forEach(function (name) { + let thunk = unwrappedContexts[name] + wrapped = thunk.namespace.bind(wrapped, thunk.context) + }) + return wrapped + } + + wrapEmitter(emitter, attach, bind) +} + +/** + * If an error comes out of a namespace, it will have a context attached to it. + * This function knows how to find it. + * + * @param {Error} exception Possibly annotated error. + */ +Namespace.prototype.fromException = function fromException(exception) { + return exception[ERROR_SYMBOL] +} + +function getNamespace(name) { + return process.namespaces[name] +} + +function createNamespace(name) { + assert.ok(name, "namespace must be given a name.") + + if (DEBUG_CLS_HOOKED) { + debug2(`NS-CREATING NAMESPACE (${name})`) + } + let namespace = new Namespace(name) + namespace.id = currentUid + + const hook = async_hooks.createHook({ + init(asyncId, type, triggerId, resource) { + currentUid = async_hooks.executionAsyncId() + + //CHAIN Parent's Context onto child if none exists. This is needed to pass net-events.spec + // let initContext = namespace.active; + // if(!initContext && triggerId) { + // let parentContext = namespace._contexts.get(triggerId); + // if (parentContext) { + // namespace.active = parentContext; + // namespace._contexts.set(currentUid, parentContext); + // if (DEBUG_CLS_HOOKED) { + // const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent); + // debug2(`${indentStr}INIT [${type}] (${name}) WITH PARENT CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`); + // } + // } else if (DEBUG_CLS_HOOKED) { + // const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent); + // debug2(`${indentStr}INIT [${type}] (${name}) MISSING CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`); + // } + // }else { + // namespace._contexts.set(currentUid, namespace.active); + // if (DEBUG_CLS_HOOKED) { + // const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent); + // debug2(`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`); + // } + // } + if (namespace.active) { + namespace._contexts.set(asyncId, namespace.active) + + if (DEBUG_CLS_HOOKED) { + const indentStr = " ".repeat( + namespace._indent < 0 ? 0 : namespace._indent + ) + debug2( + `${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect( + namespace.active, + { showHidden: true, depth: 2, colors: true } + )} resource:${resource}` + ) + } + } else if (currentUid === 0) { + // CurrentId will be 0 when triggered from C++. Promise events + // https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid + const triggerId = async_hooks.triggerAsyncId() + const triggerIdContext = namespace._contexts.get(triggerId) + if (triggerIdContext) { + namespace._contexts.set(asyncId, triggerIdContext) + if (DEBUG_CLS_HOOKED) { + const indentStr = " ".repeat( + namespace._indent < 0 ? 0 : namespace._indent + ) + debug2( + `${indentStr}INIT USING CONTEXT FROM TRIGGERID [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect( + namespace.active, + { showHidden: true, depth: 2, colors: true } + )} resource:${resource}` + ) + } + } else if (DEBUG_CLS_HOOKED) { + const indentStr = " ".repeat( + namespace._indent < 0 ? 0 : namespace._indent + ) + debug2( + `${indentStr}INIT MISSING CONTEXT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect( + namespace.active, + { showHidden: true, depth: 2, colors: true } + )} resource:${resource}` + ) + } + } + + if (DEBUG_CLS_HOOKED && type === "PROMISE") { + debug2(util.inspect(resource, { showHidden: true })) + const parentId = resource.parentId + const indentStr = " ".repeat( + namespace._indent < 0 ? 0 : namespace._indent + ) + debug2( + `${indentStr}INIT RESOURCE-PROMISE [${type}] (${name}) parentId:${parentId} asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect( + namespace.active, + { showHidden: true, depth: 2, colors: true } + )} resource:${resource}` + ) + } + }, + before(asyncId) { + currentUid = async_hooks.executionAsyncId() + let context + + /* + if(currentUid === 0){ + // CurrentId will be 0 when triggered from C++. Promise events + // https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid + //const triggerId = async_hooks.triggerAsyncId(); + context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId); + }else{ + context = namespace._contexts.get(currentUid); + } + */ + + //HACK to work with promises until they are fixed in node > 8.1.1 + context = + namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid) + + if (context) { + if (DEBUG_CLS_HOOKED) { + const triggerId = async_hooks.triggerAsyncId() + const indentStr = " ".repeat( + namespace._indent < 0 ? 0 : namespace._indent + ) + debug2( + `${indentStr}BEFORE (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect( + namespace.active, + { showHidden: true, depth: 2, colors: true } + )} context:${util.inspect(context)}` + ) + namespace._indent += 2 + } + + namespace.enter(context) + } else if (DEBUG_CLS_HOOKED) { + const triggerId = async_hooks.triggerAsyncId() + const indentStr = " ".repeat( + namespace._indent < 0 ? 0 : namespace._indent + ) + debug2( + `${indentStr}BEFORE MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect( + namespace.active, + { showHidden: true, depth: 2, colors: true } + )} namespace._contexts:${util.inspect(namespace._contexts, { + showHidden: true, + depth: 2, + colors: true, + })}` + ) + namespace._indent += 2 + } + }, + after(asyncId) { + currentUid = async_hooks.executionAsyncId() + let context // = namespace._contexts.get(currentUid); + /* + if(currentUid === 0){ + // CurrentId will be 0 when triggered from C++. Promise events + // https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid + //const triggerId = async_hooks.triggerAsyncId(); + context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId); + }else{ + context = namespace._contexts.get(currentUid); + } + */ + //HACK to work with promises until they are fixed in node > 8.1.1 + context = + namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid) + + if (context) { + if (DEBUG_CLS_HOOKED) { + const triggerId = async_hooks.triggerAsyncId() + namespace._indent -= 2 + const indentStr = " ".repeat( + namespace._indent < 0 ? 0 : namespace._indent + ) + debug2( + `${indentStr}AFTER (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect( + namespace.active, + { showHidden: true, depth: 2, colors: true } + )} context:${util.inspect(context)}` + ) + } + + namespace.exit(context) + } else if (DEBUG_CLS_HOOKED) { + const triggerId = async_hooks.triggerAsyncId() + namespace._indent -= 2 + const indentStr = " ".repeat( + namespace._indent < 0 ? 0 : namespace._indent + ) + debug2( + `${indentStr}AFTER MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect( + namespace.active, + { showHidden: true, depth: 2, colors: true } + )} context:${util.inspect(context)}` + ) + } + }, + destroy(asyncId) { + currentUid = async_hooks.executionAsyncId() + if (DEBUG_CLS_HOOKED) { + const triggerId = async_hooks.triggerAsyncId() + const indentStr = " ".repeat( + namespace._indent < 0 ? 0 : namespace._indent + ) + debug2( + `${indentStr}DESTROY (${name}) currentUid:${currentUid} asyncId:${asyncId} triggerId:${triggerId} active:${util.inspect( + namespace.active, + { showHidden: true, depth: 2, colors: true } + )} context:${util.inspect(namespace._contexts.get(currentUid))}` + ) + } + + namespace._contexts.delete(asyncId) + }, + }) + + hook.enable() + namespace._hook = hook + + process.namespaces[name] = namespace + return namespace +} + +function destroyNamespace(name) { + let namespace = getNamespace(name) + + assert.ok(namespace, "can't delete nonexistent namespace! \"" + name + '"') + assert.ok( + namespace.id, + "don't assign to process.namespaces directly! " + util.inspect(namespace) + ) + + namespace._hook.disable() + namespace._contexts = null + process.namespaces[name] = null +} + +function reset() { + // must unregister async listeners + if (process.namespaces) { + Object.keys(process.namespaces).forEach(function (name) { + destroyNamespace(name) + }) + } + process.namespaces = Object.create(null) +} + +process.namespaces = process.namespaces || {} + +//const fs = require('fs'); +function debug2(...args) { + if (DEBUG_CLS_HOOKED) { + //fs.writeSync(1, `${util.format(...args)}\n`); + process._rawDebug(`${util.format(...args)}`) + } +} + +/*function getFunctionName(fn) { + if (!fn) { + return fn; + } + if (typeof fn === 'function') { + if (fn.name) { + return fn.name; + } + return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1]; + } else if (fn.constructor && fn.constructor.name) { + return fn.constructor.name; + } +}*/ diff --git a/packages/backend-core/src/constants.js b/packages/backend-core/src/constants.js index 559dc0e6b2..172e66e603 100644 --- a/packages/backend-core/src/constants.js +++ b/packages/backend-core/src/constants.js @@ -13,9 +13,11 @@ exports.Cookies = { exports.Headers = { API_KEY: "x-budibase-api-key", + LICENSE_KEY: "x-budibase-license-key", API_VER: "x-budibase-api-version", APP_ID: "x-budibase-app-id", TYPE: "x-budibase-type", + PREVIEW_ROLE: "x-budibase-role", TENANT_ID: "x-budibase-tenant-id", TOKEN: "x-budibase-token", CSRF_TOKEN: "x-csrf-token", diff --git a/packages/backend-core/src/context/FunctionContext.js b/packages/backend-core/src/context/FunctionContext.js index 1a3f65056e..c0ed34fe78 100644 --- a/packages/backend-core/src/context/FunctionContext.js +++ b/packages/backend-core/src/context/FunctionContext.js @@ -1,73 +1,47 @@ -const cls = require("cls-hooked") +const cls = require("../clshooked") const { newid } = require("../hashing") const REQUEST_ID_KEY = "requestId" +const MAIN_CTX = cls.createNamespace("main") + +function getContextStorage(namespace) { + if (namespace && namespace.active) { + let contextData = namespace.active + delete contextData.id + delete contextData._ns_name + return contextData + } + return {} +} class FunctionContext { - static getMiddleware(updateCtxFn = null, contextName = "session") { - const namespace = this.createNamespace(contextName) - - return async function (ctx, next) { - await new Promise( - namespace.bind(function (resolve, reject) { - // store a contextual request ID that can be used anywhere (audit logs) - namespace.set(REQUEST_ID_KEY, newid()) - namespace.bindEmitter(ctx.req) - namespace.bindEmitter(ctx.res) - - if (updateCtxFn) { - updateCtxFn(ctx) - } - next().then(resolve).catch(reject) - }) - ) - } + static run(callback) { + return MAIN_CTX.runAndReturn(async () => { + const namespaceId = newid() + MAIN_CTX.set(REQUEST_ID_KEY, namespaceId) + const namespace = cls.createNamespace(namespaceId) + let response = await namespace.runAndReturn(callback) + cls.destroyNamespace(namespaceId) + return response + }) } - static run(callback, contextName = "session") { - const namespace = this.createNamespace(contextName) - - return namespace.runAndReturn(callback) - } - - static setOnContext(key, value, contextName = "session") { - const namespace = this.createNamespace(contextName) + static setOnContext(key, value) { + const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY) + const namespace = cls.getNamespace(namespaceId) namespace.set(key, value) } - static getContextStorage() { - if (this._namespace && this._namespace.active) { - let contextData = this._namespace.active - delete contextData.id - delete contextData._ns_name - return contextData - } - - return {} - } - static getFromContext(key) { - const context = this.getContextStorage() + const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY) + const namespace = cls.getNamespace(namespaceId) + const context = getContextStorage(namespace) if (context) { return context[key] } else { return null } } - - static destroyNamespace(name = "session") { - if (this._namespace) { - cls.destroyNamespace(name) - this._namespace = null - } - } - - static createNamespace(name = "session") { - if (!this._namespace) { - this._namespace = cls.createNamespace(name) - } - return this._namespace - } } module.exports = FunctionContext diff --git a/packages/backend-core/src/context/constants.ts b/packages/backend-core/src/context/constants.ts new file mode 100644 index 0000000000..ef8dcd7821 --- /dev/null +++ b/packages/backend-core/src/context/constants.ts @@ -0,0 +1,17 @@ +export enum ContextKeys { + TENANT_ID = "tenantId", + GLOBAL_DB = "globalDb", + APP_ID = "appId", + IDENTITY = "identity", + // whatever the request app DB was + CURRENT_DB = "currentDb", + // get the prod app DB from the request + PROD_DB = "prodDb", + // get the dev app DB from the request + DEV_DB = "devDb", + DB_OPTS = "dbOpts", + // check if something else is using the context, don't close DB + TENANCY_IN_USE = "tenancyInUse", + APP_IN_USE = "appInUse", + IDENTITY_IN_USE = "identityInUse", +} diff --git a/packages/backend-core/src/context/deprovision.js b/packages/backend-core/src/context/deprovision.js index 9f89dbbfa9..ba3c2d8449 100644 --- a/packages/backend-core/src/context/deprovision.js +++ b/packages/backend-core/src/context/deprovision.js @@ -1,6 +1,6 @@ const { getGlobalUserParams, getAllApps } = require("../db/utils") -const { getDB } = require("../db") -const { getGlobalDB } = require("../tenancy") +const { doWithDB } = require("../db") +const { doWithGlobalDB } = require("../tenancy") const { StaticDatabases } = require("../db/constants") const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants @@ -8,11 +8,12 @@ const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name const removeTenantFromInfoDB = async tenantId => { try { - const infoDb = getDB(PLATFORM_INFO_DB) - let tenants = await infoDb.get(TENANT_DOC) - tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId) + await doWithDB(PLATFORM_INFO_DB, async infoDb => { + let tenants = await infoDb.get(TENANT_DOC) + tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId) - await infoDb.put(tenants) + await infoDb.put(tenants) + }) } catch (err) { console.error(`Error removing tenant ${tenantId} from info db`, err) throw err @@ -20,36 +21,8 @@ const removeTenantFromInfoDB = async tenantId => { } exports.removeUserFromInfoDB = async dbUser => { - const infoDb = getDB(PLATFORM_INFO_DB) - const keys = [dbUser._id, dbUser.email] - const userDocs = await infoDb.allDocs({ - keys, - include_docs: true, - }) - const toDelete = userDocs.rows.map(row => { - return { - ...row.doc, - _deleted: true, - } - }) - await infoDb.bulkDocs(toDelete) -} - -const removeUsersFromInfoDB = async tenantId => { - try { - const globalDb = getGlobalDB(tenantId) - const infoDb = getDB(PLATFORM_INFO_DB) - const allUsers = await globalDb.allDocs( - getGlobalUserParams(null, { - include_docs: true, - }) - ) - const allEmails = allUsers.rows.map(row => row.doc.email) - // get the id docs - let keys = allUsers.rows.map(row => row.id) - // and the email docs - keys = keys.concat(allEmails) - // retrieve the docs and delete them + await doWithDB(PLATFORM_INFO_DB, async infoDb => { + const keys = [dbUser._id, dbUser.email] const userDocs = await infoDb.allDocs({ keys, include_docs: true, @@ -61,26 +34,60 @@ const removeUsersFromInfoDB = async tenantId => { } }) await infoDb.bulkDocs(toDelete) - } catch (err) { - console.error(`Error removing tenant ${tenantId} users from info db`, err) - throw err - } + }) +} + +const removeUsersFromInfoDB = async tenantId => { + return doWithGlobalDB(tenantId, async db => { + try { + const allUsers = await db.allDocs( + getGlobalUserParams(null, { + include_docs: true, + }) + ) + await doWithDB(PLATFORM_INFO_DB, async infoDb => { + const allEmails = allUsers.rows.map(row => row.doc.email) + // get the id docs + let keys = allUsers.rows.map(row => row.id) + // and the email docs + keys = keys.concat(allEmails) + // retrieve the docs and delete them + const userDocs = await infoDb.allDocs({ + keys, + include_docs: true, + }) + const toDelete = userDocs.rows.map(row => { + return { + ...row.doc, + _deleted: true, + } + }) + await infoDb.bulkDocs(toDelete) + }) + } catch (err) { + console.error(`Error removing tenant ${tenantId} users from info db`, err) + throw err + } + }) } const removeGlobalDB = async tenantId => { - try { - const globalDb = getGlobalDB(tenantId) - await globalDb.destroy() - } catch (err) { - console.error(`Error removing tenant ${tenantId} users from info db`, err) - throw err - } + return doWithGlobalDB(tenantId, async db => { + try { + await db.destroy() + } catch (err) { + console.error(`Error removing tenant ${tenantId} users from info db`, err) + throw err + } + }) } const removeTenantApps = async tenantId => { try { const apps = await getAllApps({ all: true }) - const destroyPromises = apps.map(app => getDB(app.appId).destroy()) + const destroyPromises = apps.map(app => + doWithDB(app.appId, db => db.destroy()) + ) await Promise.allSettled(destroyPromises) } catch (err) { console.error(`Error removing tenant ${tenantId} apps`, err) diff --git a/packages/backend-core/src/context/identity.ts b/packages/backend-core/src/context/identity.ts new file mode 100644 index 0000000000..37e1ecf40a --- /dev/null +++ b/packages/backend-core/src/context/identity.ts @@ -0,0 +1,50 @@ +import { + IdentityContext, + IdentityType, + User, + UserContext, + isCloudAccount, + Account, + AccountUserContext, +} from "@budibase/types" +import * as context from "." + +export const getIdentity = (): IdentityContext | undefined => { + return context.getIdentity() +} + +export const doInIdentityContext = (identity: IdentityContext, task: any) => { + return context.doInIdentityContext(identity, task) +} + +export const doInUserContext = (user: User, task: any) => { + const userContext: UserContext = { + ...user, + _id: user._id as string, + type: IdentityType.USER, + } + return doInIdentityContext(userContext, task) +} + +export const doInAccountContext = (account: Account, task: any) => { + const _id = getAccountUserId(account) + const tenantId = account.tenantId + const accountContext: AccountUserContext = { + _id, + type: IdentityType.USER, + tenantId, + account, + } + return doInIdentityContext(accountContext, task) +} + +export const getAccountUserId = (account: Account) => { + let userId: string + if (isCloudAccount(account)) { + userId = account.budibaseUserId + } else { + // use account id as user id for self hosting + userId = account.accountId + } + return userId +} diff --git a/packages/backend-core/src/context/index.js b/packages/backend-core/src/context/index.js deleted file mode 100644 index ba9a7831db..0000000000 --- a/packages/backend-core/src/context/index.js +++ /dev/null @@ -1,227 +0,0 @@ -const env = require("../environment") -const { Headers } = require("../../constants") -const { SEPARATOR, DocumentTypes } = require("../db/constants") -const cls = require("./FunctionContext") -const { getCouch } = require("../db") -const { getProdAppID, getDevelopmentAppID } = require("../db/conversions") -const { isEqual } = require("lodash") - -// some test cases call functions directly, need to -// store an app ID to pretend there is a context -let TEST_APP_ID = null - -const ContextKeys = { - TENANT_ID: "tenantId", - APP_ID: "appId", - // whatever the request app DB was - CURRENT_DB: "currentDb", - // get the prod app DB from the request - PROD_DB: "prodDb", - // get the dev app DB from the request - DEV_DB: "devDb", - DB_OPTS: "dbOpts", -} - -exports.DEFAULT_TENANT_ID = "default" - -exports.isDefaultTenant = () => { - return exports.getTenantId() === exports.DEFAULT_TENANT_ID -} - -exports.isMultiTenant = () => { - return env.MULTI_TENANCY -} - -// used for automations, API endpoints should always be in context already -exports.doInTenant = (tenantId, task) => { - return cls.run(() => { - // set the tenant id - cls.setOnContext(ContextKeys.TENANT_ID, tenantId) - - // invoke the task - return task() - }) -} - -/** - * Given an app ID this will attempt to retrieve the tenant ID from it. - * @return {null|string} The tenant ID found within the app ID. - */ -exports.getTenantIDFromAppID = appId => { - if (!appId) { - return null - } - const split = appId.split(SEPARATOR) - const hasDev = split[1] === DocumentTypes.DEV - if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) { - return null - } - if (hasDev) { - return split[2] - } else { - return split[1] - } -} - -const setAppTenantId = appId => { - const appTenantId = this.getTenantIDFromAppID(appId) || this.DEFAULT_TENANT_ID - this.updateTenantId(appTenantId) -} - -exports.doInAppContext = (appId, task) => { - if (!appId) { - throw new Error("appId is required") - } - return cls.run(() => { - // set the app tenant id - setAppTenantId(appId) - - // set the app ID - cls.setOnContext(ContextKeys.APP_ID, appId) - - // invoke the task - return task() - }) -} - -exports.updateTenantId = tenantId => { - cls.setOnContext(ContextKeys.TENANT_ID, tenantId) -} - -exports.updateAppId = appId => { - try { - cls.setOnContext(ContextKeys.APP_ID, appId) - cls.setOnContext(ContextKeys.PROD_DB, null) - cls.setOnContext(ContextKeys.DEV_DB, null) - cls.setOnContext(ContextKeys.CURRENT_DB, null) - cls.setOnContext(ContextKeys.DB_OPTS, null) - } catch (err) { - if (env.isTest()) { - TEST_APP_ID = appId - } else { - throw err - } - } -} - -exports.setTenantId = ( - ctx, - opts = { allowQs: false, allowNoTenant: false } -) => { - let tenantId - // exit early if not multi-tenant - if (!exports.isMultiTenant()) { - cls.setOnContext(ContextKeys.TENANT_ID, this.DEFAULT_TENANT_ID) - return - } - - const allowQs = opts && opts.allowQs - const allowNoTenant = opts && opts.allowNoTenant - const header = ctx.request.headers[Headers.TENANT_ID] - const user = ctx.user || {} - if (allowQs) { - const query = ctx.request.query || {} - tenantId = query.tenantId - } - // override query string (if allowed) by user, or header - // URL params cannot be used in a middleware, as they are - // processed later in the chain - tenantId = user.tenantId || header || tenantId - - // Set the tenantId from the subdomain - if (!tenantId) { - tenantId = ctx.subdomains && ctx.subdomains[0] - } - - if (!tenantId && !allowNoTenant) { - ctx.throw(403, "Tenant id not set") - } - // check tenant ID just incase no tenant was allowed - if (tenantId) { - cls.setOnContext(ContextKeys.TENANT_ID, tenantId) - } -} - -exports.isTenantIdSet = () => { - const tenantId = cls.getFromContext(ContextKeys.TENANT_ID) - return !!tenantId -} - -exports.getTenantId = () => { - if (!exports.isMultiTenant()) { - return exports.DEFAULT_TENANT_ID - } - const tenantId = cls.getFromContext(ContextKeys.TENANT_ID) - if (!tenantId) { - throw new Error("Tenant id not found") - } - return tenantId -} - -exports.getAppId = () => { - const foundId = cls.getFromContext(ContextKeys.APP_ID) - if (!foundId && env.isTest() && TEST_APP_ID) { - return TEST_APP_ID - } else { - return foundId - } -} - -function getDB(key, opts) { - const dbOptsKey = `${key}${ContextKeys.DB_OPTS}` - let storedOpts = cls.getFromContext(dbOptsKey) - let db = cls.getFromContext(key) - if (db && isEqual(opts, storedOpts)) { - return db - } - const appId = exports.getAppId() - const CouchDB = getCouch() - let toUseAppId - switch (key) { - case ContextKeys.CURRENT_DB: - toUseAppId = appId - break - case ContextKeys.PROD_DB: - toUseAppId = getProdAppID(appId) - break - case ContextKeys.DEV_DB: - toUseAppId = getDevelopmentAppID(appId) - break - } - db = new CouchDB(toUseAppId, opts) - try { - cls.setOnContext(key, db) - if (opts) { - cls.setOnContext(dbOptsKey, opts) - } - } catch (err) { - if (!env.isTest()) { - throw err - } - } - return db -} - -/** - * Opens the app database based on whatever the request - * contained, dev or prod. - */ -exports.getAppDB = opts => { - return getDB(ContextKeys.CURRENT_DB, opts) -} - -/** - * This specifically gets the prod app ID, if the request - * contained a development app ID, this will open the prod one. - */ -exports.getProdAppDB = opts => { - return getDB(ContextKeys.PROD_DB, opts) -} - -/** - * This specifically gets the dev app ID, if the request - * contained a prod app ID, this will open the dev one. - */ -exports.getDevAppDB = opts => { - return getDB(ContextKeys.DEV_DB, opts) -} diff --git a/packages/backend-core/src/context/index.ts b/packages/backend-core/src/context/index.ts new file mode 100644 index 0000000000..1e430f01de --- /dev/null +++ b/packages/backend-core/src/context/index.ts @@ -0,0 +1,251 @@ +import env from "../environment" +import { SEPARATOR, DocumentTypes } from "../db/constants" +import cls from "./FunctionContext" +import { dangerousGetDB, closeDB } from "../db" +import { baseGlobalDBName } from "../tenancy/utils" +import { IdentityContext } from "@budibase/types" +import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants" +import { ContextKeys } from "./constants" +import { + updateUsing, + closeWithUsing, + setAppTenantId, + setIdentity, + closeAppDBs, + getContextDB, +} from "./utils" + +export const DEFAULT_TENANT_ID = _DEFAULT_TENANT_ID + +// some test cases call functions directly, need to +// store an app ID to pretend there is a context +let TEST_APP_ID: string | null = null + +export const closeTenancy = async () => { + let db + try { + if (env.USE_COUCH) { + db = getGlobalDB() + } + } catch (err) { + // no DB found - skip closing + return + } + await closeDB(db) + // clear from context now that database is closed/task is finished + cls.setOnContext(ContextKeys.TENANT_ID, null) + cls.setOnContext(ContextKeys.GLOBAL_DB, null) +} + +// export const isDefaultTenant = () => { +// return getTenantId() === DEFAULT_TENANT_ID +// } + +export const isMultiTenant = () => { + return env.MULTI_TENANCY +} + +/** + * Given an app ID this will attempt to retrieve the tenant ID from it. + * @return {null|string} The tenant ID found within the app ID. + */ +export const getTenantIDFromAppID = (appId: string) => { + if (!appId) { + return null + } + const split = appId.split(SEPARATOR) + const hasDev = split[1] === DocumentTypes.DEV + if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) { + return null + } + if (hasDev) { + return split[2] + } else { + return split[1] + } +} + +// used for automations, API endpoints should always be in context already +export const doInTenant = (tenantId: string | null, task: any) => { + // make sure default always selected in single tenancy + if (!env.MULTI_TENANCY) { + tenantId = tenantId || DEFAULT_TENANT_ID + } + // the internal function is so that we can re-use an existing + // context - don't want to close DB on a parent context + async function internal(opts = { existing: false }) { + // set the tenant id + global db if this is a new context + if (!opts.existing) { + updateTenantId(tenantId) + } + + try { + // invoke the task + return await task() + } finally { + await closeWithUsing(ContextKeys.TENANCY_IN_USE, () => { + return closeTenancy() + }) + } + } + + const existing = cls.getFromContext(ContextKeys.TENANT_ID) === tenantId + return updateUsing(ContextKeys.TENANCY_IN_USE, existing, internal) +} + +export const doInAppContext = (appId: string, task: any) => { + if (!appId) { + throw new Error("appId is required") + } + + const identity = getIdentity() + + // the internal function is so that we can re-use an existing + // context - don't want to close DB on a parent context + async function internal(opts = { existing: false }) { + // set the app tenant id + if (!opts.existing) { + setAppTenantId(appId) + } + // set the app ID + cls.setOnContext(ContextKeys.APP_ID, appId) + + // preserve the identity + if (identity) { + setIdentity(identity) + } + try { + // invoke the task + return await task() + } finally { + await closeWithUsing(ContextKeys.APP_IN_USE, async () => { + await closeAppDBs() + await closeTenancy() + }) + } + } + const existing = cls.getFromContext(ContextKeys.APP_ID) === appId + return updateUsing(ContextKeys.APP_IN_USE, existing, internal) +} + +export const doInIdentityContext = (identity: IdentityContext, task: any) => { + if (!identity) { + throw new Error("identity is required") + } + + async function internal(opts = { existing: false }) { + if (!opts.existing) { + cls.setOnContext(ContextKeys.IDENTITY, identity) + // set the tenant so that doInTenant will preserve identity + if (identity.tenantId) { + updateTenantId(identity.tenantId) + } + } + + try { + // invoke the task + return await task() + } finally { + await closeWithUsing(ContextKeys.IDENTITY_IN_USE, async () => { + setIdentity(null) + await closeTenancy() + }) + } + } + + const existing = cls.getFromContext(ContextKeys.IDENTITY) + return updateUsing(ContextKeys.IDENTITY_IN_USE, existing, internal) +} + +export const getIdentity = (): IdentityContext | undefined => { + try { + return cls.getFromContext(ContextKeys.IDENTITY) + } catch (e) { + // do nothing - identity is not in context + } +} + +export const updateTenantId = (tenantId: string | null) => { + cls.setOnContext(ContextKeys.TENANT_ID, tenantId) + if (env.USE_COUCH) { + setGlobalDB(tenantId) + } +} + +export const updateAppId = async (appId: string) => { + try { + // have to close first, before removing the databases from context + await closeAppDBs() + cls.setOnContext(ContextKeys.APP_ID, appId) + } catch (err) { + if (env.isTest()) { + TEST_APP_ID = appId + } else { + throw err + } + } +} + +export const setGlobalDB = (tenantId: string | null) => { + const dbName = baseGlobalDBName(tenantId) + const db = dangerousGetDB(dbName) + cls.setOnContext(ContextKeys.GLOBAL_DB, db) + return db +} + +export const getGlobalDB = () => { + const db = cls.getFromContext(ContextKeys.GLOBAL_DB) + if (!db) { + throw new Error("Global DB not found") + } + return db +} + +export const isTenantIdSet = () => { + const tenantId = cls.getFromContext(ContextKeys.TENANT_ID) + return !!tenantId +} + +export const getTenantId = () => { + if (!isMultiTenant()) { + return DEFAULT_TENANT_ID + } + const tenantId = cls.getFromContext(ContextKeys.TENANT_ID) + if (!tenantId) { + throw new Error("Tenant id not found") + } + return tenantId +} + +export const getAppId = () => { + const foundId = cls.getFromContext(ContextKeys.APP_ID) + if (!foundId && env.isTest() && TEST_APP_ID) { + return TEST_APP_ID + } else { + return foundId + } +} + +/** + * Opens the app database based on whatever the request + * contained, dev or prod. + */ +export const getAppDB = (opts?: any) => { + return getContextDB(ContextKeys.CURRENT_DB, opts) +} + +/** + * This specifically gets the prod app ID, if the request + * contained a development app ID, this will open the prod one. + */ +export const getProdAppDB = (opts?: any) => { + return getContextDB(ContextKeys.PROD_DB, opts) +} + +/** + * This specifically gets the dev app ID, if the request + * contained a prod app ID, this will open the dev one. + */ +export const getDevAppDB = (opts?: any) => { + return getContextDB(ContextKeys.DEV_DB, opts) +} diff --git a/packages/backend-core/src/context/tests/index.spec.ts b/packages/backend-core/src/context/tests/index.spec.ts new file mode 100644 index 0000000000..55ecd333a3 --- /dev/null +++ b/packages/backend-core/src/context/tests/index.spec.ts @@ -0,0 +1,148 @@ +import "../../../tests/utilities/TestConfiguration" +import * as context from ".." +import { DEFAULT_TENANT_ID } from "../../constants" +import env from "../../environment" + +// must use require to spy index file exports due to known issue in jest +const dbUtils = require("../../db") +jest.spyOn(dbUtils, "closeDB") +jest.spyOn(dbUtils, "dangerousGetDB") + +describe("context", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("doInTenant", () => { + describe("single-tenancy", () => { + it("defaults to the default tenant", () => { + const tenantId = context.getTenantId() + expect(tenantId).toBe(DEFAULT_TENANT_ID) + }) + + it("defaults to the default tenant db", async () => { + await context.doInTenant(DEFAULT_TENANT_ID, () => { + const db = context.getGlobalDB() + expect(db.name).toBe("global-db") + }) + expect(dbUtils.dangerousGetDB).toHaveBeenCalledTimes(1) + expect(dbUtils.closeDB).toHaveBeenCalledTimes(1) + }) + }) + + describe("multi-tenancy", () => { + beforeEach(() => { + env._set("MULTI_TENANCY", 1) + }) + + it("fails when no tenant id is set", () => { + const test = () => { + let error + try { + context.getTenantId() + } catch (e: any) { + error = e + } + expect(error.message).toBe("Tenant id not found") + } + + // test under no tenancy + test() + + // test after tenancy has been accessed to ensure cleanup + context.doInTenant("test", () => {}) + test() + }) + + it("fails when no tenant db is set", () => { + const test = () => { + let error + try { + context.getGlobalDB() + } catch (e: any) { + error = e + } + expect(error.message).toBe("Global DB not found") + } + + // test under no tenancy + test() + + // test after tenancy has been accessed to ensure cleanup + context.doInTenant("test", () => {}) + test() + }) + + it("sets tenant id", () => { + context.doInTenant("test", () => { + const tenantId = context.getTenantId() + expect(tenantId).toBe("test") + }) + }) + + it("initialises the tenant db", async () => { + await context.doInTenant("test", () => { + const db = context.getGlobalDB() + expect(db.name).toBe("test_global-db") + }) + expect(dbUtils.dangerousGetDB).toHaveBeenCalledTimes(1) + expect(dbUtils.closeDB).toHaveBeenCalledTimes(1) + }) + + it("sets the tenant id when nested with same tenant id", async () => { + await context.doInTenant("test", async () => { + const tenantId = context.getTenantId() + expect(tenantId).toBe("test") + + await context.doInTenant("test", async () => { + const tenantId = context.getTenantId() + expect(tenantId).toBe("test") + + await context.doInTenant("test", () => { + const tenantId = context.getTenantId() + expect(tenantId).toBe("test") + }) + }) + }) + }) + + it("initialises the tenant db when nested with same tenant id", async () => { + await context.doInTenant("test", async () => { + const db = context.getGlobalDB() + expect(db.name).toBe("test_global-db") + + await context.doInTenant("test", async () => { + const db = context.getGlobalDB() + expect(db.name).toBe("test_global-db") + + await context.doInTenant("test", () => { + const db = context.getGlobalDB() + expect(db.name).toBe("test_global-db") + }) + }) + }) + + // only 1 db is opened and closed + expect(dbUtils.dangerousGetDB).toHaveBeenCalledTimes(1) + expect(dbUtils.closeDB).toHaveBeenCalledTimes(1) + }) + + it("sets different tenant id inside another context", () => { + context.doInTenant("test", () => { + const tenantId = context.getTenantId() + expect(tenantId).toBe("test") + + context.doInTenant("nested", () => { + const tenantId = context.getTenantId() + expect(tenantId).toBe("nested") + + context.doInTenant("double-nested", () => { + const tenantId = context.getTenantId() + expect(tenantId).toBe("double-nested") + }) + }) + }) + }) + }) + }) +}) diff --git a/packages/backend-core/src/context/utils.ts b/packages/backend-core/src/context/utils.ts new file mode 100644 index 0000000000..62693f18e8 --- /dev/null +++ b/packages/backend-core/src/context/utils.ts @@ -0,0 +1,113 @@ +import { + DEFAULT_TENANT_ID, + getAppId, + getTenantIDFromAppID, + updateTenantId, +} from "./index" +import cls from "./FunctionContext" +import { IdentityContext } from "@budibase/types" +import { ContextKeys } from "./constants" +import { dangerousGetDB, closeDB } from "../db" +import { isEqual } from "lodash" +import { getDevelopmentAppID, getProdAppID } from "../db/conversions" +import env from "../environment" + +export async function updateUsing( + usingKey: string, + existing: boolean, + internal: (opts: { existing: boolean }) => Promise +) { + const using = cls.getFromContext(usingKey) + if (using && existing) { + cls.setOnContext(usingKey, using + 1) + return internal({ existing: true }) + } else { + return cls.run(async () => { + cls.setOnContext(usingKey, 1) + return internal({ existing: false }) + }) + } +} + +export async function closeWithUsing( + usingKey: string, + closeFn: () => Promise +) { + const using = cls.getFromContext(usingKey) + if (!using || using <= 1) { + await closeFn() + } else { + cls.setOnContext(usingKey, using - 1) + } +} + +export const setAppTenantId = (appId: string) => { + const appTenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID + updateTenantId(appTenantId) +} + +export const setIdentity = (identity: IdentityContext | null) => { + cls.setOnContext(ContextKeys.IDENTITY, identity) +} + +// this function makes sure the PouchDB objects are closed and +// fully deleted when finished - this protects against memory leaks +export async function closeAppDBs() { + const dbKeys = [ + ContextKeys.CURRENT_DB, + ContextKeys.PROD_DB, + ContextKeys.DEV_DB, + ] + for (let dbKey of dbKeys) { + const db = cls.getFromContext(dbKey) + if (!db) { + continue + } + await closeDB(db) + // clear the DB from context, incase someone tries to use it again + cls.setOnContext(dbKey, null) + } + // clear the app ID now that the databases are closed + if (cls.getFromContext(ContextKeys.APP_ID)) { + cls.setOnContext(ContextKeys.APP_ID, null) + } + if (cls.getFromContext(ContextKeys.DB_OPTS)) { + cls.setOnContext(ContextKeys.DB_OPTS, null) + } +} + +export function getContextDB(key: string, opts: any) { + const dbOptsKey = `${key}${ContextKeys.DB_OPTS}` + let storedOpts = cls.getFromContext(dbOptsKey) + let db = cls.getFromContext(key) + if (db && isEqual(opts, storedOpts)) { + return db + } + + const appId = getAppId() + let toUseAppId + + switch (key) { + case ContextKeys.CURRENT_DB: + toUseAppId = appId + break + case ContextKeys.PROD_DB: + toUseAppId = getProdAppID(appId) + break + case ContextKeys.DEV_DB: + toUseAppId = getDevelopmentAppID(appId) + break + } + db = dangerousGetDB(toUseAppId, opts) + try { + cls.setOnContext(key, db) + if (opts) { + cls.setOnContext(dbOptsKey, opts) + } + } catch (err) { + if (!env.isTest()) { + throw err + } + } + return db +} diff --git a/packages/backend-core/src/db/Replication.js b/packages/backend-core/src/db/Replication.ts similarity index 69% rename from packages/backend-core/src/db/Replication.js rename to packages/backend-core/src/db/Replication.ts index 7af3c2eb9d..b46f6072be 100644 --- a/packages/backend-core/src/db/Replication.js +++ b/packages/backend-core/src/db/Replication.ts @@ -1,27 +1,35 @@ -const { getDB } = require(".") +import { dangerousGetDB, closeDB } from "." class Replication { + source: any + target: any + replication: any + /** * * @param {String} source - the DB you want to replicate or rollback to * @param {String} target - the DB you want to replicate to, or rollback from */ - constructor({ source, target }) { - this.source = getDB(source) - this.target = getDB(target) + constructor({ source, target }: any) { + this.source = dangerousGetDB(source) + this.target = dangerousGetDB(target) } - promisify(operation, opts = {}) { + close() { + return Promise.all([closeDB(this.source), closeDB(this.target)]) + } + + promisify(operation: any, opts = {}) { return new Promise(resolve => { operation(this.target, opts) - .on("denied", function (err) { + .on("denied", function (err: any) { // a document failed to replicate (e.g. due to permissions) throw new Error(`Denied: Document failed to replicate ${err}`) }) - .on("complete", function (info) { + .on("complete", function (info: any) { return resolve(info) }) - .on("error", function (err) { + .on("error", function (err: any) { throw new Error(`Replication Error: ${err}`) }) }) @@ -51,7 +59,7 @@ class Replication { async rollback() { await this.target.destroy() // Recreate the DB again - this.target = getDB(this.target.name) + this.target = dangerousGetDB(this.target.name) await this.replicate() } @@ -60,4 +68,4 @@ class Replication { } } -module.exports = Replication +export default Replication diff --git a/packages/backend-core/src/db/constants.js b/packages/backend-core/src/db/constants.js deleted file mode 100644 index 5ee8033e05..0000000000 --- a/packages/backend-core/src/db/constants.js +++ /dev/null @@ -1,39 +0,0 @@ -exports.SEPARATOR = "_" - -const PRE_APP = "app" -const PRE_DEV = "dev" - -exports.DocumentTypes = { - USER: "us", - WORKSPACE: "workspace", - CONFIG: "config", - TEMPLATE: "template", - APP: PRE_APP, - DEV: PRE_DEV, - APP_DEV: `${PRE_APP}${exports.SEPARATOR}${PRE_DEV}`, - APP_METADATA: `${PRE_APP}${exports.SEPARATOR}metadata`, - ROLE: "role", - MIGRATIONS: "migrations", - DEV_INFO: "devinfo", -} - -exports.StaticDatabases = { - GLOBAL: { - name: "global-db", - docs: { - apiKeys: "apikeys", - usageQuota: "usage_quota", - }, - }, - // contains information about tenancy and so on - PLATFORM_INFO: { - name: "global-info", - docs: { - tenants: "tenants", - }, - }, -} - -exports.APP_PREFIX = exports.DocumentTypes.APP + exports.SEPARATOR -exports.APP_DEV = exports.APP_DEV_PREFIX = - exports.DocumentTypes.APP_DEV + exports.SEPARATOR diff --git a/packages/backend-core/src/db/constants.ts b/packages/backend-core/src/db/constants.ts new file mode 100644 index 0000000000..716762dd45 --- /dev/null +++ b/packages/backend-core/src/db/constants.ts @@ -0,0 +1,65 @@ +export const SEPARATOR = "_" +export const UNICODE_MAX = "\ufff0" + +/** + * Can be used to create a few different forms of querying a view. + */ +export enum AutomationViewModes { + ALL = "all", + AUTOMATION = "automation", + STATUS = "status", +} + +export enum ViewNames { + USER_BY_EMAIL = "by_email2", + BY_API_KEY = "by_api_key", + USER_BY_BUILDERS = "by_builders", + LINK = "by_link", + ROUTING = "screen_routes", + AUTOMATION_LOGS = "automation_logs", +} + +export const DeprecatedViews = { + [ViewNames.USER_BY_EMAIL]: [ + // removed due to inaccuracy in view doc filter logic + "by_email", + ], +} + +export enum DocumentTypes { + USER = "us", + WORKSPACE = "workspace", + CONFIG = "config", + TEMPLATE = "template", + APP = "app", + DEV = "dev", + APP_DEV = "app_dev", + APP_METADATA = "app_metadata", + ROLE = "role", + MIGRATIONS = "migrations", + DEV_INFO = "devinfo", + AUTOMATION_LOG = "log_au", +} + +export const StaticDatabases = { + GLOBAL: { + name: "global-db", + docs: { + apiKeys: "apikeys", + usageQuota: "usage_quota", + licenseInfo: "license_info", + }, + }, + // contains information about tenancy and so on + PLATFORM_INFO: { + name: "global-info", + docs: { + tenants: "tenants", + install: "install", + }, + }, +} + +export const APP_PREFIX = exports.DocumentTypes.APP + exports.SEPARATOR +export const APP_DEV = exports.DocumentTypes.APP_DEV + exports.SEPARATOR +export const APP_DEV_PREFIX = APP_DEV diff --git a/packages/backend-core/src/db/conversions.js b/packages/backend-core/src/db/conversions.js index 50d896322f..455cc712d8 100644 --- a/packages/backend-core/src/db/conversions.js +++ b/packages/backend-core/src/db/conversions.js @@ -23,24 +23,30 @@ exports.isDevApp = app => { } /** - * Convert a development app ID to a deployed app ID. + * Generates a development app ID from a real app ID. + * @returns {string} the dev app ID which can be used for dev database. */ -exports.getProdAppID = appId => { - // if dev, convert it - if (appId.startsWith(APP_DEV_PREFIX)) { - const id = appId.split(APP_DEV_PREFIX)[1] - return `${APP_PREFIX}${id}` +exports.getDevelopmentAppID = appId => { + if (!appId || appId.startsWith(APP_DEV_PREFIX)) { + return appId } - return appId + // split to take off the app_ element, then join it together incase any other app_ exist + const split = appId.split(APP_PREFIX) + split.shift() + const rest = split.join(APP_PREFIX) + return `${APP_DEV_PREFIX}${rest}` } /** - * Convert a deployed app ID to a development app ID. + * Convert a development app ID to a deployed app ID. */ -exports.getDevelopmentAppID = appId => { - if (!appId.startsWith(APP_DEV_PREFIX)) { - const id = appId.split(APP_PREFIX)[1] - return `${APP_DEV_PREFIX}${id}` +exports.getProdAppID = appId => { + if (!appId || !appId.startsWith(APP_DEV_PREFIX)) { + return appId } - return appId + // split to take off the app_dev element, then join it together incase any other app_ exist + const split = appId.split(APP_DEV_PREFIX) + split.shift() + const rest = split.join(APP_DEV_PREFIX) + return `${APP_PREFIX}${rest}` } diff --git a/packages/backend-core/src/db/index.js b/packages/backend-core/src/db/index.js index 163364dbf3..aa6f7ebc2c 100644 --- a/packages/backend-core/src/db/index.js +++ b/packages/backend-core/src/db/index.js @@ -1,13 +1,91 @@ -let Pouch +const pouch = require("./pouch") +const env = require("../environment") -module.exports.setDB = pouch => { - Pouch = pouch +const openDbs = [] +let PouchDB +let initialised = false +const dbList = new Set() + +if (env.MEMORY_LEAK_CHECK) { + setInterval(() => { + console.log("--- OPEN DBS ---") + console.log(openDbs) + }, 5000) } -module.exports.getDB = dbName => { - return new Pouch(dbName) +const put = + dbPut => + async (doc, options = {}) => { + if (!doc.createdAt) { + doc.createdAt = new Date().toISOString() + } + doc.updatedAt = new Date().toISOString() + return dbPut(doc, options) + } + +const checkInitialised = () => { + if (!initialised) { + throw new Error("init has not been called") + } } -module.exports.getCouch = () => { - return Pouch +exports.init = opts => { + PouchDB = pouch.getPouch(opts) + initialised = true +} + +// NOTE: THIS IS A DANGEROUS FUNCTION - USE WITH CAUTION +// this function is prone to leaks, should only be used +// in situations that using the function doWithDB does not work +exports.dangerousGetDB = (dbName, opts) => { + checkInitialised() + if (env.isTest()) { + dbList.add(dbName) + } + const db = new PouchDB(dbName, opts) + if (env.MEMORY_LEAK_CHECK) { + openDbs.push(db.name) + } + const dbPut = db.put + db.put = put(dbPut) + return db +} + +// use this function if you have called dangerousGetDB - close +// the databases you've opened once finished +exports.closeDB = async db => { + if (!db || env.isTest()) { + return + } + if (env.MEMORY_LEAK_CHECK) { + openDbs.splice(openDbs.indexOf(db.name), 1) + } + try { + // specifically await so that if there is an error, it can be ignored + return await db.close() + } catch (err) { + // ignore error, already closed + } +} + +// we have to use a callback for this so that we can close +// the DB when we're done, without this manual requests would +// need to close the database when done with it to avoid memory leaks +exports.doWithDB = async (dbName, cb, opts = {}) => { + const db = exports.dangerousGetDB(dbName, opts) + // need this to be async so that we can correctly close DB after all + // async operations have been completed + try { + return await cb(db) + } finally { + await exports.closeDB(db) + } +} + +exports.allDbs = () => { + if (!env.isTest()) { + throw new Error("Cannot be used outside test environment.") + } + checkInitialised() + return [...dbList] } diff --git a/packages/backend-core/src/db/pouch.js b/packages/backend-core/src/db/pouch.js new file mode 100644 index 0000000000..59b7ff8ae7 --- /dev/null +++ b/packages/backend-core/src/db/pouch.js @@ -0,0 +1,117 @@ +const PouchDB = require("pouchdb") +const env = require("../environment") + +exports.getUrlInfo = (url = env.COUCH_DB_URL) => { + let cleanUrl, username, password, host + if (url) { + // Ensure the URL starts with a protocol + const protoRegex = /^https?:\/\//i + if (!protoRegex.test(url)) { + url = `http://${url}` + } + + // Split into protocol and remainder + const split = url.split("://") + const protocol = split[0] + const rest = split.slice(1).join("://") + + // Extract auth if specified + if (url.includes("@")) { + // Split into host and remainder + let parts = rest.split("@") + host = parts[parts.length - 1] + let auth = parts.slice(0, -1).join("@") + + // Split auth into username and password + if (auth.includes(":")) { + const authParts = auth.split(":") + username = authParts[0] + password = authParts.slice(1).join(":") + } else { + username = auth + } + } else { + host = rest + } + cleanUrl = `${protocol}://${host}` + } + return { + url: cleanUrl, + auth: { + username, + password, + }, + } +} + +exports.getCouchInfo = () => { + const urlInfo = exports.getUrlInfo() + let username + let password + if (env.COUCH_DB_USERNAME) { + // set from env + username = env.COUCH_DB_USERNAME + } else if (urlInfo.auth.username) { + // set from url + username = urlInfo.auth.username + } else if (!env.isTest()) { + throw new Error("CouchDB username not set") + } + if (env.COUCH_DB_PASSWORD) { + // set from env + password = env.COUCH_DB_PASSWORD + } else if (urlInfo.auth.password) { + // set from url + password = urlInfo.auth.password + } else if (!env.isTest()) { + throw new Error("CouchDB password not set") + } + const authCookie = Buffer.from(`${username}:${password}`).toString("base64") + return { + url: urlInfo.url, + auth: { + username: username, + password: password, + }, + cookie: `Basic ${authCookie}`, + } +} + +/** + * Return a constructor for PouchDB. + * This should be rarely used outside of the main application config. + * Exposed for exceptional cases such as in-memory views. + */ +exports.getPouch = (opts = {}) => { + let { url, cookie } = exports.getCouchInfo() + let POUCH_DB_DEFAULTS = { + prefix: url, + fetch: (url, opts) => { + // use a specific authorization cookie - be very explicit about how we authenticate + opts.headers.set("Authorization", cookie) + return PouchDB.fetch(url, opts) + }, + } + + if (opts.inMemory) { + const inMemory = require("pouchdb-adapter-memory") + PouchDB.plugin(inMemory) + POUCH_DB_DEFAULTS = { + prefix: undefined, + adapter: "memory", + } + } + + if (opts.replication) { + const replicationStream = require("pouchdb-replication-stream") + PouchDB.plugin(replicationStream.plugin) + PouchDB.adapter("writableStream", replicationStream.adapters.writableStream) + } + + if (opts.find) { + const find = require("pouchdb-find") + PouchDB.plugin(find) + } + + return PouchDB.defaults(POUCH_DB_DEFAULTS) +} diff --git a/packages/backend-core/src/db/tests/index.spec.js b/packages/backend-core/src/db/tests/index.spec.js new file mode 100644 index 0000000000..bc0c638126 --- /dev/null +++ b/packages/backend-core/src/db/tests/index.spec.js @@ -0,0 +1,26 @@ +require("../../../tests/utilities/TestConfiguration") +const { dangerousGetDB } = require("../") + +describe("db", () => { + + describe("getDB", () => { + it("returns a db", async () => { + const db = dangerousGetDB("test") + expect(db).toBeDefined() + expect(db._adapter).toBe("memory") + expect(db.prefix).toBe("_pouch_") + expect(db.name).toBe("test") + }) + + it("uses the custom put function", async () => { + const db = dangerousGetDB("test") + let doc = { _id: "test" } + await db.put(doc) + doc = await db.get(doc._id) + expect(doc.createdAt).toBe(new Date().toISOString()) + expect(doc.updatedAt).toBe(new Date().toISOString()) + await db.destroy() + }) + }) +}) + diff --git a/packages/backend-core/src/db/tests/pouch.spec.js b/packages/backend-core/src/db/tests/pouch.spec.js new file mode 100644 index 0000000000..30cdd0f5ec --- /dev/null +++ b/packages/backend-core/src/db/tests/pouch.spec.js @@ -0,0 +1,62 @@ +require("../../../tests/utilities/TestConfiguration") +const getUrlInfo = require("../pouch").getUrlInfo + +describe("pouch", () => { + describe("Couch DB URL parsing", () => { + it("should handle a null Couch DB URL", () => { + const info = getUrlInfo(null) + expect(info.url).toBeUndefined() + expect(info.auth.username).toBeUndefined() + }) + it("should be able to parse a basic Couch DB URL", () => { + const info = getUrlInfo("http://host.com") + expect(info.url).toBe("http://host.com") + expect(info.auth.username).toBeUndefined() + }) + it("should be able to parse a Couch DB basic URL with HTTPS", () => { + const info = getUrlInfo("https://host.com") + expect(info.url).toBe("https://host.com") + expect(info.auth.username).toBeUndefined() + }) + it("should be able to parse a basic Couch DB URL with a custom port", () => { + const info = getUrlInfo("https://host.com:1234") + expect(info.url).toBe("https://host.com:1234") + expect(info.auth.username).toBeUndefined() + }) + it("should be able to parse a Couch DB URL with auth", () => { + const info = getUrlInfo("https://user:pass@host.com:1234") + expect(info.url).toBe("https://host.com:1234") + expect(info.auth.username).toBe("user") + expect(info.auth.password).toBe("pass") + }) + it("should be able to parse a Couch DB URL with auth and special chars", () => { + const info = getUrlInfo("https://user:s:p@s://@://:d@;][~s@host.com:1234") + expect(info.url).toBe("https://host.com:1234") + expect(info.auth.username).toBe("user") + expect(info.auth.password).toBe("s:p@s://@://:d@;][~s") + }) + it("should be able to parse a Couch DB URL without a protocol", () => { + const info = getUrlInfo("host.com:1234") + expect(info.url).toBe("http://host.com:1234") + expect(info.auth.username).toBeUndefined() + }) + it("should be able to parse a Couch DB URL with auth and without a protocol", () => { + const info = getUrlInfo("user:s:p@s://@://:d@;][~s@host.com:1234") + expect(info.url).toBe("http://host.com:1234") + expect(info.auth.username).toBe("user") + expect(info.auth.password).toBe("s:p@s://@://:d@;][~s") + }) + it("should be able to parse a Couch DB URL with only username auth", () => { + const info = getUrlInfo("https://user@host.com:1234") + expect(info.url).toBe("https://host.com:1234") + expect(info.auth.username).toBe("user") + expect(info.auth.password).toBeUndefined() + }) + it("should be able to parse a Couch DB URL with only username auth and without a protocol", () => { + const info = getUrlInfo("user@host.com:1234") + expect(info.url).toBe("http://host.com:1234") + expect(info.auth.username).toBe("user") + expect(info.auth.password).toBeUndefined() + }) + }) +}) diff --git a/packages/backend-core/src/db/tests/utils.spec.js b/packages/backend-core/src/db/tests/utils.spec.js new file mode 100644 index 0000000000..5f9a224e7a --- /dev/null +++ b/packages/backend-core/src/db/tests/utils.spec.js @@ -0,0 +1,194 @@ +require("../../../tests/utilities/TestConfiguration"); +const { + generateAppID, + getDevelopmentAppID, + getProdAppID, + isDevAppID, + isProdAppID, + getPlatformUrl, + getScopedConfig +} = require("../utils") +const tenancy = require("../../tenancy"); +const { Configs, DEFAULT_TENANT_ID } = require("../../constants"); +const env = require("../../environment") + +describe("utils", () => { + describe("app ID manipulation", () => { + + function getID() { + const appId = generateAppID() + const split = appId.split("_") + const uuid = split[split.length - 1] + const devAppId = `app_dev_${uuid}` + return { appId, devAppId, split, uuid } + } + + it("should be able to generate a new app ID", () => { + expect(generateAppID().startsWith("app_")).toEqual(true) + }) + + it("should be able to convert a production app ID to development", () => { + const { appId, uuid } = getID() + expect(getDevelopmentAppID(appId)).toEqual(`app_dev_${uuid}`) + }) + + it("should be able to convert a development app ID to development", () => { + const { devAppId, uuid } = getID() + expect(getDevelopmentAppID(devAppId)).toEqual(`app_dev_${uuid}`) + }) + + it("should be able to convert a development ID to a production", () => { + const { devAppId, uuid } = getID() + expect(getProdAppID(devAppId)).toEqual(`app_${uuid}`) + }) + + it("should be able to convert a production ID to production", () => { + const { appId, uuid } = getID() + expect(getProdAppID(appId)).toEqual(`app_${uuid}`) + }) + + it("should be able to confirm dev app ID is development", () => { + const { devAppId } = getID() + expect(isDevAppID(devAppId)).toEqual(true) + }) + + it("should be able to confirm prod app ID is not development", () => { + const { appId } = getID() + expect(isDevAppID(appId)).toEqual(false) + }) + + it("should be able to confirm prod app ID is prod", () => { + const { appId } = getID() + expect(isProdAppID(appId)).toEqual(true) + }) + + it("should be able to confirm dev app ID is not prod", () => { + const { devAppId } = getID() + expect(isProdAppID(devAppId)).toEqual(false) + }) + }) +}) + +const DB_URL = "http://dburl.com" +const DEFAULT_URL = "http://localhost:10000" +const ENV_URL = "http://env.com" + +const setDbPlatformUrl = async () => { + const db = tenancy.getGlobalDB() + db.put({ + _id: "config_settings", + type: Configs.SETTINGS, + config: { + platformUrl: DB_URL + } + }) +} + +const clearSettingsConfig = async () => { + await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { + const db = tenancy.getGlobalDB() + try { + const config = await db.get("config_settings") + await db.remove("config_settings", config._rev) + } catch (e) { + if (e.status !== 404) { + throw e + } + } + }) +} + +describe("getPlatformUrl", () => { + describe("self host", () => { + + beforeEach(async () => { + env._set("SELF_HOST", 1) + await clearSettingsConfig() + }) + + it("gets the default url", async () => { + await tenancy.doInTenant(null, async () => { + const url = await getPlatformUrl() + expect(url).toBe(DEFAULT_URL) + }) + }) + + it("gets the platform url from the environment", async () => { + await tenancy.doInTenant(null, async () => { + env._set("PLATFORM_URL", ENV_URL) + const url = await getPlatformUrl() + expect(url).toBe(ENV_URL) + }) + }) + + it("gets the platform url from the database", async () => { + await tenancy.doInTenant(null, async () => { + await setDbPlatformUrl() + const url = await getPlatformUrl() + expect(url).toBe(DB_URL) + }) + }) + }) + + + describe("cloud", () => { + const TENANT_AWARE_URL = "http://default.env.com" + + beforeEach(async () => { + env._set("SELF_HOSTED", 0) + env._set("MULTI_TENANCY", 1) + env._set("PLATFORM_URL", ENV_URL) + await clearSettingsConfig() + }) + + it("gets the platform url from the environment without tenancy", async () => { + await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { + const url = await getPlatformUrl({ tenantAware: false }) + expect(url).toBe(ENV_URL) + }) + }) + + it("gets the platform url from the environment with tenancy", async () => { + await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { + const url = await getPlatformUrl() + expect(url).toBe(TENANT_AWARE_URL) + }) + }) + + it("never gets the platform url from the database", async () => { + await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { + await setDbPlatformUrl() + const url = await getPlatformUrl() + expect(url).toBe(TENANT_AWARE_URL) + }) + }) + }) +}) + +describe("getScopedConfig", () => { + describe("settings config", () => { + + beforeEach(async () => { + env._set("SELF_HOSTED", 1) + env._set("PLATFORM_URL", "") + await clearSettingsConfig() + }) + + it("returns the platform url with an existing config", async () => { + await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { + await setDbPlatformUrl() + const db = tenancy.getGlobalDB() + const config = await getScopedConfig(db, { type: Configs.SETTINGS }) + expect(config.platformUrl).toBe(DB_URL) + }) + }) + + it("returns the platform url without an existing config", async () => { + await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { + const db = tenancy.getGlobalDB() + const config = await getScopedConfig(db, { type: Configs.SETTINGS }) + expect(config.platformUrl).toBe(DEFAULT_URL) + }) + }) + }) +}) diff --git a/packages/backend-core/src/db/utils.js b/packages/backend-core/src/db/utils.ts similarity index 60% rename from packages/backend-core/src/db/utils.js rename to packages/backend-core/src/db/utils.ts index feb17c4129..ba3f1dd3e9 100644 --- a/packages/backend-core/src/db/utils.js +++ b/packages/backend-core/src/db/utils.ts @@ -1,46 +1,33 @@ -const { newid } = require("../hashing") -const Replication = require("./Replication") -const { DEFAULT_TENANT_ID, Configs } = require("../constants") -const env = require("../environment") -const { - StaticDatabases, - SEPARATOR, - DocumentTypes, - APP_PREFIX, - APP_DEV, -} = require("./constants") -const { getTenantId, getGlobalDBName } = require("../tenancy") -const fetch = require("node-fetch") -const { getCouch } = require("./index") -const { getAppMetadata } = require("../cache/appMetadata") -const { checkSlashesInUrl } = require("../helpers") -const { - isDevApp, - isProdAppID, - isDevAppID, - getDevelopmentAppID, - getProdAppID, -} = require("./conversions") +import { newid } from "../hashing" +import { DEFAULT_TENANT_ID, Configs } from "../constants" +import env from "../environment" +import { SEPARATOR, DocumentTypes, UNICODE_MAX, ViewNames } from "./constants" +import { getTenantId, getGlobalDBName, getGlobalDB } from "../tenancy" +import fetch from "node-fetch" +import { doWithDB, allDbs } from "./index" +import { getCouchInfo } from "./pouch" +import { getAppMetadata } from "../cache/appMetadata" +import { checkSlashesInUrl } from "../helpers" +import { isDevApp, isDevAppID } from "./conversions" +import { APP_PREFIX } from "./constants" +import * as events from "../events" -const UNICODE_MAX = "\ufff0" +export * from "./constants" +export * from "./conversions" +export { default as Replication } from "./Replication" -exports.ViewNames = { - USER_BY_EMAIL: "by_email", - BY_API_KEY: "by_api_key", +/** + * Generates a new app ID. + * @returns {string} The new app ID which the app doc can be stored under. + */ +export const generateAppID = (tenantId = null) => { + let id = APP_PREFIX + if (tenantId) { + id += `${tenantId}${SEPARATOR}` + } + return `${id}${newid()}` } -exports.StaticDatabases = StaticDatabases - -exports.DocumentTypes = DocumentTypes -exports.APP_PREFIX = APP_PREFIX -exports.APP_DEV = exports.APP_DEV_PREFIX = APP_DEV -exports.SEPARATOR = SEPARATOR -exports.isDevApp = isDevApp -exports.isProdAppID = isProdAppID -exports.isDevAppID = isDevAppID -exports.getDevelopmentAppID = getDevelopmentAppID -exports.getProdAppID = getProdAppID - /** * If creating DB allDocs/query params with only a single top level ID this can be used, this * is usually the case as most of our docs are top level e.g. tables, automations, users and so on. @@ -53,7 +40,11 @@ exports.getProdAppID = getProdAppID * @param {object} otherProps Add any other properties onto the request, e.g. include_docs. * @returns {object} Parameters which can then be used with an allDocs request. */ -function getDocParams(docType, docId = null, otherProps = {}) { +export function getDocParams( + docType: any, + docId: any = null, + otherProps: any = {} +) { if (docId == null) { docId = "" } @@ -63,20 +54,26 @@ function getDocParams(docType, docId = null, otherProps = {}) { endkey: `${docType}${SEPARATOR}${docId}${UNICODE_MAX}`, } } -exports.getDocParams = getDocParams + +/** + * Retrieve the correct index for a view based on default design DB. + */ +export function getQueryIndex(viewName: ViewNames) { + return `database/${viewName}` +} /** * Generates a new workspace ID. * @returns {string} The new workspace ID which the workspace doc can be stored under. */ -exports.generateWorkspaceID = () => { +export function generateWorkspaceID() { return `${DocumentTypes.WORKSPACE}${SEPARATOR}${newid()}` } /** * Gets parameters for retrieving workspaces. */ -exports.getWorkspaceParams = (id = "", otherProps = {}) => { +export function getWorkspaceParams(id = "", otherProps = {}) { return { ...otherProps, startkey: `${DocumentTypes.WORKSPACE}${SEPARATOR}${id}`, @@ -88,20 +85,24 @@ exports.getWorkspaceParams = (id = "", otherProps = {}) => { * Generates a new global user ID. * @returns {string} The new user ID which the user doc can be stored under. */ -exports.generateGlobalUserID = id => { +export function generateGlobalUserID(id?: any) { return `${DocumentTypes.USER}${SEPARATOR}${id || newid()}` } /** * Gets parameters for retrieving users. */ -exports.getGlobalUserParams = (globalId, otherProps = {}) => { +export function getGlobalUserParams(globalId: any, otherProps: any = {}) { if (!globalId) { globalId = "" } + const startkey = otherProps?.startkey return { ...otherProps, - startkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}`, + // need to include this incase pagination + startkey: startkey + ? startkey + : `${DocumentTypes.USER}${SEPARATOR}${globalId}`, endkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}${UNICODE_MAX}`, } } @@ -110,14 +111,18 @@ exports.getGlobalUserParams = (globalId, otherProps = {}) => { * Generates a template ID. * @param ownerId The owner/user of the template, this could be global or a workspace level. */ -exports.generateTemplateID = ownerId => { +export function generateTemplateID(ownerId: any) { return `${DocumentTypes.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}${newid()}` } /** * Gets parameters for retrieving templates. Owner ID must be specified, either global or a workspace level. */ -exports.getTemplateParams = (ownerId, templateId, otherProps = {}) => { +export function getTemplateParams( + ownerId: any, + templateId: any, + otherProps = {} +) { if (!templateId) { templateId = "" } @@ -138,37 +143,18 @@ exports.getTemplateParams = (ownerId, templateId, otherProps = {}) => { * Generates a new role ID. * @returns {string} The new role ID which the role doc can be stored under. */ -exports.generateRoleID = id => { +export function generateRoleID(id: any) { return `${DocumentTypes.ROLE}${SEPARATOR}${id || newid()}` } /** * Gets parameters for retrieving a role, this is a utility function for the getDocParams function. */ -exports.getRoleParams = (roleId = null, otherProps = {}) => { +export function getRoleParams(roleId = null, otherProps = {}) { return getDocParams(DocumentTypes.ROLE, roleId, otherProps) } -exports.getCouchUrl = () => { - if (!env.COUCH_DB_URL) return - - // username and password already exist in URL - if (env.COUCH_DB_URL.includes("@")) { - return env.COUCH_DB_URL - } - - const [protocol, ...rest] = env.COUCH_DB_URL.split("://") - - if (!env.COUCH_DB_USERNAME || !env.COUCH_DB_PASSWORD) { - throw new Error( - "CouchDB configuration invalid. You must provide a fully qualified CouchDB url, or the COUCH_DB_USER and COUCH_DB_PASSWORD environment variables." - ) - } - - return `${protocol}://${env.COUCH_DB_USERNAME}:${env.COUCH_DB_PASSWORD}@${rest}` -} - -exports.getStartEndKeyURL = (base, baseKey, tenantId = null) => { +export function getStartEndKeyURL(base: any, baseKey: any, tenantId = null) { const tenancy = tenantId ? `${SEPARATOR}${tenantId}` : "" return `${base}?startkey="${baseKey}${tenancy}"&endkey="${baseKey}${tenancy}${UNICODE_MAX}"` } @@ -179,15 +165,21 @@ exports.getStartEndKeyURL = (base, baseKey, tenantId = null) => { * opts.efficient can be provided to make sure this call is always quick in a multi-tenant environment, * but it may not be 100% accurate in full efficiency mode (some tenantless apps may be missed). */ -exports.getAllDbs = async (opts = { efficient: false }) => { +export async function getAllDbs(opts = { efficient: false }) { const efficient = opts && opts.efficient // specifically for testing we use the pouch package for this if (env.isTest()) { - return getCouch().allDbs() + return allDbs() } - let dbs = [] - async function addDbs(url) { - const response = await fetch(checkSlashesInUrl(encodeURI(url))) + let dbs: any[] = [] + let { url, cookie } = getCouchInfo() + async function addDbs(couchUrl: string) { + const response = await fetch(checkSlashesInUrl(encodeURI(couchUrl)), { + method: "GET", + headers: { + Authorization: cookie, + }, + }) if (response.status === 200) { let json = await response.json() dbs = dbs.concat(json) @@ -195,7 +187,7 @@ exports.getAllDbs = async (opts = { efficient: false }) => { throw "Cannot connect to CouchDB instance" } } - let couchUrl = `${exports.getCouchUrl()}/_all_dbs` + let couchUrl = `${url}/_all_dbs` let tenantId = getTenantId() if (!env.MULTI_TENANCY || (!efficient && tenantId === DEFAULT_TENANT_ID)) { // just get all DBs when: @@ -206,13 +198,9 @@ exports.getAllDbs = async (opts = { efficient: false }) => { await addDbs(couchUrl) } else { // get prod apps - await addDbs( - exports.getStartEndKeyURL(couchUrl, DocumentTypes.APP, tenantId) - ) + await addDbs(getStartEndKeyURL(couchUrl, DocumentTypes.APP, tenantId)) // get dev apps - await addDbs( - exports.getStartEndKeyURL(couchUrl, DocumentTypes.APP_DEV, tenantId) - ) + await addDbs(getStartEndKeyURL(couchUrl, DocumentTypes.APP_DEV, tenantId)) // add global db name dbs.push(getGlobalDBName(tenantId)) } @@ -225,14 +213,13 @@ exports.getAllDbs = async (opts = { efficient: false }) => { * * @return {Promise} returns the app information document stored in each app database. */ -exports.getAllApps = async ({ dev, all, idsOnly, efficient } = {}) => { - const CouchDB = getCouch() +export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) { let tenantId = getTenantId() if (!env.MULTI_TENANCY && !tenantId) { tenantId = DEFAULT_TENANT_ID } - let dbs = await exports.getAllDbs({ efficient }) - const appDbNames = dbs.filter(dbName => { + let dbs = await getAllDbs({ efficient }) + const appDbNames = dbs.filter((dbName: any) => { const split = dbName.split(SEPARATOR) // it is an app, check the tenantId if (split[0] === DocumentTypes.APP) { @@ -252,26 +239,28 @@ exports.getAllApps = async ({ dev, all, idsOnly, efficient } = {}) => { if (idsOnly) { return appDbNames } - const appPromises = appDbNames.map(app => + const appPromises = appDbNames.map((app: any) => // skip setup otherwise databases could be re-created - getAppMetadata(app, CouchDB) + getAppMetadata(app) ) if (appPromises.length === 0) { return [] } else { const response = await Promise.allSettled(appPromises) const apps = response - .filter(result => result.status === "fulfilled" && result.value != null) - .map(({ value }) => value) + .filter( + (result: any) => result.status === "fulfilled" && result.value != null + ) + .map(({ value }: any) => value) if (!all) { - return apps.filter(app => { + return apps.filter((app: any) => { if (dev) { return isDevApp(app) } return !isDevApp(app) }) } else { - return apps.map(app => ({ + return apps.map((app: any) => ({ ...app, status: isDevApp(app) ? "development" : "published", })) @@ -282,42 +271,46 @@ exports.getAllApps = async ({ dev, all, idsOnly, efficient } = {}) => { /** * Utility function for getAllApps but filters to production apps only. */ -exports.getProdAppIDs = async () => { - return (await exports.getAllApps({ idsOnly: true })).filter( - id => !exports.isDevAppID(id) +export async function getProdAppIDs() { + return (await getAllApps({ idsOnly: true })).filter( + (id: any) => !isDevAppID(id) ) } /** * Utility function for the inverse of above. */ -exports.getDevAppIDs = async () => { - return (await exports.getAllApps({ idsOnly: true })).filter(id => - exports.isDevAppID(id) +export async function getDevAppIDs() { + return (await getAllApps({ idsOnly: true })).filter((id: any) => + isDevAppID(id) ) } -exports.dbExists = async dbName => { - const CouchDB = getCouch() +export async function dbExists(dbName: any) { let exists = false - try { - const db = CouchDB(dbName, { skip_setup: true }) - // check if database exists - const info = await db.info() - if (info && !info.error) { - exists = true - } - } catch (err) { - exists = false - } - return exists + return doWithDB( + dbName, + async (db: any) => { + try { + // check if database exists + const info = await db.info() + if (info && !info.error) { + exists = true + } + } catch (err) { + exists = false + } + return exists + }, + { skip_setup: true } + ) } /** * Generates a new configuration ID. * @returns {string} The new configuration ID which the config doc can be stored under. */ -const generateConfigID = ({ type, workspace, user }) => { +export const generateConfigID = ({ type, workspace, user }: any) => { const scope = [type, workspace, user].filter(Boolean).join(SEPARATOR) return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}` @@ -326,7 +319,10 @@ const generateConfigID = ({ type, workspace, user }) => { /** * Gets parameters for retrieving configurations. */ -const getConfigParams = ({ type, workspace, user }, otherProps = {}) => { +export const getConfigParams = ( + { type, workspace, user }: any, + otherProps = {} +) => { const scope = [type, workspace, user].filter(Boolean).join(SEPARATOR) return { @@ -340,7 +336,7 @@ const getConfigParams = ({ type, workspace, user }, otherProps = {}) => { * Generates a new dev info document ID - this is scoped to a user. * @returns {string} The new dev info ID which info for dev (like api key) can be stored under. */ -const generateDevInfoID = userId => { +export const generateDevInfoID = (userId: any) => { return `${DocumentTypes.DEV_INFO}${SEPARATOR}${userId}` } @@ -350,7 +346,10 @@ const generateDevInfoID = userId => { * @param {Object} scopes - the type, workspace and userID scopes of the configuration. * @returns The most granular configuration document based on the scope. */ -const getScopedFullConfig = async function (db, { type, user, workspace }) { +export const getScopedFullConfig = async function ( + db: any, + { type, user, workspace }: any +) { const response = await db.allDocs( getConfigParams( { type, user, workspace }, @@ -360,7 +359,7 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) { ) ) - function determineScore(row) { + function determineScore(row: any) { const config = row.doc // Config is specific to a user and a workspace @@ -381,21 +380,26 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) { // Find the config with the most granular scope based on context let scopedConfig = response.rows.sort( - (a, b) => determineScore(a) - determineScore(b) + (a: any, b: any) => determineScore(a) - determineScore(b) )[0] // custom logic for settings doc - // always provide the platform URL if (type === Configs.SETTINGS) { if (scopedConfig && scopedConfig.doc) { - scopedConfig.doc.config.platformUrl = await getPlatformUrl( - scopedConfig.doc.config - ) + // overrides affected by environment variables + scopedConfig.doc.config.platformUrl = await getPlatformUrl({ + tenantAware: true, + }) + scopedConfig.doc.config.analyticsEnabled = + await events.analytics.enabled() } else { + // defaults scopedConfig = { doc: { + _id: generateConfigID({ type, user, workspace }), config: { - platformUrl: await getPlatformUrl(), + platformUrl: await getPlatformUrl({ tenantAware: true }), + analyticsEnabled: await events.analytics.enabled(), }, }, } @@ -405,58 +409,57 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) { return scopedConfig && scopedConfig.doc } -const getPlatformUrl = async settings => { +export const getPlatformUrl = async (opts = { tenantAware: true }) => { let platformUrl = env.PLATFORM_URL || "http://localhost:10000" - if (!env.SELF_HOSTED && env.MULTI_TENANCY) { + if (!env.SELF_HOSTED && env.MULTI_TENANCY && opts.tenantAware) { // cloud and multi tenant - add the tenant to the default platform url const tenantId = getTenantId() if (!platformUrl.includes("localhost:")) { platformUrl = platformUrl.replace("://", `://${tenantId}.`) } - } else { + } else if (env.SELF_HOSTED) { + const db = getGlobalDB() + // get the doc directly instead of with getScopedConfig to prevent loop + let settings + try { + settings = await db.get(generateConfigID({ type: Configs.SETTINGS })) + } catch (e: any) { + if (e.status !== 404) { + throw e + } + } + // self hosted - check for platform url override - if (settings && settings.platformUrl) { - platformUrl = settings.platformUrl + if (settings && settings.config && settings.config.platformUrl) { + platformUrl = settings.config.platformUrl } } return platformUrl } -async function getScopedConfig(db, params) { - const configDoc = await getScopedFullConfig(db, params) - return configDoc && configDoc.config ? configDoc.config : configDoc -} - -function generateNewUsageQuotaDoc() { +export function pagination( + data: any[], + pageSize: number, + { paginate, property } = { paginate: true, property: "_id" } +) { + if (!paginate) { + return { data, hasNextPage: false } + } + const hasNextPage = data.length > pageSize + let nextPage = undefined + if (hasNextPage) { + nextPage = property ? data[pageSize]?.[property] : data[pageSize]?._id + } return { - _id: StaticDatabases.GLOBAL.docs.usageQuota, - quotaReset: Date.now() + 2592000000, - usageQuota: { - automationRuns: 0, - rows: 0, - storage: 0, - apps: 0, - users: 0, - views: 0, - emails: 0, - }, - usageLimits: { - automationRuns: 1000, - rows: 4000, - apps: 4, - storage: 1000, - users: 10, - emails: 50, - }, + data: data.slice(0, pageSize), + hasNextPage, + nextPage, } } -exports.Replication = Replication -exports.getScopedConfig = getScopedConfig -exports.generateConfigID = generateConfigID -exports.getConfigParams = getConfigParams -exports.getScopedFullConfig = getScopedFullConfig -exports.generateNewUsageQuotaDoc = generateNewUsageQuotaDoc -exports.generateDevInfoID = generateDevInfoID +export async function getScopedConfig(db: any, params: any) { + const configDoc = await getScopedFullConfig(db, params) + return configDoc && configDoc.config ? configDoc.config : configDoc +} diff --git a/packages/backend-core/src/db/views.js b/packages/backend-core/src/db/views.js index e5be8e6b40..1e8dd7ee77 100644 --- a/packages/backend-core/src/db/views.js +++ b/packages/backend-core/src/db/views.js @@ -1,20 +1,42 @@ -const { DocumentTypes, ViewNames } = require("./utils") +const { + DocumentTypes, + ViewNames, + DeprecatedViews, + SEPARATOR, +} = require("./utils") const { getGlobalDB } = require("../tenancy") +const DESIGN_DB = "_design/database" + function DesignDoc() { return { - _id: "_design/database", + _id: DESIGN_DB, // view collation information, read before writing any complex views: // https://docs.couchdb.org/en/master/ddocs/views/collation.html#collation-specification views: {}, } } -exports.createUserEmailView = async () => { +async function removeDeprecated(db, viewName) { + if (!DeprecatedViews[viewName]) { + return + } + try { + const designDoc = await db.get(DESIGN_DB) + for (let deprecatedNames of DeprecatedViews[viewName]) { + delete designDoc.views[deprecatedNames] + } + await db.put(designDoc) + } catch (err) { + // doesn't exist, ignore + } +} + +exports.createNewUserEmailView = async () => { const db = getGlobalDB() let designDoc try { - designDoc = await db.get("_design/database") + designDoc = await db.get(DESIGN_DB) } catch (err) { // no design doc, make one designDoc = DesignDoc() @@ -22,7 +44,7 @@ exports.createUserEmailView = async () => { const view = { // if using variables in a map function need to inject them before use map: `function(doc) { - if (doc._id.startsWith("${DocumentTypes.USER}")) { + if (doc._id.startsWith("${DocumentTypes.USER}${SEPARATOR}")) { emit(doc.email.toLowerCase(), doc._id) } }`, @@ -56,10 +78,34 @@ exports.createApiKeyView = async () => { await db.put(designDoc) } +exports.createUserBuildersView = async () => { + const db = getGlobalDB() + let designDoc + try { + designDoc = await db.get("_design/database") + } catch (err) { + // no design doc, make one + designDoc = DesignDoc() + } + const view = { + map: `function(doc) { + if (doc.builder && doc.builder.global === true) { + emit(doc._id, doc._id) + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewNames.USER_BY_BUILDERS]: view, + } + await db.put(designDoc) +} + exports.queryGlobalView = async (viewName, params, db = null) => { const CreateFuncByName = { - [ViewNames.USER_BY_EMAIL]: exports.createUserEmailView, + [ViewNames.USER_BY_EMAIL]: exports.createNewUserEmailView, [ViewNames.BY_API_KEY]: exports.createApiKeyView, + [ViewNames.USER_BY_BUILDERS]: exports.createUserBuildersView, } // can pass DB in if working with something specific if (!db) { @@ -74,6 +120,7 @@ exports.queryGlobalView = async (viewName, params, db = null) => { } catch (err) { if (err != null && err.name === "not_found") { const createFunc = CreateFuncByName[viewName] + await removeDeprecated(db, viewName) await createFunc() return exports.queryGlobalView(viewName, params) } else { diff --git a/packages/backend-core/src/environment.js b/packages/backend-core/src/environment.js deleted file mode 100644 index d112ad8599..0000000000 --- a/packages/backend-core/src/environment.js +++ /dev/null @@ -1,36 +0,0 @@ -function isTest() { - return ( - process.env.NODE_ENV === "jest" || - process.env.NODE_ENV === "cypress" || - process.env.JEST_WORKER_ID != null - ) -} - -module.exports = { - JWT_SECRET: process.env.JWT_SECRET, - COUCH_DB_URL: process.env.COUCH_DB_URL, - COUCH_DB_USERNAME: process.env.COUCH_DB_USER, - COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, - GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, - GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, - SALT_ROUNDS: process.env.SALT_ROUNDS, - REDIS_URL: process.env.REDIS_URL, - REDIS_PASSWORD: process.env.REDIS_PASSWORD, - MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, - MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, - AWS_REGION: process.env.AWS_REGION, - MINIO_URL: process.env.MINIO_URL, - INTERNAL_API_KEY: process.env.INTERNAL_API_KEY, - MULTI_TENANCY: process.env.MULTI_TENANCY, - ACCOUNT_PORTAL_URL: process.env.ACCOUNT_PORTAL_URL, - ACCOUNT_PORTAL_API_KEY: process.env.ACCOUNT_PORTAL_API_KEY, - DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL, - SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED), - COOKIE_DOMAIN: process.env.COOKIE_DOMAIN, - PLATFORM_URL: process.env.PLATFORM_URL, - isTest, - _set(key, value) { - process.env[key] = value - module.exports[key] = value - }, -} diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts new file mode 100644 index 0000000000..37804b31a6 --- /dev/null +++ b/packages/backend-core/src/environment.ts @@ -0,0 +1,75 @@ +function isTest() { + return ( + process.env.NODE_ENV === "jest" || + process.env.NODE_ENV === "cypress" || + process.env.JEST_WORKER_ID != null + ) +} + +function isDev() { + return process.env.NODE_ENV !== "production" +} + +let LOADED = false +if (!LOADED && isDev() && !isTest()) { + require("dotenv").config() + LOADED = true +} + +const env = { + isTest, + isDev, + JWT_SECRET: process.env.JWT_SECRET, + COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005", + COUCH_DB_USERNAME: process.env.COUCH_DB_USER, + COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, + GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, + GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, + SALT_ROUNDS: process.env.SALT_ROUNDS, + REDIS_URL: process.env.REDIS_URL, + REDIS_PASSWORD: process.env.REDIS_PASSWORD, + MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, + MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, + AWS_REGION: process.env.AWS_REGION, + MINIO_URL: process.env.MINIO_URL, + INTERNAL_API_KEY: process.env.INTERNAL_API_KEY, + MULTI_TENANCY: process.env.MULTI_TENANCY, + ACCOUNT_PORTAL_URL: + process.env.ACCOUNT_PORTAL_URL || "https://account.budibase.app", + ACCOUNT_PORTAL_API_KEY: process.env.ACCOUNT_PORTAL_API_KEY, + DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL, + SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED || ""), + COOKIE_DOMAIN: process.env.COOKIE_DOMAIN, + PLATFORM_URL: process.env.PLATFORM_URL || "", + POSTHOG_TOKEN: process.env.POSTHOG_TOKEN, + ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS, + TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS, + BACKUPS_BUCKET_NAME: process.env.BACKUPS_BUCKET_NAME || "backups", + APPS_BUCKET_NAME: process.env.APPS_BUCKET_NAME || "prod-budi-app-assets", + TEMPLATES_BUCKET_NAME: process.env.TEMPLATES_BUCKET_NAME || "templates", + GLOBAL_BUCKET_NAME: process.env.GLOBAL_BUCKET_NAME || "global", + GLOBAL_CLOUD_BUCKET_NAME: + process.env.GLOBAL_CLOUD_BUCKET_NAME || "prod-budi-tenant-uploads", + USE_COUCH: process.env.USE_COUCH || true, + DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE, + DEFAULT_LICENSE: process.env.DEFAULT_LICENSE, + SERVICE: process.env.SERVICE || "budibase", + MEMORY_LEAK_CHECK: process.env.MEMORY_LEAK_CHECK || false, + DEPLOYMENT_ENVIRONMENT: + process.env.DEPLOYMENT_ENVIRONMENT || "docker-compose", + _set(key: any, value: any) { + process.env[key] = value + module.exports[key] = value + }, +} + +// clean up any environment variable edge cases +for (let [key, value] of Object.entries(env)) { + // handle the edge case of "0" to disable an environment variable + if (value === "0") { + // @ts-ignore + env[key] = 0 + } +} + +export = env diff --git a/packages/backend-core/src/errors/base.js b/packages/backend-core/src/errors/base.js new file mode 100644 index 0000000000..7cb0c0fc23 --- /dev/null +++ b/packages/backend-core/src/errors/base.js @@ -0,0 +1,11 @@ +class BudibaseError extends Error { + constructor(message, code, type) { + super(message) + this.code = code + this.type = type + } +} + +module.exports = { + BudibaseError, +} diff --git a/packages/backend-core/src/errors/generic.js b/packages/backend-core/src/errors/generic.js new file mode 100644 index 0000000000..5c7661f035 --- /dev/null +++ b/packages/backend-core/src/errors/generic.js @@ -0,0 +1,11 @@ +const { BudibaseError } = require("./base") + +class GenericError extends BudibaseError { + constructor(message, code, type) { + super(message, code, type ? type : "generic") + } +} + +module.exports = { + GenericError, +} diff --git a/packages/backend-core/src/errors/http.js b/packages/backend-core/src/errors/http.js new file mode 100644 index 0000000000..8e7cab4638 --- /dev/null +++ b/packages/backend-core/src/errors/http.js @@ -0,0 +1,12 @@ +const { GenericError } = require("./generic") + +class HTTPError extends GenericError { + constructor(message, httpStatus, code = "http", type = "generic") { + super(message, code, type) + this.status = httpStatus + } +} + +module.exports = { + HTTPError, +} diff --git a/packages/backend-core/src/errors/index.js b/packages/backend-core/src/errors/index.js new file mode 100644 index 0000000000..58b4eea8c5 --- /dev/null +++ b/packages/backend-core/src/errors/index.js @@ -0,0 +1,43 @@ +const http = require("./http") +const licensing = require("./licensing") + +const codes = { + ...licensing.codes, +} + +const types = [licensing.type] + +const context = { + ...licensing.context, +} + +const getPublicError = err => { + let error + if (err.code || err.type) { + // add generic error information + error = { + code: err.code, + type: err.type, + } + + if (err.code && context[err.code]) { + error = { + ...error, + // get any additional context from this error + ...context[err.code](err), + } + } + } + + return error +} + +module.exports = { + codes, + types, + errors: { + UsageLimitError: licensing.UsageLimitError, + HTTPError: http.HTTPError, + }, + getPublicError, +} diff --git a/packages/backend-core/src/errors/licensing.js b/packages/backend-core/src/errors/licensing.js new file mode 100644 index 0000000000..0d8ce08146 --- /dev/null +++ b/packages/backend-core/src/errors/licensing.js @@ -0,0 +1,29 @@ +const { HTTPError } = require("./http") + +const type = "license_error" + +const codes = { + USAGE_LIMIT_EXCEEDED: "usage_limit_exceeded", +} + +const context = { + [codes.USAGE_LIMIT_EXCEEDED]: err => { + return { + limitName: err.limitName, + } + }, +} + +class UsageLimitError extends HTTPError { + constructor(message, limitName) { + super(message, 400, codes.USAGE_LIMIT_EXCEEDED, type) + this.limitName = limitName + } +} + +module.exports = { + type, + codes, + context, + UsageLimitError, +} diff --git a/packages/backend-core/src/events/analytics.ts b/packages/backend-core/src/events/analytics.ts new file mode 100644 index 0000000000..802b6d6314 --- /dev/null +++ b/packages/backend-core/src/events/analytics.ts @@ -0,0 +1,57 @@ +import env from "../environment" +import tenancy from "../tenancy" +import * as dbUtils from "../db/utils" +import { Configs } from "../constants" +import { withCache, TTL, CacheKeys } from "../cache/generic" + +export const enabled = async () => { + // cloud - always use the environment variable + if (!env.SELF_HOSTED) { + return !!env.ENABLE_ANALYTICS + } + + // self host - prefer the settings doc + // use cache as events have high throughput + const enabledInDB = await withCache( + CacheKeys.ANALYTICS_ENABLED, + TTL.ONE_DAY, + async () => { + const settings = await getSettingsDoc() + + // need to do explicit checks in case the field is not set + if (settings?.config?.analyticsEnabled === false) { + return false + } else if (settings?.config?.analyticsEnabled === true) { + return true + } + } + ) + + if (enabledInDB !== undefined) { + return enabledInDB + } + + // fallback to the environment variable + // explicitly check for 0 or false here, undefined or otherwise is treated as true + const envEnabled: any = env.ENABLE_ANALYTICS + if (envEnabled === 0 || envEnabled === false) { + return false + } else { + return true + } +} + +const getSettingsDoc = async () => { + const db = tenancy.getGlobalDB() + let settings + try { + settings = await db.get( + dbUtils.generateConfigID({ type: Configs.SETTINGS }) + ) + } catch (e: any) { + if (e.status !== 404) { + throw e + } + } + return settings +} diff --git a/packages/backend-core/src/events/backfill.ts b/packages/backend-core/src/events/backfill.ts new file mode 100644 index 0000000000..e4577c5ab4 --- /dev/null +++ b/packages/backend-core/src/events/backfill.ts @@ -0,0 +1,183 @@ +import { + Event, + BackfillMetadata, + CachedEvent, + SSOCreatedEvent, + AutomationCreatedEvent, + AutomationStepCreatedEvent, + DatasourceCreatedEvent, + LayoutCreatedEvent, + QueryCreatedEvent, + RoleCreatedEvent, + ScreenCreatedEvent, + TableCreatedEvent, + ViewCreatedEvent, + ViewCalculationCreatedEvent, + ViewFilterCreatedEvent, + AppPublishedEvent, + UserCreatedEvent, + RoleAssignedEvent, + UserPermissionAssignedEvent, + AppCreatedEvent, +} from "@budibase/types" +import * as context from "../context" +import { CacheKeys } from "../cache/generic" +import * as cache from "../cache/generic" + +// LIFECYCLE + +export const start = async (events: Event[]) => { + const metadata: BackfillMetadata = { + eventWhitelist: events, + } + return saveBackfillMetadata(metadata) +} + +export const recordEvent = async (event: Event, properties: any) => { + const eventKey = getEventKey(event, properties) + // don't use a ttl - cleaned up by migration + // don't use tenancy - already in the key + await cache.store(eventKey, properties, undefined, { useTenancy: false }) +} + +export const end = async () => { + await deleteBackfillMetadata() + await clearEvents() +} + +// CRUD + +const getBackfillMetadata = async (): Promise => { + return cache.get(CacheKeys.BACKFILL_METADATA) +} + +const saveBackfillMetadata = async ( + backfill: BackfillMetadata +): Promise => { + // no TTL - deleted by backfill + return cache.store(CacheKeys.BACKFILL_METADATA, backfill) +} + +const deleteBackfillMetadata = async (): Promise => { + await cache.delete(CacheKeys.BACKFILL_METADATA) +} + +const clearEvents = async () => { + // wildcard + const pattern = getEventKey() + const keys = await cache.keys(pattern) + + for (const key of keys) { + // delete each key + // don't use tenancy, already in the key + await cache.delete(key, { useTenancy: false }) + } +} + +// HELPERS + +export const isBackfillingEvent = async (event: Event) => { + const backfill = await getBackfillMetadata() + const events = backfill?.eventWhitelist + if (events && events.includes(event)) { + return true + } else { + return false + } +} + +export const isAlreadySent = async (event: Event, properties: any) => { + const eventKey = getEventKey(event, properties) + const cachedEvent: CachedEvent = await cache.get(eventKey, { + useTenancy: false, + }) + return !!cachedEvent +} + +const CUSTOM_PROPERTY_SUFFIX: any = { + // APP EVENTS + [Event.AUTOMATION_CREATED]: (properties: AutomationCreatedEvent) => { + return properties.automationId + }, + [Event.AUTOMATION_STEP_CREATED]: (properties: AutomationStepCreatedEvent) => { + return properties.stepId + }, + [Event.DATASOURCE_CREATED]: (properties: DatasourceCreatedEvent) => { + return properties.datasourceId + }, + [Event.LAYOUT_CREATED]: (properties: LayoutCreatedEvent) => { + return properties.layoutId + }, + [Event.QUERY_CREATED]: (properties: QueryCreatedEvent) => { + return properties.queryId + }, + [Event.ROLE_CREATED]: (properties: RoleCreatedEvent) => { + return properties.roleId + }, + [Event.SCREEN_CREATED]: (properties: ScreenCreatedEvent) => { + return properties.screenId + }, + [Event.TABLE_CREATED]: (properties: TableCreatedEvent) => { + return properties.tableId + }, + [Event.VIEW_CREATED]: (properties: ViewCreatedEvent) => { + return properties.tableId // best uniqueness + }, + [Event.VIEW_CALCULATION_CREATED]: ( + properties: ViewCalculationCreatedEvent + ) => { + return properties.tableId // best uniqueness + }, + [Event.VIEW_FILTER_CREATED]: (properties: ViewFilterCreatedEvent) => { + return properties.tableId // best uniqueness + }, + [Event.APP_CREATED]: (properties: AppCreatedEvent) => { + return properties.appId // best uniqueness + }, + [Event.APP_PUBLISHED]: (properties: AppPublishedEvent) => { + return properties.appId // best uniqueness + }, + // GLOBAL EVENTS + [Event.AUTH_SSO_CREATED]: (properties: SSOCreatedEvent) => { + return properties.type + }, + [Event.AUTH_SSO_ACTIVATED]: (properties: SSOCreatedEvent) => { + return properties.type + }, + [Event.USER_CREATED]: (properties: UserCreatedEvent) => { + return properties.userId + }, + [Event.USER_PERMISSION_ADMIN_ASSIGNED]: ( + properties: UserPermissionAssignedEvent + ) => { + return properties.userId + }, + [Event.USER_PERMISSION_BUILDER_ASSIGNED]: ( + properties: UserPermissionAssignedEvent + ) => { + return properties.userId + }, + [Event.ROLE_ASSIGNED]: (properties: RoleAssignedEvent) => { + return `${properties.roleId}-${properties.userId}` + }, +} + +const getEventKey = (event?: Event, properties?: any) => { + let eventKey: string + + const tenantId = context.getTenantId() + if (event) { + eventKey = `${CacheKeys.EVENTS}:${tenantId}:${event}` + + // use some properties to make the key more unique + const custom = CUSTOM_PROPERTY_SUFFIX[event] + const suffix = custom ? custom(properties) : undefined + if (suffix) { + eventKey = `${eventKey}:${suffix}` + } + } else { + eventKey = `${CacheKeys.EVENTS}:${tenantId}:*` + } + + return eventKey +} diff --git a/packages/backend-core/src/events/events.ts b/packages/backend-core/src/events/events.ts new file mode 100644 index 0000000000..cda90d12c9 --- /dev/null +++ b/packages/backend-core/src/events/events.ts @@ -0,0 +1,31 @@ +import { Event } from "@budibase/types" +import { processors } from "./processors" +import * as identification from "./identification" +import * as backfill from "./backfill" + +export const publishEvent = async ( + event: Event, + properties: any, + timestamp?: string | number +) => { + // in future this should use async events via a distributed queue. + const identity = await identification.getCurrentIdentity() + + const backfilling = await backfill.isBackfillingEvent(event) + // no backfill - send the event and exit + if (!backfilling) { + await processors.processEvent(event, identity, properties, timestamp) + return + } + + // backfill active - check if the event has been sent already + const alreadySent = await backfill.isAlreadySent(event, properties) + if (alreadySent) { + // do nothing + return + } else { + // send and record the event + await processors.processEvent(event, identity, properties, timestamp) + await backfill.recordEvent(event, properties) + } +} diff --git a/packages/backend-core/src/events/identification.ts b/packages/backend-core/src/events/identification.ts new file mode 100644 index 0000000000..a29a6821cd --- /dev/null +++ b/packages/backend-core/src/events/identification.ts @@ -0,0 +1,302 @@ +import * as context from "../context" +import * as identityCtx from "../context/identity" +import env from "../environment" +import { + Hosting, + User, + Identity, + IdentityType, + Account, + isCloudAccount, + isSSOAccount, + TenantGroup, + SettingsConfig, + CloudAccount, + UserIdentity, + InstallationGroup, + UserContext, + Group, +} from "@budibase/types" +import { processors } from "./processors" +import * as dbUtils from "../db/utils" +import { Configs } from "../constants" +import * as hashing from "../hashing" +import * as installation from "../installation" +import { withCache, TTL, CacheKeys } from "../cache/generic" + +const pkg = require("../../package.json") + +/** + * An identity can be: + * - account user (Self host) + * - budibase user + * - tenant + * - installation + */ +export const getCurrentIdentity = async (): Promise => { + let identityContext = identityCtx.getIdentity() + const environment = getDeploymentEnvironment() + + let identityType + + if (!identityContext) { + identityType = IdentityType.TENANT + } else { + identityType = identityContext.type + } + + if (identityType === IdentityType.INSTALLATION) { + const installationId = await getInstallationId() + const hosting = getHostingFromEnv() + return { + id: formatDistinctId(installationId, identityType), + hosting, + type: identityType, + installationId, + environment, + } + } else if (identityType === IdentityType.TENANT) { + const installationId = await getInstallationId() + const tenantId = await getEventTenantId(context.getTenantId()) + const hosting = getHostingFromEnv() + + return { + id: formatDistinctId(tenantId, identityType), + type: identityType, + hosting, + installationId, + tenantId, + environment, + } + } else if (identityType === IdentityType.USER) { + const userContext = identityContext as UserContext + const tenantId = await getEventTenantId(context.getTenantId()) + const installationId = await getInstallationId() + + const account = userContext.account + let hosting + if (account) { + hosting = account.hosting + } else { + hosting = getHostingFromEnv() + } + + return { + id: userContext._id, + type: identityType, + hosting, + installationId, + tenantId, + environment, + } + } else { + throw new Error("Unknown identity type") + } +} + +export const identifyInstallationGroup = async ( + installId: string, + timestamp?: string | number +): Promise => { + const id = installId + const type = IdentityType.INSTALLATION + const hosting = getHostingFromEnv() + const version = pkg.version + const environment = getDeploymentEnvironment() + + const group: InstallationGroup = { + id, + type, + hosting, + version, + environment, + } + + await identifyGroup(group, timestamp) + // need to create a normal identity for the group to be able to query it globally + // match the posthog syntax to link this identity to the empty auto generated one + await identify({ ...group, id: `$${type}_${id}` }, timestamp) +} + +export const identifyTenantGroup = async ( + tenantId: string, + account: Account | undefined, + timestamp?: string | number +): Promise => { + const id = await getEventTenantId(tenantId) + const type = IdentityType.TENANT + const installationId = await getInstallationId() + const environment = getDeploymentEnvironment() + + let hosting: Hosting + let profession: string | undefined + let companySize: string | undefined + + if (account) { + profession = account.profession + companySize = account.size + hosting = account.hosting + } else { + hosting = getHostingFromEnv() + } + + const group: TenantGroup = { + id, + type, + hosting, + environment, + installationId, + profession, + companySize, + } + + await identifyGroup(group, timestamp) + // need to create a normal identity for the group to be able to query it globally + // match the posthog syntax to link this identity to the auto generated one + await identify({ ...group, id: `$${type}_${id}` }, timestamp) +} + +export const identifyUser = async ( + user: User, + account: CloudAccount | undefined, + timestamp?: string | number +) => { + const id = user._id as string + const tenantId = await getEventTenantId(user.tenantId) + const type = IdentityType.USER + let builder = user.builder?.global || false + let admin = user.admin?.global || false + let providerType = user.providerType + const accountHolder = account?.budibaseUserId === user._id || false + const verified = + account && account?.budibaseUserId === user._id ? account.verified : false + const installationId = await getInstallationId() + const hosting = account ? account.hosting : getHostingFromEnv() + const environment = getDeploymentEnvironment() + + const identity: UserIdentity = { + id, + type, + hosting, + installationId, + tenantId, + verified, + accountHolder, + providerType, + builder, + admin, + environment, + } + + await identify(identity, timestamp) +} + +export const identifyAccount = async (account: Account) => { + let id = account.accountId + const tenantId = account.tenantId + let type = IdentityType.USER + let providerType = isSSOAccount(account) ? account.providerType : undefined + const verified = account.verified + const accountHolder = true + const hosting = account.hosting + const installationId = await getInstallationId() + const environment = getDeploymentEnvironment() + + if (isCloudAccount(account)) { + if (account.budibaseUserId) { + // use the budibase user as the id if set + id = account.budibaseUserId + } + } + + const identity: UserIdentity = { + id, + type, + hosting, + installationId, + tenantId, + providerType, + verified, + accountHolder, + environment, + } + + await identify(identity) +} + +export const identify = async ( + identity: Identity, + timestamp?: string | number +) => { + await processors.identify(identity, timestamp) +} + +export const identifyGroup = async ( + group: Group, + timestamp?: string | number +) => { + await processors.identifyGroup(group, timestamp) +} + +const getDeploymentEnvironment = () => { + if (env.isDev()) { + return "development" + } else { + return env.DEPLOYMENT_ENVIRONMENT + } +} + +const getHostingFromEnv = () => { + return env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD +} + +export const getInstallationId = async () => { + if (isAccountPortal()) { + return "account-portal" + } + const install = await installation.getInstall() + return install.installId +} + +const getEventTenantId = async (tenantId: string): Promise => { + if (env.SELF_HOSTED) { + return getUniqueTenantId(tenantId) + } else { + // tenant id's in the cloud are already unique + return tenantId + } +} + +const getUniqueTenantId = async (tenantId: string): Promise => { + // make sure this tenantId always matches the tenantId in context + return context.doInTenant(tenantId, () => { + return withCache(CacheKeys.UNIQUE_TENANT_ID, TTL.ONE_DAY, async () => { + const db = context.getGlobalDB() + const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, { + type: Configs.SETTINGS, + }) + + let uniqueTenantId: string + if (config.config.uniqueTenantId) { + return config.config.uniqueTenantId + } else { + uniqueTenantId = `${hashing.newid()}_${tenantId}` + config.config.uniqueTenantId = uniqueTenantId + await db.put(config) + return uniqueTenantId + } + }) + }) +} + +const isAccountPortal = () => { + return env.SERVICE === "account-portal" +} + +const formatDistinctId = (id: string, type: IdentityType) => { + if (type === IdentityType.INSTALLATION || type === IdentityType.TENANT) { + return `$${type}_${id}` + } else { + return id + } +} diff --git a/packages/backend-core/src/events/index.ts b/packages/backend-core/src/events/index.ts new file mode 100644 index 0000000000..814399655d --- /dev/null +++ b/packages/backend-core/src/events/index.ts @@ -0,0 +1,11 @@ +export * from "./publishers" +export * as processors from "./processors" +export * as analytics from "./analytics" +export * as identification from "./identification" +export * as backfillCache from "./backfill" + +import { processors } from "./processors" + +export const shutdown = () => { + processors.shutdown() +} diff --git a/packages/backend-core/src/events/processors/AnalyticsProcessor.ts b/packages/backend-core/src/events/processors/AnalyticsProcessor.ts new file mode 100644 index 0000000000..2ee7a02afa --- /dev/null +++ b/packages/backend-core/src/events/processors/AnalyticsProcessor.ts @@ -0,0 +1,64 @@ +import { Event, Identity, Group, IdentityType } from "@budibase/types" +import { EventProcessor } from "./types" +import env from "../../environment" +import * as analytics from "../analytics" +import PosthogProcessor from "./PosthogProcessor" + +/** + * Events that are always captured. + */ +const EVENT_WHITELIST = [ + Event.INSTALLATION_VERSION_UPGRADED, + Event.INSTALLATION_VERSION_DOWNGRADED, +] +const IDENTITY_WHITELIST = [IdentityType.INSTALLATION, IdentityType.TENANT] + +export default class AnalyticsProcessor implements EventProcessor { + posthog: PosthogProcessor | undefined + + constructor() { + if (env.POSTHOG_TOKEN && !env.isTest()) { + this.posthog = new PosthogProcessor(env.POSTHOG_TOKEN) + } + } + + async processEvent( + event: Event, + identity: Identity, + properties: any, + timestamp?: string | number + ): Promise { + if (!EVENT_WHITELIST.includes(event) && !(await analytics.enabled())) { + return + } + if (this.posthog) { + this.posthog.processEvent(event, identity, properties, timestamp) + } + } + + async identify(identity: Identity, timestamp?: string | number) { + // Group indentifications (tenant and installation) always on + if ( + !IDENTITY_WHITELIST.includes(identity.type) && + !(await analytics.enabled()) + ) { + return + } + if (this.posthog) { + this.posthog.identify(identity, timestamp) + } + } + + async identifyGroup(group: Group, timestamp?: string | number) { + // Group indentifications (tenant and installation) always on + if (this.posthog) { + this.posthog.identifyGroup(group, timestamp) + } + } + + shutdown() { + if (this.posthog) { + this.posthog.shutdown() + } + } +} diff --git a/packages/backend-core/src/events/processors/LoggingProcessor.ts b/packages/backend-core/src/events/processors/LoggingProcessor.ts new file mode 100644 index 0000000000..a517fba09c --- /dev/null +++ b/packages/backend-core/src/events/processors/LoggingProcessor.ts @@ -0,0 +1,54 @@ +import { Event, Identity, Group } from "@budibase/types" +import { EventProcessor } from "./types" +import env from "../../environment" + +const getTimestampString = (timestamp?: string | number) => { + let timestampString = "" + if (timestamp) { + timestampString = `[timestamp=${new Date(timestamp).toISOString()}]` + } + return timestampString +} + +const skipLogging = env.SELF_HOSTED && !env.isDev() + +export default class LoggingProcessor implements EventProcessor { + async processEvent( + event: Event, + identity: Identity, + properties: any, + timestamp?: string + ): Promise { + if (skipLogging) { + return + } + let timestampString = getTimestampString(timestamp) + console.log( + `[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${timestampString} ${event} ` + ) + } + + async identify(identity: Identity, timestamp?: string | number) { + if (skipLogging) { + return + } + let timestampString = getTimestampString(timestamp) + console.log( + `[audit] [${JSON.stringify(identity)}] ${timestampString} identified` + ) + } + + async identifyGroup(group: Group, timestamp?: string | number) { + if (skipLogging) { + return + } + let timestampString = getTimestampString(timestamp) + console.log( + `[audit] [${JSON.stringify(group)}] ${timestampString} group identified` + ) + } + + shutdown(): void { + // no-op + } +} diff --git a/packages/backend-core/src/events/processors/PosthogProcessor.ts b/packages/backend-core/src/events/processors/PosthogProcessor.ts new file mode 100644 index 0000000000..eb12db1dc4 --- /dev/null +++ b/packages/backend-core/src/events/processors/PosthogProcessor.ts @@ -0,0 +1,81 @@ +import PostHog from "posthog-node" +import { Event, Identity, Group, BaseEvent } from "@budibase/types" +import { EventProcessor } from "./types" +import env from "../../environment" +import * as context from "../../context" +const pkg = require("../../../package.json") + +export default class PosthogProcessor implements EventProcessor { + posthog: PostHog + + constructor(token: string | undefined) { + if (!token) { + throw new Error("Posthog token is not defined") + } + this.posthog = new PostHog(token) + } + + async processEvent( + event: Event, + identity: Identity, + properties: BaseEvent, + timestamp?: string | number + ): Promise { + properties.version = pkg.version + properties.service = env.SERVICE + properties.environment = identity.environment + properties.hosting = identity.hosting + + const appId = context.getAppId() + if (appId) { + properties.appId = appId + } + + const payload: any = { distinctId: identity.id, event, properties } + + if (timestamp) { + payload.timestamp = new Date(timestamp) + } + + // add groups to the event + if (identity.installationId || identity.tenantId) { + payload.groups = {} + if (identity.installationId) { + payload.groups.installation = identity.installationId + payload.properties.installationId = identity.installationId + } + if (identity.tenantId) { + payload.groups.tenant = identity.tenantId + payload.properties.tenantId = identity.tenantId + } + } + + this.posthog.capture(payload) + } + + async identify(identity: Identity, timestamp?: string | number) { + const payload: any = { distinctId: identity.id, properties: identity } + if (timestamp) { + payload.timestamp = new Date(timestamp) + } + this.posthog.identify(payload) + } + + async identifyGroup(group: Group, timestamp?: string | number) { + const payload: any = { + distinctId: group.id, + groupType: group.type, + groupKey: group.id, + properties: group, + } + + if (timestamp) { + payload.timestamp = new Date(timestamp) + } + this.posthog.groupIdentify(payload) + } + + shutdown() { + this.posthog.shutdown() + } +} diff --git a/packages/backend-core/src/events/processors/Processors.ts b/packages/backend-core/src/events/processors/Processors.ts new file mode 100644 index 0000000000..4baedd909f --- /dev/null +++ b/packages/backend-core/src/events/processors/Processors.ts @@ -0,0 +1,46 @@ +import { Event, Identity, Group } from "@budibase/types" +import { EventProcessor } from "./types" + +export default class Processor implements EventProcessor { + initialised: boolean = false + processors: EventProcessor[] = [] + + constructor(processors: EventProcessor[]) { + this.processors = processors + } + + async processEvent( + event: Event, + identity: Identity, + properties: any, + timestamp?: string | number + ): Promise { + for (const eventProcessor of this.processors) { + await eventProcessor.processEvent(event, identity, properties, timestamp) + } + } + + async identify( + identity: Identity, + timestamp?: string | number + ): Promise { + for (const eventProcessor of this.processors) { + await eventProcessor.identify(identity, timestamp) + } + } + + async identifyGroup( + identity: Group, + timestamp?: string | number + ): Promise { + for (const eventProcessor of this.processors) { + await eventProcessor.identifyGroup(identity, timestamp) + } + } + + shutdown() { + for (const eventProcessor of this.processors) { + eventProcessor.shutdown() + } + } +} diff --git a/packages/backend-core/src/events/processors/index.ts b/packages/backend-core/src/events/processors/index.ts new file mode 100644 index 0000000000..0e75f050db --- /dev/null +++ b/packages/backend-core/src/events/processors/index.ts @@ -0,0 +1,8 @@ +import AnalyticsProcessor from "./AnalyticsProcessor" +import LoggingProcessor from "./LoggingProcessor" +import Processors from "./Processors" + +export const analyticsProcessor = new AnalyticsProcessor() +const loggingProcessor = new LoggingProcessor() + +export const processors = new Processors([analyticsProcessor, loggingProcessor]) diff --git a/packages/backend-core/src/events/processors/types.ts b/packages/backend-core/src/events/processors/types.ts new file mode 100644 index 0000000000..f4066fe248 --- /dev/null +++ b/packages/backend-core/src/events/processors/types.ts @@ -0,0 +1,18 @@ +import { Event, Identity, Group } from "@budibase/types" + +export enum EventProcessorType { + POSTHOG = "posthog", + LOGGING = "logging", +} + +export interface EventProcessor { + processEvent( + event: Event, + identity: Identity, + properties: any, + timestamp?: string | number + ): Promise + identify(identity: Identity, timestamp?: string | number): Promise + identifyGroup(group: Group, timestamp?: string | number): Promise + shutdown(): void +} diff --git a/packages/backend-core/src/events/publishers/account.ts b/packages/backend-core/src/events/publishers/account.ts new file mode 100644 index 0000000000..3f1a8a9161 --- /dev/null +++ b/packages/backend-core/src/events/publishers/account.ts @@ -0,0 +1,29 @@ +import { publishEvent } from "../events" +import { + Event, + Account, + AccountCreatedEvent, + AccountDeletedEvent, + AccountVerifiedEvent, +} from "@budibase/types" + +export async function created(account: Account) { + const properties: AccountCreatedEvent = { + tenantId: account.tenantId, + } + await publishEvent(Event.ACCOUNT_CREATED, properties) +} + +export async function deleted(account: Account) { + const properties: AccountDeletedEvent = { + tenantId: account.tenantId, + } + await publishEvent(Event.ACCOUNT_DELETED, properties) +} + +export async function verified(account: Account) { + const properties: AccountVerifiedEvent = { + tenantId: account.tenantId, + } + await publishEvent(Event.ACCOUNT_VERIFIED, properties) +} diff --git a/packages/backend-core/src/events/publishers/app.ts b/packages/backend-core/src/events/publishers/app.ts new file mode 100644 index 0000000000..dd77b0b8a2 --- /dev/null +++ b/packages/backend-core/src/events/publishers/app.ts @@ -0,0 +1,108 @@ +import { publishEvent } from "../events" +import { + Event, + App, + AppCreatedEvent, + AppUpdatedEvent, + AppDeletedEvent, + AppPublishedEvent, + AppUnpublishedEvent, + AppFileImportedEvent, + AppTemplateImportedEvent, + AppVersionUpdatedEvent, + AppVersionRevertedEvent, + AppRevertedEvent, + AppExportedEvent, +} from "@budibase/types" + +export const created = async (app: App, timestamp?: string | number) => { + const properties: AppCreatedEvent = { + appId: app.appId, + version: app.version, + } + await publishEvent(Event.APP_CREATED, properties, timestamp) +} + +export async function updated(app: App) { + const properties: AppUpdatedEvent = { + appId: app.appId, + version: app.version, + } + await publishEvent(Event.APP_UPDATED, properties) +} + +export async function deleted(app: App) { + const properties: AppDeletedEvent = { + appId: app.appId, + } + await publishEvent(Event.APP_DELETED, properties) +} + +export async function published(app: App, timestamp?: string | number) { + const properties: AppPublishedEvent = { + appId: app.appId, + } + await publishEvent(Event.APP_PUBLISHED, properties, timestamp) +} + +export async function unpublished(app: App) { + const properties: AppUnpublishedEvent = { + appId: app.appId, + } + await publishEvent(Event.APP_UNPUBLISHED, properties) +} + +export async function fileImported(app: App) { + const properties: AppFileImportedEvent = { + appId: app.appId, + } + await publishEvent(Event.APP_FILE_IMPORTED, properties) +} + +export async function templateImported(app: App, templateKey: string) { + const properties: AppTemplateImportedEvent = { + appId: app.appId, + templateKey, + } + await publishEvent(Event.APP_TEMPLATE_IMPORTED, properties) +} + +export async function versionUpdated( + app: App, + currentVersion: string, + updatedToVersion: string +) { + const properties: AppVersionUpdatedEvent = { + appId: app.appId, + currentVersion, + updatedToVersion, + } + await publishEvent(Event.APP_VERSION_UPDATED, properties) +} + +export async function versionReverted( + app: App, + currentVersion: string, + revertedToVersion: string +) { + const properties: AppVersionRevertedEvent = { + appId: app.appId, + currentVersion, + revertedToVersion, + } + await publishEvent(Event.APP_VERSION_REVERTED, properties) +} + +export async function reverted(app: App) { + const properties: AppRevertedEvent = { + appId: app.appId, + } + await publishEvent(Event.APP_REVERTED, properties) +} + +export async function exported(app: App) { + const properties: AppExportedEvent = { + appId: app.appId, + } + await publishEvent(Event.APP_EXPORTED, properties) +} diff --git a/packages/backend-core/src/events/publishers/auth.ts b/packages/backend-core/src/events/publishers/auth.ts new file mode 100644 index 0000000000..93378501f3 --- /dev/null +++ b/packages/backend-core/src/events/publishers/auth.ts @@ -0,0 +1,58 @@ +import { publishEvent } from "../events" +import { + Event, + LoginEvent, + LoginSource, + LogoutEvent, + SSOActivatedEvent, + SSOCreatedEvent, + SSODeactivatedEvent, + SSOType, + SSOUpdatedEvent, +} from "@budibase/types" +import { identification } from ".." + +export async function login(source: LoginSource) { + const identity = await identification.getCurrentIdentity() + const properties: LoginEvent = { + userId: identity.id, + source, + } + await publishEvent(Event.AUTH_LOGIN, properties) +} + +export async function logout() { + const identity = await identification.getCurrentIdentity() + const properties: LogoutEvent = { + userId: identity.id, + } + await publishEvent(Event.AUTH_LOGOUT, properties) +} + +export async function SSOCreated(type: SSOType, timestamp?: string | number) { + const properties: SSOCreatedEvent = { + type, + } + await publishEvent(Event.AUTH_SSO_CREATED, properties, timestamp) +} + +export async function SSOUpdated(type: SSOType) { + const properties: SSOUpdatedEvent = { + type, + } + await publishEvent(Event.AUTH_SSO_UPDATED, properties) +} + +export async function SSOActivated(type: SSOType, timestamp?: string | number) { + const properties: SSOActivatedEvent = { + type, + } + await publishEvent(Event.AUTH_SSO_ACTIVATED, properties, timestamp) +} + +export async function SSODeactivated(type: SSOType) { + const properties: SSODeactivatedEvent = { + type, + } + await publishEvent(Event.AUTH_SSO_DEACTIVATED, properties) +} diff --git a/packages/backend-core/src/events/publishers/automation.ts b/packages/backend-core/src/events/publishers/automation.ts new file mode 100644 index 0000000000..95f9cb8db6 --- /dev/null +++ b/packages/backend-core/src/events/publishers/automation.ts @@ -0,0 +1,94 @@ +import { publishEvent } from "../events" +import { + Automation, + Event, + AutomationStep, + AutomationCreatedEvent, + AutomationDeletedEvent, + AutomationTestedEvent, + AutomationStepCreatedEvent, + AutomationStepDeletedEvent, + AutomationTriggerUpdatedEvent, + AutomationsRunEvent, +} from "@budibase/types" + +export async function created( + automation: Automation, + timestamp?: string | number +) { + const properties: AutomationCreatedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + } + await publishEvent(Event.AUTOMATION_CREATED, properties, timestamp) +} + +export async function triggerUpdated(automation: Automation) { + const properties: AutomationTriggerUpdatedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + } + await publishEvent(Event.AUTOMATION_TRIGGER_UPDATED, properties) +} + +export async function deleted(automation: Automation) { + const properties: AutomationDeletedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + } + await publishEvent(Event.AUTOMATION_DELETED, properties) +} + +export async function tested(automation: Automation) { + const properties: AutomationTestedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + } + await publishEvent(Event.AUTOMATION_TESTED, properties) +} + +export const run = async (count: number, timestamp?: string | number) => { + const properties: AutomationsRunEvent = { + count, + } + await publishEvent(Event.AUTOMATIONS_RUN, properties, timestamp) +} + +export async function stepCreated( + automation: Automation, + step: AutomationStep, + timestamp?: string | number +) { + const properties: AutomationStepCreatedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + stepId: step.id, + stepType: step.stepId, + } + await publishEvent(Event.AUTOMATION_STEP_CREATED, properties, timestamp) +} + +export async function stepDeleted( + automation: Automation, + step: AutomationStep +) { + const properties: AutomationStepDeletedEvent = { + appId: automation.appId, + automationId: automation._id as string, + triggerId: automation.definition?.trigger?.id, + triggerType: automation.definition?.trigger?.stepId, + stepId: step.id, + stepType: step.stepId, + } + await publishEvent(Event.AUTOMATION_STEP_DELETED, properties) +} diff --git a/packages/backend-core/src/events/publishers/backfill.ts b/packages/backend-core/src/events/publishers/backfill.ts new file mode 100644 index 0000000000..c16b3cc9dc --- /dev/null +++ b/packages/backend-core/src/events/publishers/backfill.ts @@ -0,0 +1,67 @@ +import { publishEvent } from "../events" +import { + Event, + AppBackfillSucceededEvent, + AppBackfillFailedEvent, + TenantBackfillSucceededEvent, + TenantBackfillFailedEvent, + InstallationBackfillSucceededEvent, + InstallationBackfillFailedEvent, +} from "@budibase/types" +const env = require("../../environment") + +const shouldSkip = !env.SELF_HOSTED && !env.isDev() + +export async function appSucceeded(properties: AppBackfillSucceededEvent) { + if (shouldSkip) { + return + } + await publishEvent(Event.APP_BACKFILL_SUCCEEDED, properties) +} + +export async function appFailed(error: any) { + if (shouldSkip) { + return + } + const properties: AppBackfillFailedEvent = { + error: JSON.stringify(error, Object.getOwnPropertyNames(error)), + } + await publishEvent(Event.APP_BACKFILL_FAILED, properties) +} + +export async function tenantSucceeded( + properties: TenantBackfillSucceededEvent +) { + if (shouldSkip) { + return + } + await publishEvent(Event.TENANT_BACKFILL_SUCCEEDED, properties) +} + +export async function tenantFailed(error: any) { + if (shouldSkip) { + return + } + const properties: TenantBackfillFailedEvent = { + error: JSON.stringify(error, Object.getOwnPropertyNames(error)), + } + await publishEvent(Event.TENANT_BACKFILL_FAILED, properties) +} + +export async function installationSucceeded() { + if (shouldSkip) { + return + } + const properties: InstallationBackfillSucceededEvent = {} + await publishEvent(Event.INSTALLATION_BACKFILL_SUCCEEDED, properties) +} + +export async function installationFailed(error: any) { + if (shouldSkip) { + return + } + const properties: InstallationBackfillFailedEvent = { + error: JSON.stringify(error, Object.getOwnPropertyNames(error)), + } + await publishEvent(Event.INSTALLATION_BACKFILL_FAILED, properties) +} diff --git a/packages/backend-core/src/events/publishers/datasource.ts b/packages/backend-core/src/events/publishers/datasource.ts new file mode 100644 index 0000000000..3cd68033fc --- /dev/null +++ b/packages/backend-core/src/events/publishers/datasource.ts @@ -0,0 +1,35 @@ +import { publishEvent } from "../events" +import { + Event, + Datasource, + DatasourceCreatedEvent, + DatasourceUpdatedEvent, + DatasourceDeletedEvent, +} from "@budibase/types" + +export async function created( + datasource: Datasource, + timestamp?: string | number +) { + const properties: DatasourceCreatedEvent = { + datasourceId: datasource._id as string, + source: datasource.source, + } + await publishEvent(Event.DATASOURCE_CREATED, properties, timestamp) +} + +export async function updated(datasource: Datasource) { + const properties: DatasourceUpdatedEvent = { + datasourceId: datasource._id as string, + source: datasource.source, + } + await publishEvent(Event.DATASOURCE_UPDATED, properties) +} + +export async function deleted(datasource: Datasource) { + const properties: DatasourceDeletedEvent = { + datasourceId: datasource._id as string, + source: datasource.source, + } + await publishEvent(Event.DATASOURCE_DELETED, properties) +} diff --git a/packages/backend-core/src/events/publishers/email.ts b/packages/backend-core/src/events/publishers/email.ts new file mode 100644 index 0000000000..42c5fb4d4c --- /dev/null +++ b/packages/backend-core/src/events/publishers/email.ts @@ -0,0 +1,12 @@ +import { publishEvent } from "../events" +import { Event, SMTPCreatedEvent, SMTPUpdatedEvent } from "@budibase/types" + +export async function SMTPCreated(timestamp?: string | number) { + const properties: SMTPCreatedEvent = {} + await publishEvent(Event.EMAIL_SMTP_CREATED, properties, timestamp) +} + +export async function SMTPUpdated() { + const properties: SMTPUpdatedEvent = {} + await publishEvent(Event.EMAIL_SMTP_UPDATED, properties) +} diff --git a/packages/backend-core/src/events/publishers/index.ts b/packages/backend-core/src/events/publishers/index.ts new file mode 100644 index 0000000000..65785d4d8b --- /dev/null +++ b/packages/backend-core/src/events/publishers/index.ts @@ -0,0 +1,19 @@ +export * as account from "./account" +export * as app from "./app" +export * as auth from "./auth" +export * as automation from "./automation" +export * as datasource from "./datasource" +export * as email from "./email" +export * as license from "./license" +export * as layout from "./layout" +export * as org from "./org" +export * as query from "./query" +export * as role from "./role" +export * as screen from "./screen" +export * as rows from "./rows" +export * as table from "./table" +export * as serve from "./serve" +export * as user from "./user" +export * as view from "./view" +export * as installation from "./installation" +export * as backfill from "./backfill" diff --git a/packages/backend-core/src/events/publishers/installation.ts b/packages/backend-core/src/events/publishers/installation.ts new file mode 100644 index 0000000000..ef27935210 --- /dev/null +++ b/packages/backend-core/src/events/publishers/installation.ts @@ -0,0 +1,31 @@ +import { publishEvent } from "../events" +import { Event, VersionCheckedEvent, VersionChangeEvent } from "@budibase/types" + +export async function versionChecked(version: string) { + const properties: VersionCheckedEvent = { + currentVersion: version, + } + await publishEvent(Event.INSTALLATION_VERSION_CHECKED, properties) +} + +export async function upgraded(from: string, to: string) { + const properties: VersionChangeEvent = { + from, + to, + } + + await publishEvent(Event.INSTALLATION_VERSION_UPGRADED, properties) +} + +export async function downgraded(from: string, to: string) { + const properties: VersionChangeEvent = { + from, + to, + } + await publishEvent(Event.INSTALLATION_VERSION_DOWNGRADED, properties) +} + +export async function firstStartup() { + const properties = {} + await publishEvent(Event.INSTALLATION_FIRST_STARTUP, properties) +} diff --git a/packages/backend-core/src/events/publishers/layout.ts b/packages/backend-core/src/events/publishers/layout.ts new file mode 100644 index 0000000000..1eede40143 --- /dev/null +++ b/packages/backend-core/src/events/publishers/layout.ts @@ -0,0 +1,21 @@ +import { publishEvent } from "../events" +import { + Event, + Layout, + LayoutCreatedEvent, + LayoutDeletedEvent, +} from "@budibase/types" + +export async function created(layout: Layout, timestamp?: string | number) { + const properties: LayoutCreatedEvent = { + layoutId: layout._id as string, + } + await publishEvent(Event.LAYOUT_CREATED, properties, timestamp) +} + +export async function deleted(layoutId: string) { + const properties: LayoutDeletedEvent = { + layoutId, + } + await publishEvent(Event.LAYOUT_DELETED, properties) +} diff --git a/packages/backend-core/src/events/publishers/license.ts b/packages/backend-core/src/events/publishers/license.ts new file mode 100644 index 0000000000..44dafd84ce --- /dev/null +++ b/packages/backend-core/src/events/publishers/license.ts @@ -0,0 +1,33 @@ +import { publishEvent } from "../events" +import { + Event, + License, + LicenseActivatedEvent, + LicenseDowngradedEvent, + LicenseUpdatedEvent, + LicenseUpgradedEvent, +} from "@budibase/types" + +// TODO +export async function updgraded(license: License) { + const properties: LicenseUpgradedEvent = {} + await publishEvent(Event.LICENSE_UPGRADED, properties) +} + +// TODO +export async function downgraded(license: License) { + const properties: LicenseDowngradedEvent = {} + await publishEvent(Event.LICENSE_DOWNGRADED, properties) +} + +// TODO +export async function updated(license: License) { + const properties: LicenseUpdatedEvent = {} + await publishEvent(Event.LICENSE_UPDATED, properties) +} + +// TODO +export async function activated(license: License) { + const properties: LicenseActivatedEvent = {} + await publishEvent(Event.LICENSE_ACTIVATED, properties) +} diff --git a/packages/backend-core/src/events/publishers/org.ts b/packages/backend-core/src/events/publishers/org.ts new file mode 100644 index 0000000000..4567357db8 --- /dev/null +++ b/packages/backend-core/src/events/publishers/org.ts @@ -0,0 +1,29 @@ +import { publishEvent } from "../events" +import { Event } from "@budibase/types" + +export async function nameUpdated(timestamp?: string | number) { + const properties = {} + await publishEvent(Event.ORG_NAME_UPDATED, properties, timestamp) +} + +export async function logoUpdated(timestamp?: string | number) { + const properties = {} + await publishEvent(Event.ORG_LOGO_UPDATED, properties, timestamp) +} + +export async function platformURLUpdated(timestamp?: string | number) { + const properties = {} + await publishEvent(Event.ORG_PLATFORM_URL_UPDATED, properties, timestamp) +} + +// TODO + +export async function analyticsOptOut() { + const properties = {} + await publishEvent(Event.ANALYTICS_OPT_OUT, properties) +} + +export async function analyticsOptIn() { + const properties = {} + await publishEvent(Event.ANALYTICS_OPT_OUT, properties) +} diff --git a/packages/backend-core/src/events/publishers/query.ts b/packages/backend-core/src/events/publishers/query.ts new file mode 100644 index 0000000000..1bcf561fd0 --- /dev/null +++ b/packages/backend-core/src/events/publishers/query.ts @@ -0,0 +1,79 @@ +import { publishEvent } from "../events" +import { + Event, + Datasource, + Query, + QueryCreatedEvent, + QueryUpdatedEvent, + QueryDeletedEvent, + QueryImportedEvent, + QueryPreviewedEvent, + QueriesRunEvent, +} from "@budibase/types" + +/* eslint-disable */ + +export const created = async ( + datasource: Datasource, + query: Query, + timestamp?: string | number +) => { + const properties: QueryCreatedEvent = { + queryId: query._id as string, + datasourceId: datasource._id as string, + source: datasource.source, + queryVerb: query.queryVerb, + } + await publishEvent(Event.QUERY_CREATED, properties, timestamp) +} + +export const updated = async (datasource: Datasource, query: Query) => { + const properties: QueryUpdatedEvent = { + queryId: query._id as string, + datasourceId: datasource._id as string, + source: datasource.source, + queryVerb: query.queryVerb, + } + await publishEvent(Event.QUERY_UPDATED, properties) +} + +export const deleted = async (datasource: Datasource, query: Query) => { + const properties: QueryDeletedEvent = { + queryId: query._id as string, + datasourceId: datasource._id as string, + source: datasource.source, + queryVerb: query.queryVerb, + } + await publishEvent(Event.QUERY_DELETED, properties) +} + +export const imported = async ( + datasource: Datasource, + importSource: any, + count: any +) => { + const properties: QueryImportedEvent = { + datasourceId: datasource._id as string, + source: datasource.source, + count, + importSource, + } + await publishEvent(Event.QUERY_IMPORT, properties) +} + +export const run = async (count: number, timestamp?: string | number) => { + const properties: QueriesRunEvent = { + count, + } + await publishEvent(Event.QUERIES_RUN, properties, timestamp) +} + +export const previewed = async (datasource: Datasource, query: Query) => { + const properties: QueryPreviewedEvent = { + queryId: query._id, + datasourceId: datasource._id as string, + source: datasource.source, + queryVerb: query.queryVerb, + } + await publishEvent(Event.QUERY_PREVIEWED, properties) +} diff --git a/packages/backend-core/src/events/publishers/role.ts b/packages/backend-core/src/events/publishers/role.ts new file mode 100644 index 0000000000..99074d76a5 --- /dev/null +++ b/packages/backend-core/src/events/publishers/role.ts @@ -0,0 +1,54 @@ +import { publishEvent } from "../events" +import { + Event, + Role, + RoleAssignedEvent, + RoleCreatedEvent, + RoleDeletedEvent, + RoleUnassignedEvent, + RoleUpdatedEvent, + User, +} from "@budibase/types" + +export async function created(role: Role, timestamp?: string | number) { + const properties: RoleCreatedEvent = { + roleId: role._id as string, + permissionId: role.permissionId, + inherits: role.inherits, + } + await publishEvent(Event.ROLE_CREATED, properties, timestamp) +} + +export async function updated(role: Role) { + const properties: RoleUpdatedEvent = { + roleId: role._id as string, + permissionId: role.permissionId, + inherits: role.inherits, + } + await publishEvent(Event.ROLE_UPDATED, properties) +} + +export async function deleted(role: Role) { + const properties: RoleDeletedEvent = { + roleId: role._id as string, + permissionId: role.permissionId, + inherits: role.inherits, + } + await publishEvent(Event.ROLE_DELETED, properties) +} + +export async function assigned(user: User, roleId: string, timestamp?: number) { + const properties: RoleAssignedEvent = { + userId: user._id as string, + roleId, + } + await publishEvent(Event.ROLE_ASSIGNED, properties, timestamp) +} + +export async function unassigned(user: User, roleId: string) { + const properties: RoleUnassignedEvent = { + userId: user._id as string, + roleId, + } + await publishEvent(Event.ROLE_UNASSIGNED, properties) +} diff --git a/packages/backend-core/src/events/publishers/rows.ts b/packages/backend-core/src/events/publishers/rows.ts new file mode 100644 index 0000000000..487e7ea074 --- /dev/null +++ b/packages/backend-core/src/events/publishers/rows.ts @@ -0,0 +1,30 @@ +import { publishEvent } from "../events" +import { + Event, + RowsImportedEvent, + RowsCreatedEvent, + RowImportFormat, + Table, +} from "@budibase/types" + +/* eslint-disable */ + +export const created = async (count: number, timestamp?: string | number) => { + const properties: RowsCreatedEvent = { + count, + } + await publishEvent(Event.ROWS_CREATED, properties, timestamp) +} + +export const imported = async ( + table: Table, + format: RowImportFormat, + count: number +) => { + const properties: RowsImportedEvent = { + tableId: table._id as string, + format, + count, + } + await publishEvent(Event.ROWS_IMPORTED, properties) +} diff --git a/packages/backend-core/src/events/publishers/screen.ts b/packages/backend-core/src/events/publishers/screen.ts new file mode 100644 index 0000000000..966bf72e52 --- /dev/null +++ b/packages/backend-core/src/events/publishers/screen.ts @@ -0,0 +1,25 @@ +import { publishEvent } from "../events" +import { + Event, + Screen, + ScreenCreatedEvent, + ScreenDeletedEvent, +} from "@budibase/types" + +export async function created(screen: Screen, timestamp?: string | number) { + const properties: ScreenCreatedEvent = { + layoutId: screen.layoutId, + screenId: screen._id as string, + roleId: screen.routing.roleId, + } + await publishEvent(Event.SCREEN_CREATED, properties, timestamp) +} + +export async function deleted(screen: Screen) { + const properties: ScreenDeletedEvent = { + layoutId: screen.layoutId, + screenId: screen._id as string, + roleId: screen.routing.roleId, + } + await publishEvent(Event.SCREEN_DELETED, properties) +} diff --git a/packages/backend-core/src/events/publishers/serve.ts b/packages/backend-core/src/events/publishers/serve.ts new file mode 100644 index 0000000000..13afede029 --- /dev/null +++ b/packages/backend-core/src/events/publishers/serve.ts @@ -0,0 +1,28 @@ +import { publishEvent } from "../events" +import { + App, + BuilderServedEvent, + Event, + AppPreviewServedEvent, + AppServedEvent, +} from "@budibase/types" + +export async function servedBuilder() { + const properties: BuilderServedEvent = {} + await publishEvent(Event.SERVED_BUILDER, properties) +} + +export async function servedApp(app: App) { + const properties: AppServedEvent = { + appVersion: app.version, + } + await publishEvent(Event.SERVED_APP, properties) +} + +export async function servedAppPreview(app: App) { + const properties: AppPreviewServedEvent = { + appId: app.appId, + appVersion: app.version, + } + await publishEvent(Event.SERVED_APP_PREVIEW, properties) +} diff --git a/packages/backend-core/src/events/publishers/table.ts b/packages/backend-core/src/events/publishers/table.ts new file mode 100644 index 0000000000..ba53163ca3 --- /dev/null +++ b/packages/backend-core/src/events/publishers/table.ts @@ -0,0 +1,49 @@ +import { publishEvent } from "../events" +import { + Event, + TableExportFormat, + TableImportFormat, + Table, + TableCreatedEvent, + TableUpdatedEvent, + TableDeletedEvent, + TableExportedEvent, + TableImportedEvent, +} from "@budibase/types" + +export async function created(table: Table, timestamp?: string | number) { + const properties: TableCreatedEvent = { + tableId: table._id as string, + } + await publishEvent(Event.TABLE_CREATED, properties, timestamp) +} + +export async function updated(table: Table) { + const properties: TableUpdatedEvent = { + tableId: table._id as string, + } + await publishEvent(Event.TABLE_UPDATED, properties) +} + +export async function deleted(table: Table) { + const properties: TableDeletedEvent = { + tableId: table._id as string, + } + await publishEvent(Event.TABLE_DELETED, properties) +} + +export async function exported(table: Table, format: TableExportFormat) { + const properties: TableExportedEvent = { + tableId: table._id as string, + format, + } + await publishEvent(Event.TABLE_EXPORTED, properties) +} + +export async function imported(table: Table, format: TableImportFormat) { + const properties: TableImportedEvent = { + tableId: table._id as string, + format, + } + await publishEvent(Event.TABLE_IMPORTED, properties) +} diff --git a/packages/backend-core/src/events/publishers/user.ts b/packages/backend-core/src/events/publishers/user.ts new file mode 100644 index 0000000000..2c83b267d6 --- /dev/null +++ b/packages/backend-core/src/events/publishers/user.ts @@ -0,0 +1,122 @@ +import { publishEvent } from "../events" +import { + Event, + User, + UserCreatedEvent, + UserDeletedEvent, + UserInviteAcceptedEvent, + UserInvitedEvent, + UserPasswordForceResetEvent, + UserPasswordResetEvent, + UserPasswordResetRequestedEvent, + UserPasswordUpdatedEvent, + UserPermissionAssignedEvent, + UserPermissionRemovedEvent, + UserUpdatedEvent, +} from "@budibase/types" + +export async function created(user: User, timestamp?: number) { + const properties: UserCreatedEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_CREATED, properties, timestamp) +} + +export async function updated(user: User) { + const properties: UserUpdatedEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_UPDATED, properties) +} + +export async function deleted(user: User) { + const properties: UserDeletedEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_DELETED, properties) +} + +// PERMISSIONS + +export async function permissionAdminAssigned(user: User, timestamp?: number) { + const properties: UserPermissionAssignedEvent = { + userId: user._id as string, + } + await publishEvent( + Event.USER_PERMISSION_ADMIN_ASSIGNED, + properties, + timestamp + ) +} + +export async function permissionAdminRemoved(user: User) { + const properties: UserPermissionRemovedEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_PERMISSION_ADMIN_REMOVED, properties) +} + +export async function permissionBuilderAssigned( + user: User, + timestamp?: number +) { + const properties: UserPermissionAssignedEvent = { + userId: user._id as string, + } + await publishEvent( + Event.USER_PERMISSION_BUILDER_ASSIGNED, + properties, + timestamp + ) +} + +export async function permissionBuilderRemoved(user: User) { + const properties: UserPermissionRemovedEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_PERMISSION_BUILDER_REMOVED, properties) +} + +// INVITE + +export async function invited() { + const properties: UserInvitedEvent = {} + await publishEvent(Event.USER_INVITED, properties) +} + +export async function inviteAccepted(user: User) { + const properties: UserInviteAcceptedEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_INVITED_ACCEPTED, properties) +} + +// PASSWORD + +export async function passwordForceReset(user: User) { + const properties: UserPasswordForceResetEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_PASSWORD_FORCE_RESET, properties) +} + +export async function passwordUpdated(user: User) { + const properties: UserPasswordUpdatedEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_PASSWORD_UPDATED, properties) +} + +export async function passwordResetRequested(user: User) { + const properties: UserPasswordResetRequestedEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_PASSWORD_RESET_REQUESTED, properties) +} + +export async function passwordReset(user: User) { + const properties: UserPasswordResetEvent = { + userId: user._id as string, + } + await publishEvent(Event.USER_PASSWORD_RESET, properties) +} diff --git a/packages/backend-core/src/events/publishers/view.ts b/packages/backend-core/src/events/publishers/view.ts new file mode 100644 index 0000000000..17947758d5 --- /dev/null +++ b/packages/backend-core/src/events/publishers/view.ts @@ -0,0 +1,97 @@ +import { publishEvent } from "../events" +import { + Event, + ViewCalculationCreatedEvent, + ViewCalculationDeletedEvent, + ViewCalculationUpdatedEvent, + ViewCreatedEvent, + ViewDeletedEvent, + ViewExportedEvent, + ViewFilterCreatedEvent, + ViewFilterDeletedEvent, + ViewFilterUpdatedEvent, + ViewUpdatedEvent, + View, + ViewCalculation, + Table, + TableExportFormat, +} from "@budibase/types" + +/* eslint-disable */ + +export async function created(view: View, timestamp?: string | number) { + const properties: ViewCreatedEvent = { + tableId: view.tableId, + } + await publishEvent(Event.VIEW_CREATED, properties, timestamp) +} + +export async function updated(view: View) { + const properties: ViewUpdatedEvent = { + tableId: view.tableId, + } + await publishEvent(Event.VIEW_UPDATED, properties) +} + +export async function deleted(view: View) { + const properties: ViewDeletedEvent = { + tableId: view.tableId, + } + await publishEvent(Event.VIEW_DELETED, properties) +} + +export async function exported(table: Table, format: TableExportFormat) { + const properties: ViewExportedEvent = { + tableId: table._id as string, + format, + } + await publishEvent(Event.VIEW_EXPORTED, properties) +} + +export async function filterCreated(view: View, timestamp?: string | number) { + const properties: ViewFilterCreatedEvent = { + tableId: view.tableId, + } + await publishEvent(Event.VIEW_FILTER_CREATED, properties, timestamp) +} + +export async function filterUpdated(view: View) { + const properties: ViewFilterUpdatedEvent = { + tableId: view.tableId, + } + await publishEvent(Event.VIEW_FILTER_UPDATED, properties) +} + +export async function filterDeleted(view: View) { + const properties: ViewFilterDeletedEvent = { + tableId: view.tableId, + } + await publishEvent(Event.VIEW_FILTER_DELETED, properties) +} + +export async function calculationCreated( + view: View, + timestamp?: string | number +) { + const properties: ViewCalculationCreatedEvent = { + tableId: view.tableId, + calculation: view.calculation as ViewCalculation, + } + await publishEvent(Event.VIEW_CALCULATION_CREATED, properties, timestamp) +} + +export async function calculationUpdated(view: View) { + const properties: ViewCalculationUpdatedEvent = { + tableId: view.tableId, + calculation: view.calculation as ViewCalculation, + } + await publishEvent(Event.VIEW_CALCULATION_UPDATED, properties) +} + +export async function calculationDeleted(existingView: View) { + const properties: ViewCalculationDeletedEvent = { + tableId: existingView.tableId, + calculation: existingView.calculation as ViewCalculation, + } + await publishEvent(Event.VIEW_CALCULATION_DELETED, properties) +} diff --git a/packages/backend-core/src/featureFlags/index.js b/packages/backend-core/src/featureFlags/index.js new file mode 100644 index 0000000000..c050cbdfef --- /dev/null +++ b/packages/backend-core/src/featureFlags/index.js @@ -0,0 +1,53 @@ +const env = require("../environment") +const tenancy = require("../tenancy") + +/** + * Read the TENANT_FEATURE_FLAGS env var and return an array of features flags for each tenant. + * The env var is formatted as: + * tenant1:feature1:feature2,tenant2:feature1 + */ +const getFeatureFlags = () => { + if (!env.TENANT_FEATURE_FLAGS) { + return + } + + const tenantFeatureFlags = {} + + env.TENANT_FEATURE_FLAGS.split(",").forEach(tenantToFeatures => { + const [tenantId, ...features] = tenantToFeatures.split(":") + + features.forEach(feature => { + if (!tenantFeatureFlags[tenantId]) { + tenantFeatureFlags[tenantId] = [] + } + tenantFeatureFlags[tenantId].push(feature) + }) + }) + + return tenantFeatureFlags +} + +const TENANT_FEATURE_FLAGS = getFeatureFlags() + +exports.isEnabled = featureFlag => { + const tenantId = tenancy.getTenantId() + + return ( + TENANT_FEATURE_FLAGS && + TENANT_FEATURE_FLAGS[tenantId] && + TENANT_FEATURE_FLAGS[tenantId].includes(featureFlag) + ) +} + +exports.getTenantFeatureFlags = tenantId => { + if (TENANT_FEATURE_FLAGS && TENANT_FEATURE_FLAGS[tenantId]) { + return TENANT_FEATURE_FLAGS[tenantId] + } + + return [] +} + +exports.FeatureFlag = { + LICENSING: "LICENSING", + GOOGLE_SHEETS: "GOOGLE_SHEETS", +} diff --git a/packages/backend-core/src/hashing.js b/packages/backend-core/src/hashing.js index 7f3af2855f..45abe2f9bd 100644 --- a/packages/backend-core/src/hashing.js +++ b/packages/backend-core/src/hashing.js @@ -1,4 +1,4 @@ -const bcrypt = require("bcryptjs") +const bcrypt = require("bcrypt") const env = require("./environment") const { v4 } = require("uuid") diff --git a/packages/backend-core/src/index.js b/packages/backend-core/src/index.js deleted file mode 100644 index b0bc524d9b..0000000000 --- a/packages/backend-core/src/index.js +++ /dev/null @@ -1,18 +0,0 @@ -const { setDB } = require("./db") - -module.exports = { - init(pouch) { - setDB(pouch) - }, - // some default exports from the library, however these ideally shouldn't - // be used, instead the syntax require("@budibase/backend-core/db") should be used - StaticDatabases: require("./db/utils").StaticDatabases, - db: require("../db"), - redis: require("../redis"), - objectStore: require("../objectStore"), - utils: require("../utils"), - cache: require("../cache"), - auth: require("../auth"), - constants: require("../constants"), - migrations: require("../migrations"), -} diff --git a/packages/backend-core/src/index.ts b/packages/backend-core/src/index.ts new file mode 100644 index 0000000000..ab89eed3b2 --- /dev/null +++ b/packages/backend-core/src/index.ts @@ -0,0 +1,57 @@ +import errors from "./errors" +const errorClasses = errors.errors +import * as events from "./events" +import * as migrations from "./migrations" +import * as users from "./users" +import * as accounts from "./cloud/accounts" +import * as installation from "./installation" +import env from "./environment" +import tenancy from "./tenancy" +import featureFlags from "./featureFlags" +import sessions from "./security/sessions" +import deprovisioning from "./context/deprovision" +import auth from "./auth" +import constants from "./constants" +import * as dbConstants from "./db/constants" +import logging from "./logging" + +// mimic the outer package exports +import * as db from "./pkg/db" +import * as objectStore from "./pkg/objectStore" +import * as utils from "./pkg/utils" +import redis from "./pkg/redis" +import cache from "./pkg/cache" +import context from "./pkg/context" + +const init = (opts: any = {}) => { + db.init(opts.db) +} + +const core = { + init, + db, + ...dbConstants, + redis, + objectStore, + utils, + users, + cache, + auth, + constants, + ...constants, + migrations, + env, + accounts, + tenancy, + context, + featureFlags, + events, + sessions, + deprovisioning, + installation, + errors, + logging, + ...errorClasses, +} + +export = core diff --git a/packages/backend-core/src/installation.ts b/packages/backend-core/src/installation.ts new file mode 100644 index 0000000000..da9b6c5b76 --- /dev/null +++ b/packages/backend-core/src/installation.ts @@ -0,0 +1,96 @@ +import * as hashing from "./hashing" +import * as events from "./events" +import { StaticDatabases } from "./db/constants" +import { doWithDB } from "./db" +import { Installation, IdentityType } from "@budibase/types" +import * as context from "./context" +import semver from "semver" +import { bustCache, withCache, TTL, CacheKeys } from "./cache/generic" + +const pkg = require("../package.json") + +export const getInstall = async (): Promise => { + return withCache(CacheKeys.INSTALLATION, TTL.ONE_DAY, getInstallFromDB, { + useTenancy: false, + }) +} + +const getInstallFromDB = async (): Promise => { + return doWithDB( + StaticDatabases.PLATFORM_INFO.name, + async (platformDb: any) => { + let install: Installation + try { + install = await platformDb.get( + StaticDatabases.PLATFORM_INFO.docs.install + ) + } catch (e: any) { + if (e.status === 404) { + install = { + _id: StaticDatabases.PLATFORM_INFO.docs.install, + installId: hashing.newid(), + version: pkg.version, + } + const resp = await platformDb.put(install) + install._rev = resp.rev + } else { + throw e + } + } + return install + } + ) +} + +const updateVersion = async (version: string): Promise => { + try { + await doWithDB( + StaticDatabases.PLATFORM_INFO.name, + async (platformDb: any) => { + const install = await getInstall() + install.version = version + await platformDb.put(install) + await bustCache(CacheKeys.INSTALLATION) + } + ) + } catch (e: any) { + if (e.status === 409) { + // do nothing - version has already been updated + // likely in clustered environment + return false + } + throw e + } + return true +} + +export const checkInstallVersion = async (): Promise => { + const install = await getInstall() + + const currentVersion = install.version + const newVersion = pkg.version + + if (currentVersion !== newVersion) { + const isUpgrade = semver.gt(newVersion, currentVersion) + const isDowngrade = semver.lt(newVersion, currentVersion) + + const success = await updateVersion(newVersion) + + if (success) { + await context.doInIdentityContext( + { + _id: install.installId, + type: IdentityType.INSTALLATION, + }, + async () => { + if (isUpgrade) { + await events.installation.upgraded(currentVersion, newVersion) + } else if (isDowngrade) { + await events.installation.downgraded(currentVersion, newVersion) + } + } + ) + await events.identification.identifyInstallationGroup(install.installId) + } + } +} diff --git a/packages/backend-core/src/logging.ts b/packages/backend-core/src/logging.ts new file mode 100644 index 0000000000..8eda15ac79 --- /dev/null +++ b/packages/backend-core/src/logging.ts @@ -0,0 +1,25 @@ +const NonErrors = ["AccountError"] + +function isSuppressed(e?: any) { + return e && e["suppressAlert"] +} + +export function logAlert(message: string, e?: any) { + if (e && NonErrors.includes(e.name) && isSuppressed(e)) { + return + } + let errorJson = "" + if (e) { + errorJson = ": " + JSON.stringify(e, Object.getOwnPropertyNames(e)) + } + console.error(`bb-alert: ${message} ${errorJson}`) +} + +export function logWarn(message: string) { + console.warn(`bb-warn: ${message}`) +} + +export default { + logAlert, + logWarn, +} diff --git a/packages/backend-core/src/middleware/authenticated.js b/packages/backend-core/src/middleware/authenticated.js index ef982c799b..4e6e0b7ba2 100644 --- a/packages/backend-core/src/middleware/authenticated.js +++ b/packages/backend-core/src/middleware/authenticated.js @@ -4,9 +4,12 @@ const { getUser } = require("../cache/user") const { getSession, updateSessionTTL } = require("../security/sessions") const { buildMatcherRegex, matches } = require("./matchers") const env = require("../environment") -const { SEPARATOR, ViewNames, queryGlobalView } = require("../../db") +const { SEPARATOR } = require("../db/constants") +const { ViewNames } = require("../db/utils") +const { queryGlobalView } = require("../db/views") const { getGlobalDB, doInTenant } = require("../tenancy") const { decrypt } = require("../security/encryption") +const identity = require("../context/identity") function finalise( ctx, @@ -91,7 +94,6 @@ module.exports = ( user = await getUser(userId, session.tenantId) } user.csrfToken = session.csrfToken - delete user.password authenticated = true } catch (err) { error = err @@ -125,6 +127,8 @@ module.exports = ( } if (!user && tenantId) { user = { tenantId } + } else { + delete user.password } // be explicit if (authenticated !== true) { @@ -132,7 +136,12 @@ module.exports = ( } // isAuthenticated is a function, so use a variable to be able to check authed state finalise(ctx, { authenticated, user, internal, version, publicEndpoint }) - return next() + + if (user && user.email) { + return identity.doInUserContext(user, next) + } else { + return next() + } } catch (err) { // invalid token, clear the cookie if (err && err.name === "JsonWebTokenError") { diff --git a/packages/backend-core/src/middleware/index.js b/packages/backend-core/src/middleware/index.js index 6c4c0d8883..1721d56a3c 100644 --- a/packages/backend-core/src/middleware/index.js +++ b/packages/backend-core/src/middleware/index.js @@ -2,7 +2,7 @@ const jwt = require("./passport/jwt") const local = require("./passport/local") const google = require("./passport/google") const oidc = require("./passport/oidc") -const { authError } = require("./passport/utils") +const { authError, ssoCallbackUrl } = require("./passport/utils") const authenticated = require("./authenticated") const auditLog = require("./auditLog") const tenancy = require("./tenancy") @@ -20,6 +20,7 @@ module.exports = { tenancy, authError, internalApi, + ssoCallbackUrl, datasource: { google: datasourceGoogle, }, diff --git a/packages/backend-core/src/middleware/passport/datasource/google.js b/packages/backend-core/src/middleware/passport/datasource/google.js index 5046815b1f..8f2022c2d7 100644 --- a/packages/backend-core/src/middleware/passport/datasource/google.js +++ b/packages/backend-core/src/middleware/passport/datasource/google.js @@ -1,8 +1,9 @@ const google = require("../google") +const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy const { Cookies, Configs } = require("../../../constants") const { clearCookie, getCookie } = require("../../../utils") -const { getDB } = require("../../../db") -const { getScopedConfig } = require("../../../db/utils") +const { getScopedConfig, getPlatformUrl } = require("../../../db/utils") +const { doWithDB } = require("../../../db") const environment = require("../../../environment") const { getGlobalDB } = require("../../../tenancy") @@ -13,18 +14,20 @@ async function fetchGoogleCreds() { type: Configs.GOOGLE, }) // or fall back to env variables - const config = googleConfig || { - clientID: environment.GOOGLE_CLIENT_ID, - clientSecret: environment.GOOGLE_CLIENT_SECRET, - } - - return config + return ( + googleConfig || { + clientID: environment.GOOGLE_CLIENT_ID, + clientSecret: environment.GOOGLE_CLIENT_SECRET, + } + ) } async function preAuth(passport, ctx, next) { // get the relevant config const googleConfig = await fetchGoogleCreds() - let callbackUrl = `${environment.PLATFORM_URL}/api/global/auth/datasource/google/callback` + const platformUrl = await getPlatformUrl({ tenantAware: false }) + + let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback` const strategy = await google.strategyFactory(googleConfig, callbackUrl) if (!ctx.query.appId || !ctx.query.datasourceId) { @@ -41,34 +44,37 @@ async function preAuth(passport, ctx, next) { async function postAuth(passport, ctx, next) { // get the relevant config const config = await fetchGoogleCreds() + const platformUrl = await getPlatformUrl({ tenantAware: false }) - let callbackUrl = `${environment.PLATFORM_URL}/api/global/auth/datasource/google/callback` - const strategy = await google.strategyFactory( - config, - callbackUrl, - (accessToken, refreshToken, profile, done) => { - clearCookie(ctx, Cookies.DatasourceAuth) - done(null, { accessToken, refreshToken }) - } - ) - + let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback` const authStateCookie = getCookie(ctx, Cookies.DatasourceAuth) return passport.authenticate( - strategy, + new GoogleStrategy( + { + clientID: config.clientID, + clientSecret: config.clientSecret, + callbackURL: callbackUrl, + }, + (accessToken, refreshToken, profile, done) => { + clearCookie(ctx, Cookies.DatasourceAuth) + done(null, { accessToken, refreshToken }) + } + ), { successRedirect: "/", failureRedirect: "/error" }, async (err, tokens) => { // update the DB for the datasource with all the user info - const db = getDB(authStateCookie.appId) - const datasource = await db.get(authStateCookie.datasourceId) - if (!datasource.config) { - datasource.config = {} - } - datasource.config.auth = { type: "google", ...tokens } - await db.put(datasource) - ctx.redirect( - `/builder/app/${authStateCookie.appId}/data/datasource/${authStateCookie.datasourceId}` - ) + await doWithDB(authStateCookie.appId, async db => { + const datasource = await db.get(authStateCookie.datasourceId) + if (!datasource.config) { + datasource.config = {} + } + datasource.config.auth = { type: "google", ...tokens } + await db.put(datasource) + ctx.redirect( + `/builder/app/${authStateCookie.appId}/data/datasource/${authStateCookie.datasourceId}` + ) + }) } )(ctx, next) } diff --git a/packages/backend-core/src/middleware/passport/google.js b/packages/backend-core/src/middleware/passport/google.js index 8fd0961ea1..7419974cd7 100644 --- a/packages/backend-core/src/middleware/passport/google.js +++ b/packages/backend-core/src/middleware/passport/google.js @@ -1,25 +1,29 @@ const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy - +const { ssoCallbackUrl } = require("./utils") const { authenticateThirdParty } = require("./third-party-common") +const { Configs } = require("../../../constants") -async function authenticate(accessToken, refreshToken, profile, done) { - const thirdPartyUser = { - provider: profile.provider, // should always be 'google' - providerType: "google", - userId: profile.id, - profile: profile, - email: profile._json.email, - oauth2: { - accessToken: accessToken, - refreshToken: refreshToken, - }, +const buildVerifyFn = saveUserFn => { + return (accessToken, refreshToken, profile, done) => { + const thirdPartyUser = { + provider: profile.provider, // should always be 'google' + providerType: "google", + userId: profile.id, + profile: profile, + email: profile._json.email, + oauth2: { + accessToken, + refreshToken, + }, + } + + return authenticateThirdParty( + thirdPartyUser, + true, // require local accounts to exist + done, + saveUserFn + ) } - - return authenticateThirdParty( - thirdPartyUser, - true, // require local accounts to exist - done - ) } /** @@ -27,11 +31,7 @@ async function authenticate(accessToken, refreshToken, profile, done) { * from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport. * @returns Dynamically configured Passport Google Strategy */ -exports.strategyFactory = async function ( - config, - callbackUrl, - verify = authenticate -) { +exports.strategyFactory = async function (config, callbackUrl, saveUserFn) { try { const { clientID, clientSecret } = config @@ -41,6 +41,7 @@ exports.strategyFactory = async function ( ) } + const verify = buildVerifyFn(saveUserFn) return new GoogleStrategy( { clientID: config.clientID, @@ -57,5 +58,10 @@ exports.strategyFactory = async function ( ) } } + +exports.getCallbackUrl = async function (db, config) { + return ssoCallbackUrl(db, config, Configs.GOOGLE) +} + // expose for testing -exports.authenticate = authenticate +exports.buildVerifyFn = buildVerifyFn diff --git a/packages/backend-core/src/middleware/passport/local.js b/packages/backend-core/src/middleware/passport/local.js index 2149bd3e18..b955d29102 100644 --- a/packages/backend-core/src/middleware/passport/local.js +++ b/packages/backend-core/src/middleware/passport/local.js @@ -2,7 +2,7 @@ const jwt = require("jsonwebtoken") const { UserStatus } = require("../../constants") const { compare } = require("../../hashing") const env = require("../../environment") -const { getGlobalUserByEmail } = require("../../utils") +const users = require("../../users") const { authError } = require("./utils") const { newid } = require("../../hashing") const { createASession } = require("../../security/sessions") @@ -28,9 +28,9 @@ exports.authenticate = async function (ctx, email, password, done) { if (!email) return authError(done, "Email Required") if (!password) return authError(done, "Password Required") - const dbUser = await getGlobalUserByEmail(email) + const dbUser = await users.getGlobalUserByEmail(email) if (dbUser == null) { - return authError(done, "User not found") + return authError(done, `User not found: [${email}]`) } // check that the user is currently inactive, if this is the case throw invalid @@ -55,6 +55,7 @@ exports.authenticate = async function (ctx, email, password, done) { if (await compare(password, dbUser.password)) { const sessionId = newid() const tenantId = getTenantId() + await createASession(dbUser._id, { sessionId, tenantId }) dbUser.token = jwt.sign( diff --git a/packages/backend-core/src/middleware/passport/oidc.js b/packages/backend-core/src/middleware/passport/oidc.js index 3a75dfcf8e..20dbd4669b 100644 --- a/packages/backend-core/src/middleware/passport/oidc.js +++ b/packages/backend-core/src/middleware/passport/oidc.js @@ -1,47 +1,52 @@ const fetch = require("node-fetch") const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy const { authenticateThirdParty } = require("./third-party-common") +const { ssoCallbackUrl } = require("./utils") +const { Configs } = require("../../../constants") -/** - * @param {*} issuer The identity provider base URL - * @param {*} sub The user ID - * @param {*} profile The user profile information. Created by passport from the /userinfo response - * @param {*} jwtClaims The parsed id_token claims - * @param {*} accessToken The access_token for contacting the identity provider - may or may not be a JWT - * @param {*} refreshToken The refresh_token for obtaining a new access_token - usually not a JWT - * @param {*} idToken The id_token - always a JWT - * @param {*} params The response body from requesting an access_token - * @param {*} done The passport callback: err, user, info - */ -async function authenticate( - issuer, - sub, - profile, - jwtClaims, - accessToken, - refreshToken, - idToken, - params, - done -) { - const thirdPartyUser = { - // store the issuer info to enable sync in future - provider: issuer, - providerType: "oidc", - userId: profile.id, - profile: profile, - email: getEmail(profile, jwtClaims), - oauth2: { - accessToken: accessToken, - refreshToken: refreshToken, - }, - } - - return authenticateThirdParty( - thirdPartyUser, - false, // don't require local accounts to exist +const buildVerifyFn = saveUserFn => { + /** + * @param {*} issuer The identity provider base URL + * @param {*} sub The user ID + * @param {*} profile The user profile information. Created by passport from the /userinfo response + * @param {*} jwtClaims The parsed id_token claims + * @param {*} accessToken The access_token for contacting the identity provider - may or may not be a JWT + * @param {*} refreshToken The refresh_token for obtaining a new access_token - usually not a JWT + * @param {*} idToken The id_token - always a JWT + * @param {*} params The response body from requesting an access_token + * @param {*} done The passport callback: err, user, info + */ + return async ( + issuer, + sub, + profile, + jwtClaims, + accessToken, + refreshToken, + idToken, + params, done - ) + ) => { + const thirdPartyUser = { + // store the issuer info to enable sync in future + provider: issuer, + providerType: "oidc", + userId: profile.id, + profile: profile, + email: getEmail(profile, jwtClaims), + oauth2: { + accessToken: accessToken, + refreshToken: refreshToken, + }, + } + + return authenticateThirdParty( + thirdPartyUser, + false, // don't require local accounts to exist + done, + saveUserFn + ) + } } /** @@ -86,11 +91,24 @@ function validEmail(value) { * from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport. * @returns Dynamically configured Passport OIDC Strategy */ -exports.strategyFactory = async function (config, callbackUrl) { +exports.strategyFactory = async function (config, saveUserFn) { try { - const { clientID, clientSecret, configUrl } = config + const verify = buildVerifyFn(saveUserFn) + const strategy = new OIDCStrategy(config, verify) + strategy.name = "oidc" + return strategy + } catch (err) { + console.error(err) + throw new Error("Error constructing OIDC authentication strategy", err) + } +} + +exports.fetchStrategyConfig = async function (enrichedConfig, callbackUrl) { + try { + const { clientID, clientSecret, configUrl } = enrichedConfig if (!clientID || !clientSecret || !callbackUrl || !configUrl) { + //check for remote config and all required elements throw new Error( "Configuration invalid. Must contain clientID, clientSecret, callbackUrl and configUrl" ) @@ -106,23 +124,24 @@ exports.strategyFactory = async function (config, callbackUrl) { const body = await response.json() - return new OIDCStrategy( - { - issuer: body.issuer, - authorizationURL: body.authorization_endpoint, - tokenURL: body.token_endpoint, - userInfoURL: body.userinfo_endpoint, - clientID: clientID, - clientSecret: clientSecret, - callbackURL: callbackUrl, - }, - authenticate - ) + return { + issuer: body.issuer, + authorizationURL: body.authorization_endpoint, + tokenURL: body.token_endpoint, + userInfoURL: body.userinfo_endpoint, + clientID: clientID, + clientSecret: clientSecret, + callbackURL: callbackUrl, + } } catch (err) { console.error(err) - throw new Error("Error constructing OIDC authentication strategy", err) + throw new Error("Error constructing OIDC authentication configuration", err) } } +exports.getCallbackUrl = async function (db, config) { + return ssoCallbackUrl(db, config, Configs.OIDC) +} + // expose for testing -exports.authenticate = authenticate +exports.buildVerifyFn = buildVerifyFn diff --git a/packages/backend-core/src/middleware/passport/tests/google.spec.js b/packages/backend-core/src/middleware/passport/tests/google.spec.js index 9cc878bba9..c5580ea309 100644 --- a/packages/backend-core/src/middleware/passport/tests/google.spec.js +++ b/packages/backend-core/src/middleware/passport/tests/google.spec.js @@ -58,8 +58,10 @@ describe("google", () => { it("delegates authentication to third party common", async () => { const google = require("../google") + const mockSaveUserFn = jest.fn() + const authenticate = await google.buildVerifyFn(mockSaveUserFn) - await google.authenticate( + await authenticate( data.accessToken, data.refreshToken, profile, @@ -69,7 +71,8 @@ describe("google", () => { expect(authenticateThirdParty).toHaveBeenCalledWith( user, true, - mockDone) + mockDone, + mockSaveUserFn) }) }) }) diff --git a/packages/backend-core/src/middleware/passport/tests/oidc.spec.js b/packages/backend-core/src/middleware/passport/tests/oidc.spec.js index 44538b9135..c00ab2ea7d 100644 --- a/packages/backend-core/src/middleware/passport/tests/oidc.spec.js +++ b/packages/backend-core/src/middleware/passport/tests/oidc.spec.js @@ -48,8 +48,8 @@ describe("oidc", () => { it("should create successfully create an oidc strategy", async () => { const oidc = require("../oidc") - - await oidc.strategyFactory(oidcConfig, callbackUrl) + const enrichedConfig = await oidc.fetchStrategyConfig(oidcConfig, callbackUrl) + await oidc.strategyFactory(enrichedConfig, callbackUrl) expect(mockFetch).toHaveBeenCalledWith(oidcConfig.configUrl) @@ -71,7 +71,7 @@ describe("oidc", () => { describe("authenticate", () => { afterEach(() => { - jest.clearAllMocks(); + jest.clearAllMocks() }); // mock third party common authentication @@ -80,11 +80,13 @@ describe("oidc", () => { // mock the passport callback const mockDone = jest.fn() + const mockSaveUserFn = jest.fn() async function doAuthenticate() { const oidc = require("../oidc") + const authenticate = await oidc.buildVerifyFn(mockSaveUserFn) - await oidc.authenticate( + await authenticate( issuer, sub, profile, @@ -103,11 +105,13 @@ describe("oidc", () => { expect(authenticateThirdParty).toHaveBeenCalledWith( user, false, - mockDone) + mockDone, + mockSaveUserFn, + ) } it("delegates authentication to third party common", async () => { - doTest() + await doTest() }) it("uses JWT email to get email", async () => { @@ -116,7 +120,7 @@ describe("oidc", () => { email : "mock@budibase.com" } - doTest() + await doTest() }) it("uses JWT username to get email", async () => { @@ -125,7 +129,7 @@ describe("oidc", () => { preferred_username : "mock@budibase.com" } - doTest() + await doTest() }) it("uses JWT invalid username to get email", async () => { diff --git a/packages/backend-core/src/middleware/passport/tests/third-party-common.spec.js b/packages/backend-core/src/middleware/passport/tests/third-party-common.spec.js index 3a3c55bfa0..41a253b328 100644 --- a/packages/backend-core/src/middleware/passport/tests/third-party-common.spec.js +++ b/packages/backend-core/src/middleware/passport/tests/third-party-common.spec.js @@ -1,18 +1,11 @@ -// Mock data - -require("../../../tests/utilities/dbConfig") - -const database = require("../../../db") +require("../../../../tests/utilities/TestConfiguration") const { authenticateThirdParty } = require("../third-party-common") const { data } = require("./utilities/mock-data") +const { DEFAULT_TENANT_ID } = require("../../../constants") -const { - StaticDatabases, - generateGlobalUserID -} = require("../../../db/utils") +const { generateGlobalUserID } = require("../../../db/utils") const { newid } = require("../../../hashing") - -let db +const { doWithGlobalDB, doInTenant } = require("../../../tenancy") const done = jest.fn() @@ -21,43 +14,52 @@ const getErrorMessage = () => { } const saveUser = async (user) => { - return await db.put(user) + return doWithGlobalDB(DEFAULT_TENANT_ID, async db => { + return await db.put(user) + }) +} + +function authenticate(user, requireLocal, saveFn) { + return doInTenant(DEFAULT_TENANT_ID, () => { + return authenticateThirdParty(user, requireLocal, done, saveFn) + }) } describe("third party common", () => { - describe("authenticateThirdParty", () => { + describe("authenticateThirdParty", () => { let thirdPartyUser - + beforeEach(() => { - db = database.getDB(StaticDatabases.GLOBAL.name) thirdPartyUser = data.buildThirdPartyUser() }) afterEach(async () => { - jest.clearAllMocks() - await db.destroy() + return doWithGlobalDB(DEFAULT_TENANT_ID, async db => { + jest.clearAllMocks() + await db.destroy() + }) }) - + describe("validation", () => { const testValidation = async (message) => { - await authenticateThirdParty(thirdPartyUser, false, done, saveUser) + await authenticate(thirdPartyUser, false, saveUser) expect(done.mock.calls.length).toBe(1) expect(getErrorMessage()).toContain(message) } it("provider fails", async () => { delete thirdPartyUser.provider - testValidation("third party user provider required") + await testValidation("third party user provider required") }) it("user id fails", async () => { delete thirdPartyUser.userId - testValidation("third party user id required") + await testValidation("third party user id required") }) it("email fails", async () => { delete thirdPartyUser.email - testValidation("third party user email required") + await testValidation("third party user email required") }) }) @@ -81,34 +83,37 @@ describe("third party common", () => { describe("when the user doesn't exist", () => { describe("when a local account is required", () => { it("returns an error message", async () => { - await authenticateThirdParty(thirdPartyUser, true, done, saveUser) + await authenticate(thirdPartyUser, true, saveUser) expect(done.mock.calls.length).toBe(1) expect(getErrorMessage()).toContain("Email does not yet exist. You must set up your local budibase account first.") }) }) - + describe("when a local account isn't required", () => { it("creates and authenticates the user", async () => { - await authenticateThirdParty(thirdPartyUser, false, done, saveUser) + await authenticate(thirdPartyUser, false, saveUser) const user = expectUserIsAuthenticated() expectUserIsSynced(user, thirdPartyUser) expect(user.roles).toStrictEqual({}) }) }) }) - + describe("when the user exists", () => { let dbUser let id let email const createUser = async () => { - dbUser = { - _id: id, - email: email, - } - const response = await db.put(dbUser) - dbUser._rev = response.rev + return doWithGlobalDB(DEFAULT_TENANT_ID, async db => { + dbUser = { + _id: id, + email: email, + } + const response = await db.put(dbUser) + dbUser._rev = response.rev + return dbUser + }) } const expectUserIsUpdated = (user) => { @@ -126,8 +131,8 @@ describe("third party common", () => { }) it("syncs and authenticates the user", async () => { - await authenticateThirdParty(thirdPartyUser, true, done, saveUser) - + await authenticate(thirdPartyUser, true, saveUser) + const user = expectUserIsAuthenticated() expectUserIsSynced(user, thirdPartyUser) expectUserIsUpdated(user) @@ -142,8 +147,8 @@ describe("third party common", () => { }) it("syncs and authenticates the user", async () => { - await authenticateThirdParty(thirdPartyUser, true, done, saveUser) - + await authenticate(thirdPartyUser, true, saveUser) + const user = expectUserIsAuthenticated() expectUserIsSynced(user, thirdPartyUser) expectUserIsUpdated(user) @@ -160,8 +165,8 @@ describe("third party common", () => { }) it("syncs and authenticates the user", async () => { - await authenticateThirdParty(thirdPartyUser, true, done, saveUser) - + await authenticate(thirdPartyUser, true, saveUser) + const user = expectUserIsAuthenticated() expectUserIsSynced(user, thirdPartyUser) expectUserIsUpdated(user) diff --git a/packages/backend-core/src/middleware/passport/third-party-common.js b/packages/backend-core/src/middleware/passport/third-party-common.js index b467c0b10b..1c5891fce7 100644 --- a/packages/backend-core/src/middleware/passport/third-party-common.js +++ b/packages/backend-core/src/middleware/passport/third-party-common.js @@ -1,11 +1,10 @@ const env = require("../../environment") const jwt = require("jsonwebtoken") const { generateGlobalUserID } = require("../../db/utils") -const { saveUser } = require("../../utils") const { authError } = require("./utils") const { newid } = require("../../hashing") const { createASession } = require("../../security/sessions") -const { getGlobalUserByEmail } = require("../../utils") +const users = require("../../users") const { getGlobalDB, getTenantId } = require("../../tenancy") const fetch = require("node-fetch") @@ -16,8 +15,11 @@ exports.authenticateThirdParty = async function ( thirdPartyUser, requireLocalAccount = true, done, - saveUserFn = saveUser + saveUserFn ) { + if (!saveUserFn) { + throw new Error("Save user function must be provided") + } if (!thirdPartyUser.provider) { return authError(done, "third party user provider required") } @@ -50,7 +52,7 @@ exports.authenticateThirdParty = async function ( // fallback to loading by email if (!dbUser) { - dbUser = await getGlobalUserByEmail(thirdPartyUser.email) + dbUser = await users.getGlobalUserByEmail(thirdPartyUser.email) } // exit early if there is still no user and auto creation is disabled @@ -77,14 +79,14 @@ exports.authenticateThirdParty = async function ( dbUser.forceResetPassword = false // create or sync the user - let response try { - response = await saveUserFn(dbUser, getTenantId(), false, false) + await saveUserFn(dbUser, false, false) } catch (err) { return authError(done, err) } - dbUser._rev = response.rev + // now that we're sure user exists, load them from the db + dbUser = await db.get(dbUser._id) // authenticate const sessionId = newid() diff --git a/packages/backend-core/src/middleware/passport/utils.js b/packages/backend-core/src/middleware/passport/utils.js index cbb93bfa3b..217130cd6d 100644 --- a/packages/backend-core/src/middleware/passport/utils.js +++ b/packages/backend-core/src/middleware/passport/utils.js @@ -1,3 +1,7 @@ +const { isMultiTenant, getTenantId } = require("../../tenancy") +const { getScopedConfig } = require("../../db/utils") +const { Configs } = require("../../constants") + /** * Utility to handle authentication errors. * @@ -5,6 +9,7 @@ * @param {*} message Message that will be returned in the response body * @param {*} err (Optional) error that will be logged */ + exports.authError = function (done, message, err = null) { return done( err, @@ -12,3 +17,21 @@ exports.authError = function (done, message, err = null) { { message: message } ) } + +exports.ssoCallbackUrl = async (db, config, type) => { + // incase there is a callback URL from before + if (config && config.callbackURL) { + return config.callbackURL + } + const publicConfig = await getScopedConfig(db, { + type: Configs.SETTINGS, + }) + + let callbackUrl = `/api/global/auth` + if (isMultiTenant()) { + callbackUrl += `/${getTenantId()}` + } + callbackUrl += `/${type}/callback` + + return `${publicConfig.platformUrl}${callbackUrl}` +} diff --git a/packages/backend-core/src/middleware/tenancy.js b/packages/backend-core/src/middleware/tenancy.js index 5bb81f8824..8083322b29 100644 --- a/packages/backend-core/src/middleware/tenancy.js +++ b/packages/backend-core/src/middleware/tenancy.js @@ -1,6 +1,38 @@ -const { setTenantId } = require("../tenancy") -const ContextFactory = require("../context/FunctionContext") +const { doInTenant, isMultiTenant, DEFAULT_TENANT_ID } = require("../tenancy") const { buildMatcherRegex, matches } = require("./matchers") +const { Headers } = require("../constants") + +const getTenantID = (ctx, opts = { allowQs: false, allowNoTenant: false }) => { + // exit early if not multi-tenant + if (!isMultiTenant()) { + return DEFAULT_TENANT_ID + } + + let tenantId + const allowQs = opts && opts.allowQs + const allowNoTenant = opts && opts.allowNoTenant + const header = ctx.request.headers[Headers.TENANT_ID] + const user = ctx.user || {} + if (allowQs) { + const query = ctx.request.query || {} + tenantId = query.tenantId + } + // override query string (if allowed) by user, or header + // URL params cannot be used in a middleware, as they are + // processed later in the chain + tenantId = user.tenantId || header || tenantId + + // Set the tenantId from the subdomain + if (!tenantId) { + tenantId = ctx.subdomains && ctx.subdomains[0] + } + + if (!tenantId && !allowNoTenant) { + ctx.throw(403, "Tenant id not set") + } + + return tenantId +} module.exports = ( allowQueryStringPatterns, @@ -10,10 +42,11 @@ module.exports = ( const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns) const noTenancyOptions = buildMatcherRegex(noTenancyPatterns) - return ContextFactory.getMiddleware(ctx => { + return async function (ctx, next) { const allowNoTenant = opts.noTenancyRequired || !!matches(ctx, noTenancyOptions) const allowQs = !!matches(ctx, allowQsOptions) - setTenantId(ctx, { allowQs, allowNoTenant }) - }) + const tenantId = getTenantID(ctx, { allowQs, allowNoTenant }) + return doInTenant(tenantId, next) + } } diff --git a/packages/backend-core/src/migrations/definitions.ts b/packages/backend-core/src/migrations/definitions.ts new file mode 100644 index 0000000000..745c8718c9 --- /dev/null +++ b/packages/backend-core/src/migrations/definitions.ts @@ -0,0 +1,40 @@ +import { + MigrationType, + MigrationName, + MigrationDefinition, +} from "@budibase/types" + +export const DEFINITIONS: MigrationDefinition[] = [ + { + type: MigrationType.GLOBAL, + name: MigrationName.USER_EMAIL_VIEW_CASING, + }, + { + type: MigrationType.GLOBAL, + name: MigrationName.QUOTAS_1, + }, + { + type: MigrationType.APP, + name: MigrationName.APP_URLS, + }, + { + type: MigrationType.GLOBAL, + name: MigrationName.DEVELOPER_QUOTA, + }, + { + type: MigrationType.GLOBAL, + name: MigrationName.PUBLISHED_APP_QUOTA, + }, + { + type: MigrationType.APP, + name: MigrationName.EVENT_APP_BACKFILL, + }, + { + type: MigrationType.GLOBAL, + name: MigrationName.EVENT_GLOBAL_BACKFILL, + }, + { + type: MigrationType.INSTALLATION, + name: MigrationName.EVENT_INSTALLATION_BACKFILL, + }, +] diff --git a/packages/backend-core/src/migrations/index.js b/packages/backend-core/src/migrations/index.js deleted file mode 100644 index db0fe6b8ce..0000000000 --- a/packages/backend-core/src/migrations/index.js +++ /dev/null @@ -1,117 +0,0 @@ -const { DEFAULT_TENANT_ID } = require("../constants") -const { DocumentTypes } = require("../db/constants") -const { getAllApps } = require("../db/utils") -const environment = require("../environment") -const { - doInTenant, - getTenantIds, - getGlobalDBName, - getTenantId, -} = require("../tenancy") - -exports.MIGRATION_TYPES = { - GLOBAL: "global", // run once, recorded in global db, global db is provided as an argument - APP: "app", // run per app, recorded in each app db, app db is provided as an argument -} - -exports.getMigrationsDoc = async db => { - // get the migrations doc - try { - return await db.get(DocumentTypes.MIGRATIONS) - } catch (err) { - if (err.status && err.status === 404) { - return { _id: DocumentTypes.MIGRATIONS } - } - console.error(err) - } -} - -const runMigration = async (CouchDB, migration, options = {}) => { - const tenantId = getTenantId() - const migrationType = migration.type - const migrationName = migration.name - - // get the db to store the migration in - let dbNames - if (migrationType === exports.MIGRATION_TYPES.GLOBAL) { - dbNames = [getGlobalDBName()] - } else if (migrationType === exports.MIGRATION_TYPES.APP) { - const apps = await getAllApps(migration.opts) - dbNames = apps.map(app => app.appId) - } else { - throw new Error( - `[Tenant: ${tenantId}] Unrecognised migration type [${migrationType}]` - ) - } - - // run the migration against each db - for (const dbName of dbNames) { - const db = new CouchDB(dbName) - try { - const doc = await exports.getMigrationsDoc(db) - - // exit if the migration has been performed already - if (doc[migrationName]) { - if ( - options.force && - options.force[migrationType] && - options.force[migrationType].includes(migrationName) - ) { - console.log( - `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Forcing` - ) - } else { - // the migration has already been performed - continue - } - } - - console.log( - `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Running` - ) - // run the migration with tenant context - await migration.fn(db) - console.log( - `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Complete` - ) - - // mark as complete - doc[migrationName] = Date.now() - await db.put(doc) - } catch (err) { - console.error( - `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Error: `, - err - ) - throw err - } - } -} - -exports.runMigrations = async (CouchDB, migrations, options = {}) => { - console.log("Running migrations") - let tenantIds - if (environment.MULTI_TENANCY) { - if (!options.tenantIds || !options.tenantIds.length) { - // run for all tenants - tenantIds = await getTenantIds() - } else { - tenantIds = options.tenantIds - } - } else { - // single tenancy - tenantIds = [DEFAULT_TENANT_ID] - } - - // for all tenants - for (const tenantId of tenantIds) { - // for all migrations - for (const migration of migrations) { - // run the migration - await doInTenant(tenantId, () => - runMigration(CouchDB, migration, options) - ) - } - } - console.log("Migrations complete") -} diff --git a/packages/backend-core/src/migrations/index.ts b/packages/backend-core/src/migrations/index.ts new file mode 100644 index 0000000000..bce0cfc75c --- /dev/null +++ b/packages/backend-core/src/migrations/index.ts @@ -0,0 +1,2 @@ +export * from "./migrations" +export * from "./definitions" diff --git a/packages/backend-core/src/migrations/migrations.ts b/packages/backend-core/src/migrations/migrations.ts new file mode 100644 index 0000000000..2e4ef0da76 --- /dev/null +++ b/packages/backend-core/src/migrations/migrations.ts @@ -0,0 +1,189 @@ +import { DEFAULT_TENANT_ID } from "../constants" +import { doWithDB } from "../db" +import { DocumentTypes, StaticDatabases } from "../db/constants" +import { getAllApps } from "../db/utils" +import environment from "../environment" +import { + doInTenant, + getTenantIds, + getGlobalDBName, + getTenantId, +} from "../tenancy" +import * as context from "../context" +import { DEFINITIONS } from "." +import { + Migration, + MigrationOptions, + MigrationType, + MigrationNoOpOptions, +} from "@budibase/types" + +export const getMigrationsDoc = async (db: any) => { + // get the migrations doc + try { + return await db.get(DocumentTypes.MIGRATIONS) + } catch (err: any) { + if (err.status && err.status === 404) { + return { _id: DocumentTypes.MIGRATIONS } + } else { + console.error(err) + throw err + } + } +} + +export const backPopulateMigrations = async (opts: MigrationNoOpOptions) => { + // filter migrations to the type and populate a no-op migration + const migrations: Migration[] = DEFINITIONS.filter( + def => def.type === opts.type + ).map(d => ({ ...d, fn: () => {} })) + await runMigrations(migrations, { noOp: opts }) +} + +export const runMigration = async ( + migration: Migration, + options: MigrationOptions = {} +) => { + const migrationType = migration.type + let tenantId: string + if (migrationType !== MigrationType.INSTALLATION) { + tenantId = getTenantId() + } + const migrationName = migration.name + const silent = migration.silent + + const log = (message: string) => { + if (!silent) { + console.log(message) + } + } + + // get the db to store the migration in + let dbNames + if (migrationType === MigrationType.GLOBAL) { + dbNames = [getGlobalDBName()] + } else if (migrationType === MigrationType.APP) { + if (options.noOp) { + dbNames = [options.noOp.appId] + } else { + const apps = await getAllApps(migration.appOpts) + dbNames = apps.map(app => app.appId) + } + } else if (migrationType === MigrationType.INSTALLATION) { + dbNames = [StaticDatabases.PLATFORM_INFO.name] + } else { + throw new Error(`Unrecognised migration type [${migrationType}]`) + } + + const length = dbNames.length + let count = 0 + + // run the migration against each db + for (const dbName of dbNames) { + count++ + const lengthStatement = length > 1 ? `[${count}/${length}]` : "" + + await doWithDB(dbName, async (db: any) => { + try { + const doc = await exports.getMigrationsDoc(db) + + // the migration has already been run + if (doc[migrationName]) { + // check for force + if ( + options.force && + options.force[migrationType] && + options.force[migrationType].includes(migrationName) + ) { + log( + `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Forcing` + ) + } else { + // no force, exit + return + } + } + + // check if the migration is not a no-op + if (!options.noOp) { + log( + `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Running ${lengthStatement}` + ) + + if (migration.preventRetry) { + // eagerly set the completion date + // so that we never run this migration twice even upon failure + doc[migrationName] = Date.now() + const response = await db.put(doc) + doc._rev = response.rev + } + + // run the migration + if (migrationType === MigrationType.APP) { + await context.doInAppContext(db.name, async () => { + await migration.fn(db) + }) + } else { + await migration.fn(db) + } + + log( + `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Complete` + ) + } + + // mark as complete + doc[migrationName] = Date.now() + await db.put(doc) + } catch (err) { + console.error( + `[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Error: `, + err + ) + throw err + } + }) + } +} + +export const runMigrations = async ( + migrations: Migration[], + options: MigrationOptions = {} +) => { + let tenantIds + + if (environment.MULTI_TENANCY) { + if (options.noOp) { + tenantIds = [options.noOp.tenantId] + } else if (!options.tenantIds || !options.tenantIds.length) { + // run for all tenants + tenantIds = await getTenantIds() + } else { + tenantIds = options.tenantIds + } + } else { + // single tenancy + tenantIds = [DEFAULT_TENANT_ID] + } + + if (tenantIds.length > 1) { + console.log(`Checking migrations for ${tenantIds.length} tenants`) + } else { + console.log("Checking migrations") + } + + let count = 0 + // for all tenants + for (const tenantId of tenantIds) { + count++ + if (tenantIds.length > 1) { + console.log(`Progress [${count}/${tenantIds.length}]`) + } + // for all migrations + for (const migration of migrations) { + // run the migration + await doInTenant(tenantId, () => runMigration(migration, options)) + } + } + console.log("Migrations complete") +} diff --git a/packages/backend-core/src/migrations/tests/__snapshots__/index.spec.js.snap b/packages/backend-core/src/migrations/tests/__snapshots__/index.spec.js.snap index 222c3b1228..532b5a32db 100644 --- a/packages/backend-core/src/migrations/tests/__snapshots__/index.spec.js.snap +++ b/packages/backend-core/src/migrations/tests/__snapshots__/index.spec.js.snap @@ -3,7 +3,9 @@ exports[`migrations should match snapshot 1`] = ` Object { "_id": "migrations", - "_rev": "1-6277abc4e3db950221768e5a2618a059", - "test": 1487076708000, + "_rev": "1-a32b0b708e59eeb006ed5e063cfeb36a", + "createdAt": "2020-01-01T00:00:00.000Z", + "test": 1577836800000, + "updatedAt": "2020-01-01T00:00:00.000Z", } `; diff --git a/packages/backend-core/src/migrations/tests/index.spec.js b/packages/backend-core/src/migrations/tests/index.spec.js index 12a2e54cb3..c5ec143143 100644 --- a/packages/backend-core/src/migrations/tests/index.spec.js +++ b/packages/backend-core/src/migrations/tests/index.spec.js @@ -1,12 +1,10 @@ -require("../../tests/utilities/dbConfig") - +require("../../../tests/utilities/TestConfiguration") const { runMigrations, getMigrationsDoc } = require("../index") -const CouchDB = require("../../db").getCouch() +const { dangerousGetDB } = require("../../db") const { StaticDatabases, } = require("../../db/utils") -Date.now = jest.fn(() => 1487076708000) let db describe("migrations", () => { @@ -20,7 +18,7 @@ describe("migrations", () => { }] beforeEach(() => { - db = new CouchDB(StaticDatabases.GLOBAL.name) + db = dangerousGetDB(StaticDatabases.GLOBAL.name) }) afterEach(async () => { @@ -29,7 +27,7 @@ describe("migrations", () => { }) const migrate = () => { - return runMigrations(CouchDB, MIGRATIONS) + return runMigrations(MIGRATIONS) } it("should run a new migration", async () => { diff --git a/packages/backend-core/src/objectStore/index.js b/packages/backend-core/src/objectStore/index.ts similarity index 68% rename from packages/backend-core/src/objectStore/index.js rename to packages/backend-core/src/objectStore/index.ts index 2385149f4d..a7e0b0c134 100644 --- a/packages/backend-core/src/objectStore/index.js +++ b/packages/backend-core/src/objectStore/index.ts @@ -1,16 +1,16 @@ const sanitize = require("sanitize-s3-objectkey") -const AWS = require("aws-sdk") -const stream = require("stream") -const fetch = require("node-fetch") -const tar = require("tar-fs") +import AWS from "aws-sdk" +import stream from "stream" +import fetch from "node-fetch" +import tar from "tar-fs" const zlib = require("zlib") -const { promisify } = require("util") -const { join } = require("path") -const fs = require("fs") -const env = require("../environment") -const { budibaseTempDir, ObjectStoreBuckets } = require("./utils") -const { v4 } = require("uuid") -const { APP_PREFIX, APP_DEV_PREFIX } = require("../db/utils") +import { promisify } from "util" +import { join } from "path" +import fs from "fs" +import env from "../environment" +import { budibaseTempDir, ObjectStoreBuckets } from "./utils" +import { v4 } from "uuid" +import { APP_PREFIX, APP_DEV_PREFIX } from "../db/utils" const streamPipeline = promisify(stream.pipeline) // use this as a temporary store of buckets that are being created @@ -18,7 +18,7 @@ const STATE = { bucketCreationPromises: {}, } -const CONTENT_TYPE_MAP = { +const CONTENT_TYPE_MAP: any = { html: "text/html", css: "text/css", js: "application/javascript", @@ -32,20 +32,16 @@ const STRING_CONTENT_TYPES = [ ] // does normal sanitization and then swaps dev apps to apps -function sanitizeKey(input) { +export function sanitizeKey(input: any) { return sanitize(sanitizeBucket(input)).replace(/\\/g, "/") } -exports.sanitizeKey = sanitizeKey - // simply handles the dev app to app conversion -function sanitizeBucket(input) { +export function sanitizeBucket(input: any) { return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX) } -exports.sanitizeBucket = sanitizeBucket - -function publicPolicy(bucketName) { +function publicPolicy(bucketName: any) { return { Version: "2012-10-17", Statement: [ @@ -69,13 +65,13 @@ const PUBLIC_BUCKETS = [ObjectStoreBuckets.APPS, ObjectStoreBuckets.GLOBAL] * @return {Object} an S3 object store object, check S3 Nodejs SDK for usage. * @constructor */ -exports.ObjectStore = bucket => { +export const ObjectStore = (bucket: any) => { AWS.config.update({ accessKeyId: env.MINIO_ACCESS_KEY, secretAccessKey: env.MINIO_SECRET_KEY, region: env.AWS_REGION, }) - const config = { + const config: any = { s3ForcePathStyle: true, signatureVersion: "v4", apiVersion: "2006-03-01", @@ -93,7 +89,7 @@ exports.ObjectStore = bucket => { * Given an object store and a bucket name this will make sure the bucket exists, * if it does not exist then it will create it. */ -exports.makeSureBucketExists = async (client, bucketName) => { +export const makeSureBucketExists = async (client: any, bucketName: any) => { bucketName = sanitizeBucket(bucketName) try { await client @@ -101,8 +97,8 @@ exports.makeSureBucketExists = async (client, bucketName) => { Bucket: bucketName, }) .promise() - } catch (err) { - const promises = STATE.bucketCreationPromises + } catch (err: any) { + const promises: any = STATE.bucketCreationPromises const doesntExist = err.statusCode === 404, noAccess = err.statusCode === 403 if (promises[bucketName]) { @@ -138,20 +134,20 @@ exports.makeSureBucketExists = async (client, bucketName) => { * Uploads the contents of a file given the required parameters, useful when * temp files in use (for example file uploaded as an attachment). */ -exports.upload = async ({ +export const upload = async ({ bucket: bucketName, filename, path, type, metadata, -}) => { +}: any) => { const extension = [...filename.split(".")].pop() const fileBytes = fs.readFileSync(path) - const objectStore = exports.ObjectStore(bucketName) - await exports.makeSureBucketExists(objectStore, bucketName) + const objectStore = ObjectStore(bucketName) + await makeSureBucketExists(objectStore, bucketName) - const config = { + const config: any = { // windows file paths need to be converted to forward slashes for s3 Key: sanitizeKey(filename), Body: fileBytes, @@ -167,9 +163,14 @@ exports.upload = async ({ * Similar to the upload function but can be used to send a file stream * through to the object store. */ -exports.streamUpload = async (bucketName, filename, stream, extra = {}) => { - const objectStore = exports.ObjectStore(bucketName) - await exports.makeSureBucketExists(objectStore, bucketName) +export const streamUpload = async ( + bucketName: any, + filename: any, + stream: any, + extra = {} +) => { + const objectStore = ObjectStore(bucketName) + await makeSureBucketExists(objectStore, bucketName) const params = { Bucket: sanitizeBucket(bucketName), @@ -184,13 +185,13 @@ exports.streamUpload = async (bucketName, filename, stream, extra = {}) => { * retrieves the contents of a file from the object store, if it is a known content type it * will be converted, otherwise it will be returned as a buffer stream. */ -exports.retrieve = async (bucketName, filepath) => { - const objectStore = exports.ObjectStore(bucketName) +export const retrieve = async (bucketName: any, filepath: any) => { + const objectStore = ObjectStore(bucketName) const params = { Bucket: sanitizeBucket(bucketName), Key: sanitizeKey(filepath), } - const response = await objectStore.getObject(params).promise() + const response: any = await objectStore.getObject(params).promise() // currently these are all strings if (STRING_CONTENT_TYPES.includes(response.ContentType)) { return response.Body.toString("utf8") @@ -202,10 +203,10 @@ exports.retrieve = async (bucketName, filepath) => { /** * Same as retrieval function but puts to a temporary file. */ -exports.retrieveToTmp = async (bucketName, filepath) => { +export const retrieveToTmp = async (bucketName: any, filepath: any) => { bucketName = sanitizeBucket(bucketName) filepath = sanitizeKey(filepath) - const data = await exports.retrieve(bucketName, filepath) + const data = await retrieve(bucketName, filepath) const outputPath = join(budibaseTempDir(), v4()) fs.writeFileSync(outputPath, data) return outputPath @@ -214,9 +215,9 @@ exports.retrieveToTmp = async (bucketName, filepath) => { /** * Delete a single file. */ -exports.deleteFile = async (bucketName, filepath) => { - const objectStore = exports.ObjectStore(bucketName) - await exports.makeSureBucketExists(objectStore, bucketName) +export const deleteFile = async (bucketName: any, filepath: any) => { + const objectStore = ObjectStore(bucketName) + await makeSureBucketExists(objectStore, bucketName) const params = { Bucket: bucketName, Key: filepath, @@ -224,13 +225,13 @@ exports.deleteFile = async (bucketName, filepath) => { return objectStore.deleteObject(params) } -exports.deleteFiles = async (bucketName, filepaths) => { - const objectStore = exports.ObjectStore(bucketName) - await exports.makeSureBucketExists(objectStore, bucketName) +export const deleteFiles = async (bucketName: any, filepaths: any) => { + const objectStore = ObjectStore(bucketName) + await makeSureBucketExists(objectStore, bucketName) const params = { Bucket: bucketName, Delete: { - Objects: filepaths.map(path => ({ Key: path })), + Objects: filepaths.map((path: any) => ({ Key: path })), }, } return objectStore.deleteObjects(params).promise() @@ -239,38 +240,45 @@ exports.deleteFiles = async (bucketName, filepaths) => { /** * Delete a path, including everything within. */ -exports.deleteFolder = async (bucketName, folder) => { +export const deleteFolder = async ( + bucketName: any, + folder: any +): Promise => { bucketName = sanitizeBucket(bucketName) folder = sanitizeKey(folder) - const client = exports.ObjectStore(bucketName) + const client = ObjectStore(bucketName) const listParams = { Bucket: bucketName, Prefix: folder, } - let response = await client.listObjects(listParams).promise() + let response: any = await client.listObjects(listParams).promise() if (response.Contents.length === 0) { return } - const deleteParams = { + const deleteParams: any = { Bucket: bucketName, Delete: { Objects: [], }, } - response.Contents.forEach(content => { + response.Contents.forEach((content: any) => { deleteParams.Delete.Objects.push({ Key: content.Key }) }) response = await client.deleteObjects(deleteParams).promise() // can only empty 1000 items at once if (response.Deleted.length === 1000) { - return exports.deleteFolder(bucketName, folder) + return deleteFolder(bucketName, folder) } } -exports.uploadDirectory = async (bucketName, localPath, bucketPath) => { +export const uploadDirectory = async ( + bucketName: any, + localPath: any, + bucketPath: any +) => { bucketName = sanitizeBucket(bucketName) let uploads = [] const files = fs.readdirSync(localPath, { withFileTypes: true }) @@ -278,17 +286,25 @@ exports.uploadDirectory = async (bucketName, localPath, bucketPath) => { const path = sanitizeKey(join(bucketPath, file.name)) const local = join(localPath, file.name) if (file.isDirectory()) { - uploads.push(exports.uploadDirectory(bucketName, local, path)) + uploads.push(uploadDirectory(bucketName, local, path)) } else { - uploads.push( - exports.streamUpload(bucketName, path, fs.createReadStream(local)) - ) + uploads.push(streamUpload(bucketName, path, fs.createReadStream(local))) } } await Promise.all(uploads) } -exports.downloadTarball = async (url, bucketName, path) => { +exports.downloadTarballDirect = async (url: string, path: string) => { + path = sanitizeKey(path) + const response = await fetch(url) + if (!response.ok) { + throw new Error(`unexpected response ${response.statusText}`) + } + + await streamPipeline(response.body, zlib.Unzip(), tar.extract(path)) +} + +export const downloadTarball = async (url: any, bucketName: any, path: any) => { bucketName = sanitizeBucket(bucketName) path = sanitizeKey(path) const response = await fetch(url) @@ -299,7 +315,7 @@ exports.downloadTarball = async (url, bucketName, path) => { const tmpPath = join(budibaseTempDir(), path) await streamPipeline(response.body, zlib.Unzip(), tar.extract(tmpPath)) if (!env.isTest() && env.SELF_HOSTED) { - await exports.uploadDirectory(bucketName, tmpPath, path) + await uploadDirectory(bucketName, tmpPath, path) } // return the temporary path incase there is a use for it return tmpPath diff --git a/packages/backend-core/src/objectStore/utils.js b/packages/backend-core/src/objectStore/utils.js index 1634a24981..a243553df8 100644 --- a/packages/backend-core/src/objectStore/utils.js +++ b/packages/backend-core/src/objectStore/utils.js @@ -1,12 +1,13 @@ const { join } = require("path") const { tmpdir } = require("os") +const env = require("../environment") exports.ObjectStoreBuckets = { - BACKUPS: "backups", - APPS: "prod-budi-app-assets", - TEMPLATES: "templates", - GLOBAL: "global", - GLOBAL_CLOUD: "prod-budi-tenant-uploads", + BACKUPS: env.BACKUPS_BUCKET_NAME, + APPS: env.APPS_BUCKET_NAME, + TEMPLATES: env.TEMPLATES_BUCKET_NAME, + GLOBAL: env.GLOBAL_BUCKET_NAME, + GLOBAL_CLOUD: env.GLOBAL_CLOUD_BUCKET_NAME, } exports.budibaseTempDir = function () { diff --git a/packages/backend-core/src/pkg/cache.ts b/packages/backend-core/src/pkg/cache.ts new file mode 100644 index 0000000000..1aaa40370d --- /dev/null +++ b/packages/backend-core/src/pkg/cache.ts @@ -0,0 +1,11 @@ +// Mimic the outer package export for usage in index.ts +// The outer exports can't be used as they now reference dist directly +import * as generic from "../cache/generic" +import * as user from "../cache/user" +import * as app from "../cache/appMetadata" + +export = { + app, + user, + ...generic, +} diff --git a/packages/backend-core/src/pkg/context.ts b/packages/backend-core/src/pkg/context.ts new file mode 100644 index 0000000000..5caa82ab0c --- /dev/null +++ b/packages/backend-core/src/pkg/context.ts @@ -0,0 +1,24 @@ +// Mimic the outer package export for usage in index.ts +// The outer exports can't be used as they now reference dist directly +import { + getAppDB, + getDevAppDB, + getProdAppDB, + getAppId, + updateAppId, + doInAppContext, + doInTenant, +} from "../context" + +import * as identity from "../context/identity" + +export = { + getAppDB, + getDevAppDB, + getProdAppDB, + getAppId, + updateAppId, + doInAppContext, + doInTenant, + identity, +} diff --git a/packages/backend-core/src/pkg/db.ts b/packages/backend-core/src/pkg/db.ts new file mode 100644 index 0000000000..0254adddd5 --- /dev/null +++ b/packages/backend-core/src/pkg/db.ts @@ -0,0 +1,7 @@ +// Mimic the outer package export for usage in index.ts +// The outer exports can't be used as they now reference dist directly +export * from "../db" +export * from "../db/utils" +export * from "../db/views" +export * from "../db/pouch" +export * from "../db/constants" diff --git a/packages/backend-core/src/pkg/objectStore.ts b/packages/backend-core/src/pkg/objectStore.ts new file mode 100644 index 0000000000..0447c6b3c2 --- /dev/null +++ b/packages/backend-core/src/pkg/objectStore.ts @@ -0,0 +1,4 @@ +// Mimic the outer package export for usage in index.ts +// The outer exports can't be used as they now reference dist directly +export * from "../objectStore" +export * from "../objectStore/utils" diff --git a/packages/backend-core/src/pkg/redis.ts b/packages/backend-core/src/pkg/redis.ts new file mode 100644 index 0000000000..65ab186d9a --- /dev/null +++ b/packages/backend-core/src/pkg/redis.ts @@ -0,0 +1,11 @@ +// Mimic the outer package export for usage in index.ts +// The outer exports can't be used as they now reference dist directly +import Client from "../redis" +import utils from "../redis/utils" +import clients from "../redis/init" + +export = { + Client, + utils, + clients, +} diff --git a/packages/backend-core/src/pkg/utils.ts b/packages/backend-core/src/pkg/utils.ts new file mode 100644 index 0000000000..5272046524 --- /dev/null +++ b/packages/backend-core/src/pkg/utils.ts @@ -0,0 +1,4 @@ +// Mimic the outer package export for usage in index.ts +// The outer exports can't be used as they now reference dist directly +export * from "../utils" +export * from "../hashing" diff --git a/packages/backend-core/src/redis/authRedis.js b/packages/backend-core/src/redis/authRedis.js deleted file mode 100644 index ca5c9bae37..0000000000 --- a/packages/backend-core/src/redis/authRedis.js +++ /dev/null @@ -1,37 +0,0 @@ -const Client = require("./index") -const utils = require("./utils") - -let userClient, sessionClient, appClient - -async function init() { - userClient = await new Client(utils.Databases.USER_CACHE).init() - sessionClient = await new Client(utils.Databases.SESSIONS).init() - appClient = await new Client(utils.Databases.APP_METADATA).init() -} - -process.on("exit", async () => { - if (userClient) await userClient.finish() - if (sessionClient) await sessionClient.finish() - if (appClient) await appClient.finish() -}) - -module.exports = { - getUserClient: async () => { - if (!userClient) { - await init() - } - return userClient - }, - getSessionClient: async () => { - if (!sessionClient) { - await init() - } - return sessionClient - }, - getAppClient: async () => { - if (!appClient) { - await init() - } - return appClient - }, -} diff --git a/packages/backend-core/src/redis/index.js b/packages/backend-core/src/redis/index.ts similarity index 54% rename from packages/backend-core/src/redis/index.js rename to packages/backend-core/src/redis/index.ts index 0ee17265ce..206110366f 100644 --- a/packages/backend-core/src/redis/index.js +++ b/packages/backend-core/src/redis/index.ts @@ -1,3 +1,4 @@ +import RedisWrapper from "../redis" const env = require("../environment") // ioredis mock is all in memory const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") @@ -6,24 +7,34 @@ const { removeDbPrefix, getRedisOptions, SEPARATOR, + SelectableDatabases, } = require("./utils") const RETRY_PERIOD_MS = 2000 const STARTUP_TIMEOUT_MS = 5000 const CLUSTERED = false +const DEFAULT_SELECT_DB = SelectableDatabases.DEFAULT // for testing just generate the client once let CLOSED = false -let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null +let CLIENTS: { [key: number]: any } = {} // if in test always connected -let CONNECTED = !!env.isTest() +let CONNECTED = env.isTest() -function connectionError(timeout, err) { +function pickClient(selectDb: number): any { + return CLIENTS[selectDb] +} + +function connectionError( + selectDb: number, + timeout: NodeJS.Timeout, + err: Error | string +) { // manually shut down, ignore errors if (CLOSED) { return } - CLIENT.end() + pickClient(selectDb).disconnect() CLOSED = true // always clear this on error clearTimeout(timeout) @@ -38,59 +49,69 @@ function connectionError(timeout, err) { * Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise * will return the ioredis client which will be ready to use. */ -function init() { - let timeout +function init(selectDb = DEFAULT_SELECT_DB) { + let timeout: NodeJS.Timeout CLOSED = false - // testing uses a single in memory client - if (env.isTest() || (CLIENT && CONNECTED)) { + let client = pickClient(selectDb) + // already connected, ignore + if (client && CONNECTED) { return } + // testing uses a single in memory client + if (env.isTest()) { + CLIENTS[selectDb] = new Redis(getRedisOptions()) + } // start the timer - only allowed 5 seconds to connect timeout = setTimeout(() => { if (!CONNECTED) { - connectionError(timeout, "Did not successfully connect in timeout") + connectionError( + selectDb, + timeout, + "Did not successfully connect in timeout" + ) } }, STARTUP_TIMEOUT_MS) // disconnect any lingering client - if (CLIENT) { - CLIENT.disconnect() + if (client) { + client.disconnect() } const { redisProtocolUrl, opts, host, port } = getRedisOptions(CLUSTERED) if (CLUSTERED) { - CLIENT = new Redis.Cluster([{ host, port }], opts) + client = new Redis.Cluster([{ host, port }], opts) } else if (redisProtocolUrl) { - CLIENT = new Redis(redisProtocolUrl) + client = new Redis(redisProtocolUrl) } else { - CLIENT = new Redis(opts) + client = new Redis(opts) } // attach handlers - CLIENT.on("end", err => { - connectionError(timeout, err) + client.on("end", (err: Error) => { + connectionError(selectDb, timeout, err) }) - CLIENT.on("error", err => { - connectionError(timeout, err) + client.on("error", (err: Error) => { + connectionError(selectDb, timeout, err) }) - CLIENT.on("connect", () => { + client.on("connect", () => { clearTimeout(timeout) CONNECTED = true }) + CLIENTS[selectDb] = client } -function waitForConnection() { +function waitForConnection(selectDb: number = DEFAULT_SELECT_DB) { return new Promise(resolve => { - if (CLIENT == null) { + if (pickClient(selectDb) == null) { init() } else if (CONNECTED) { - resolve() + resolve("") return } // check if the connection is ready const interval = setInterval(() => { if (CONNECTED) { clearInterval(interval) - resolve() + resolve("") } }, 500) }) @@ -100,25 +121,26 @@ function waitForConnection() { * Utility function, takes a redis stream and converts it to a promisified response - * this can only be done with redis streams because they will have an end. * @param stream A redis stream, specifically as this type of stream will have an end. + * @param client The client to use for further lookups. * @return {Promise} The final output of the stream */ -function promisifyStream(stream) { +function promisifyStream(stream: any, client: RedisWrapper) { return new Promise((resolve, reject) => { const outputKeys = new Set() - stream.on("data", keys => { + stream.on("data", (keys: string[]) => { keys.forEach(key => { outputKeys.add(key) }) }) - stream.on("error", err => { + stream.on("error", (err: Error) => { reject(err) }) stream.on("end", async () => { - const keysArray = Array.from(outputKeys) + const keysArray: string[] = Array.from(outputKeys) as string[] try { let getPromises = [] for (let key of keysArray) { - getPromises.push(CLIENT.get(key)) + getPromises.push(client.get(key)) } const jsonArray = await Promise.all(getPromises) resolve( @@ -134,39 +156,52 @@ function promisifyStream(stream) { }) } -class RedisWrapper { - constructor(db) { +export = class RedisWrapper { + _db: string + _select: number + + constructor(db: string, selectDb: number | null = null) { this._db = db + this._select = selectDb || DEFAULT_SELECT_DB + } + + getClient() { + return pickClient(this._select) } async init() { CLOSED = false - init() - await waitForConnection() + init(this._select) + await waitForConnection(this._select) return this } async finish() { CLOSED = true - CLIENT.disconnect() + this.getClient().disconnect() } - async scan(key = "") { + async scan(key = ""): Promise { const db = this._db key = `${db}${SEPARATOR}${key}` let stream if (CLUSTERED) { - let node = CLIENT.nodes("master") + let node = this.getClient().nodes("master") stream = node[0].scanStream({ match: key + "*", count: 100 }) } else { - stream = CLIENT.scanStream({ match: key + "*", count: 100 }) + stream = this.getClient().scanStream({ match: key + "*", count: 100 }) } - return promisifyStream(stream) + return promisifyStream(stream, this.getClient()) } - async get(key) { + async keys(pattern: string) { const db = this._db - let response = await CLIENT.get(addDbPrefix(db, key)) + return this.getClient().keys(addDbPrefix(db, pattern)) + } + + async get(key: string) { + const db = this._db + let response = await this.getClient().get(addDbPrefix(db, key)) // overwrite the prefixed key if (response != null && response.key) { response.key = key @@ -179,39 +214,37 @@ class RedisWrapper { } } - async store(key, value, expirySeconds = null) { + async store(key: string, value: any, expirySeconds: number | null = null) { const db = this._db if (typeof value === "object") { value = JSON.stringify(value) } const prefixedKey = addDbPrefix(db, key) - await CLIENT.set(prefixedKey, value) + await this.getClient().set(prefixedKey, value) if (expirySeconds) { - await CLIENT.expire(prefixedKey, expirySeconds) + await this.getClient().expire(prefixedKey, expirySeconds) } } - async getTTL(key) { + async getTTL(key: string) { const db = this._db const prefixedKey = addDbPrefix(db, key) - return CLIENT.ttl(prefixedKey) + return this.getClient().ttl(prefixedKey) } - async setExpiry(key, expirySeconds) { + async setExpiry(key: string, expirySeconds: number | null) { const db = this._db const prefixedKey = addDbPrefix(db, key) - await CLIENT.expire(prefixedKey, expirySeconds) + await this.getClient().expire(prefixedKey, expirySeconds) } - async delete(key) { + async delete(key: string) { const db = this._db - await CLIENT.del(addDbPrefix(db, key)) + await this.getClient().del(addDbPrefix(db, key)) } async clear() { let items = await this.scan() - await Promise.all(items.map(obj => this.delete(obj.key))) + await Promise.all(items.map((obj: any) => this.delete(obj.key))) } } - -module.exports = RedisWrapper diff --git a/packages/backend-core/src/redis/init.js b/packages/backend-core/src/redis/init.js new file mode 100644 index 0000000000..8e5d10f838 --- /dev/null +++ b/packages/backend-core/src/redis/init.js @@ -0,0 +1,72 @@ +const Client = require("./index") +const utils = require("./utils") +const { getRedlock } = require("./redlock") + +let userClient, sessionClient, appClient, cacheClient, writethroughClient +let migrationsRedlock + +// turn retry off so that only one instance can ever hold the lock +const migrationsRedlockConfig = { retryCount: 0 } + +async function init() { + userClient = await new Client(utils.Databases.USER_CACHE).init() + sessionClient = await new Client(utils.Databases.SESSIONS).init() + appClient = await new Client(utils.Databases.APP_METADATA).init() + cacheClient = await new Client(utils.Databases.GENERIC_CACHE).init() + writethroughClient = await new Client( + utils.Databases.WRITE_THROUGH, + utils.SelectableDatabases.WRITE_THROUGH + ).init() + // pass the underlying ioredis client to redlock + migrationsRedlock = getRedlock( + cacheClient.getClient(), + migrationsRedlockConfig + ) +} + +process.on("exit", async () => { + if (userClient) await userClient.finish() + if (sessionClient) await sessionClient.finish() + if (appClient) await appClient.finish() + if (cacheClient) await cacheClient.finish() + if (writethroughClient) await writethroughClient.finish() +}) + +module.exports = { + getUserClient: async () => { + if (!userClient) { + await init() + } + return userClient + }, + getSessionClient: async () => { + if (!sessionClient) { + await init() + } + return sessionClient + }, + getAppClient: async () => { + if (!appClient) { + await init() + } + return appClient + }, + getCacheClient: async () => { + if (!cacheClient) { + await init() + } + return cacheClient + }, + getWritethroughClient: async () => { + if (!writethroughClient) { + await init() + } + return writethroughClient + }, + getMigrationsRedlock: async () => { + if (!migrationsRedlock) { + await init() + } + return migrationsRedlock + }, +} diff --git a/packages/backend-core/src/redis/redlock.ts b/packages/backend-core/src/redis/redlock.ts new file mode 100644 index 0000000000..beef375b55 --- /dev/null +++ b/packages/backend-core/src/redis/redlock.ts @@ -0,0 +1,21 @@ +import Redlock from "redlock" + +export const getRedlock = (redisClient: any, opts = { retryCount: 10 }) => { + return new Redlock([redisClient], { + // the expected clock drift; for more details + // see http://redis.io/topics/distlock + driftFactor: 0.01, // multiplied by lock ttl to determine drift time + + // the max number of times Redlock will attempt + // to lock a resource before erroring + retryCount: opts.retryCount, + + // the time in ms between attempts + retryDelay: 200, // time in ms + + // the max time in ms randomly added to retries + // to improve performance under high contention + // see https://www.awsarchitectureblog.com/2015/03/backoff.html + retryJitter: 200, // time in ms + }) +} diff --git a/packages/backend-core/src/redis/utils.js b/packages/backend-core/src/redis/utils.js index 4c2b2f5cae..90b3561f31 100644 --- a/packages/backend-core/src/redis/utils.js +++ b/packages/backend-core/src/redis/utils.js @@ -6,6 +6,14 @@ const SEPARATOR = "-" const REDIS_URL = !env.REDIS_URL ? "localhost:6379" : env.REDIS_URL const REDIS_PASSWORD = !env.REDIS_PASSWORD ? "budibase" : env.REDIS_PASSWORD +/** + * These Redis databases help us to segment up a Redis keyspace by prepending the + * specified database name onto the cache key. This means that a single real Redis database + * can be split up a bit; allowing us to use scans on small databases to find some particular + * keys within. + * If writing a very large volume of keys is expected (say 10K+) then it is better to keep these out + * of the default keyspace and use a separate one - the SelectableDatabases can be used for this. + */ exports.Databases = { PW_RESETS: "pwReset", VERIFICATIONS: "verification", @@ -17,6 +25,37 @@ exports.Databases = { FLAGS: "flags", APP_METADATA: "appMetadata", QUERY_VARS: "queryVars", + LICENSES: "license", + GENERIC_CACHE: "data_cache", + WRITE_THROUGH: "writeThrough", +} + +/** + * These define the numeric Redis databases that can be access with the SELECT command - + * (https://redis.io/commands/select/). By default a Redis server/cluster will have 16 selectable + * databases, increasing this count increases the amount of CPU/memory required to run the server. + * Ideally new Redis keyspaces should be used sparingly, only when absolutely necessary for performance + * to be maintained. Generally a keyspace can grow to be very large is scans are not needed or desired, + * but if you need to walk through all values in a database periodically then a separate selectable + * keyspace should be used. + */ +exports.SelectableDatabases = { + DEFAULT: 0, + WRITE_THROUGH: 1, + UNUSED_1: 2, + UNUSED_2: 3, + UNUSED_3: 4, + UNUSED_4: 5, + UNUSED_5: 6, + UNUSED_6: 7, + UNUSED_7: 8, + UNUSED_8: 9, + UNUSED_9: 10, + UNUSED_10: 11, + UNUSED_11: 12, + UNUSED_12: 13, + UNUSED_13: 14, + UNUSED_14: 15, } exports.SEPARATOR = SEPARATOR diff --git a/packages/backend-core/src/security/permissions.js b/packages/backend-core/src/security/permissions.js index 28044a5129..2ecb8a9f1e 100644 --- a/packages/backend-core/src/security/permissions.js +++ b/packages/backend-core/src/security/permissions.js @@ -96,6 +96,7 @@ const BUILTIN_PERMISSIONS = { new Permission(PermissionTypes.QUERY, PermissionLevels.WRITE), new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE), new Permission(PermissionTypes.VIEW, PermissionLevels.READ), + new Permission(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), ], }, POWER: { diff --git a/packages/backend-core/src/security/roles.js b/packages/backend-core/src/security/roles.js index 8535cdc716..7c57cadcbf 100644 --- a/packages/backend-core/src/security/roles.js +++ b/packages/backend-core/src/security/roles.js @@ -7,7 +7,7 @@ const { SEPARATOR, } = require("../db/utils") const { getAppDB } = require("../context") -const { getDB } = require("../db") +const { doWithDB } = require("../db") const BUILTIN_IDS = { ADMIN: "ADMIN", @@ -199,43 +199,49 @@ exports.checkForRoleResourceArray = (rolePerms, resourceId) => { * @return {Promise} An array of the role objects that were found. */ exports.getAllRoles = async appId => { - const db = appId ? getDB(appId) : getAppDB() - const body = await db.allDocs( - getRoleParams(null, { - include_docs: true, - }) - ) - let roles = body.rows.map(row => row.doc) - const builtinRoles = exports.getBuiltinRoles() + if (appId) { + return doWithDB(appId, internal) + } else { + return internal(getAppDB()) + } + async function internal(db) { + const body = await db.allDocs( + getRoleParams(null, { + include_docs: true, + }) + ) + let roles = body.rows.map(row => row.doc) + const builtinRoles = exports.getBuiltinRoles() - // need to combine builtin with any DB record of them (for sake of permissions) - for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) { - const builtinRole = builtinRoles[builtinRoleId] - const dbBuiltin = roles.filter( - dbRole => exports.getExternalRoleID(dbRole._id) === builtinRoleId - )[0] - if (dbBuiltin == null) { - roles.push(builtinRole || builtinRoles.BASIC) - } else { - // remove role and all back after combining with the builtin - roles = roles.filter(role => role._id !== dbBuiltin._id) - dbBuiltin._id = exports.getExternalRoleID(dbBuiltin._id) - roles.push(Object.assign(builtinRole, dbBuiltin)) + // need to combine builtin with any DB record of them (for sake of permissions) + for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) { + const builtinRole = builtinRoles[builtinRoleId] + const dbBuiltin = roles.filter( + dbRole => exports.getExternalRoleID(dbRole._id) === builtinRoleId + )[0] + if (dbBuiltin == null) { + roles.push(builtinRole || builtinRoles.BASIC) + } else { + // remove role and all back after combining with the builtin + roles = roles.filter(role => role._id !== dbBuiltin._id) + dbBuiltin._id = exports.getExternalRoleID(dbBuiltin._id) + roles.push(Object.assign(builtinRole, dbBuiltin)) + } } + // check permissions + for (let role of roles) { + if (!role.permissions) { + continue + } + for (let resourceId of Object.keys(role.permissions)) { + role.permissions = exports.checkForRoleResourceArray( + role.permissions, + resourceId + ) + } + } + return roles } - // check permissions - for (let role of roles) { - if (!role.permissions) { - continue - } - for (let resourceId of Object.keys(role.permissions)) { - role.permissions = exports.checkForRoleResourceArray( - role.permissions, - resourceId - ) - } - } - return roles } /** diff --git a/packages/backend-core/src/security/sessions.js b/packages/backend-core/src/security/sessions.js index bbe6be299d..8874b47469 100644 --- a/packages/backend-core/src/security/sessions.js +++ b/packages/backend-core/src/security/sessions.js @@ -1,4 +1,4 @@ -const redis = require("../redis/authRedis") +const redis = require("../redis/init") const { v4: uuidv4 } = require("uuid") // a week in seconds @@ -14,7 +14,40 @@ function makeSessionID(userId, sessionId) { return `${userId}/${sessionId}` } +async function invalidateSessions(userId, sessionIds = null) { + try { + let sessions = [] + + // If no sessionIds, get all the sessions for the user + if (!sessionIds) { + sessions = await getSessionsForUser(userId) + sessions.forEach( + session => + (session.key = makeSessionID(session.userId, session.sessionId)) + ) + } else { + // use the passed array of sessionIds + sessions = Array.isArray(sessionIds) ? sessionIds : [sessionIds] + sessions = sessions.map(sessionId => ({ + key: makeSessionID(userId, sessionId), + })) + } + + const client = await redis.getSessionClient() + const promises = [] + for (let session of sessions) { + promises.push(client.delete(session.key)) + } + await Promise.all(promises) + } catch (err) { + console.error(`Error invalidating sessions: ${err}`) + } +} + exports.createASession = async (userId, session) => { + // invalidate all other sessions + await invalidateSessions(userId) + const client = await redis.getSessionClient() const sessionId = session.sessionId if (!session.csrfToken) { @@ -29,32 +62,6 @@ exports.createASession = async (userId, session) => { await client.store(makeSessionID(userId, sessionId), session, EXPIRY_SECONDS) } -exports.invalidateSessions = async (userId, sessionIds = null) => { - let sessions = [] - - // If no sessionIds, get all the sessions for the user - if (!sessionIds) { - sessions = await getSessionsForUser(userId) - sessions.forEach( - session => - (session.key = makeSessionID(session.userId, session.sessionId)) - ) - } else { - // use the passed array of sessionIds - sessions = Array.isArray(sessionIds) ? sessionIds : [sessionIds] - sessions = sessions.map(sessionId => ({ - key: makeSessionID(userId, sessionId), - })) - } - - const client = await redis.getSessionClient() - const promises = [] - for (let session of sessions) { - promises.push(client.delete(session.key)) - } - await Promise.all(promises) -} - exports.updateSessionTTL = async session => { const client = await redis.getSessionClient() const key = makeSessionID(session.userId, session.sessionId) @@ -67,14 +74,13 @@ exports.endSession = async (userId, sessionId) => { await client.delete(makeSessionID(userId, sessionId)) } -exports.getUserSessions = getSessionsForUser - exports.getSession = async (userId, sessionId) => { try { const client = await redis.getSessionClient() return client.get(makeSessionID(userId, sessionId)) } catch (err) { // if can't get session don't error, just don't return anything + console.error(err) return null } } @@ -84,3 +90,6 @@ exports.getAllSessions = async () => { const sessions = await client.scan() return sessions.map(session => session.value) } + +exports.getUserSessions = getSessionsForUser +exports.invalidateSessions = invalidateSessions diff --git a/packages/backend-core/src/tenancy/index.js b/packages/backend-core/src/tenancy/index.js deleted file mode 100644 index c847033a12..0000000000 --- a/packages/backend-core/src/tenancy/index.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - ...require("../context"), - ...require("./tenancy"), -} diff --git a/packages/backend-core/src/tenancy/index.ts b/packages/backend-core/src/tenancy/index.ts new file mode 100644 index 0000000000..e0006abab2 --- /dev/null +++ b/packages/backend-core/src/tenancy/index.ts @@ -0,0 +1,9 @@ +import * as context from "../context" +import * as tenancy from "./tenancy" + +const pkg = { + ...context, + ...tenancy, +} + +export = pkg diff --git a/packages/backend-core/src/tenancy/tenancy.js b/packages/backend-core/src/tenancy/tenancy.js deleted file mode 100644 index 24acc16862..0000000000 --- a/packages/backend-core/src/tenancy/tenancy.js +++ /dev/null @@ -1,147 +0,0 @@ -const { getDB } = require("../db") -const { SEPARATOR, StaticDatabases } = require("../db/constants") -const { - getTenantId, - DEFAULT_TENANT_ID, - isMultiTenant, - getTenantIDFromAppID, -} = require("../context") -const env = require("../environment") - -const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants -const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name - -exports.addTenantToUrl = url => { - const tenantId = getTenantId() - - if (isMultiTenant()) { - const char = url.indexOf("?") === -1 ? "?" : "&" - url += `${char}tenantId=${tenantId}` - } - - return url -} - -exports.doesTenantExist = async tenantId => { - const db = getDB(PLATFORM_INFO_DB) - let tenants - try { - tenants = await db.get(TENANT_DOC) - } catch (err) { - // if theres an error the doc doesn't exist, no tenants exist - return false - } - return ( - tenants && - Array.isArray(tenants.tenantIds) && - tenants.tenantIds.indexOf(tenantId) !== -1 - ) -} - -exports.tryAddTenant = async (tenantId, userId, email) => { - const db = getDB(PLATFORM_INFO_DB) - const getDoc = async id => { - if (!id) { - return null - } - try { - return await db.get(id) - } catch (err) { - return { _id: id } - } - } - let [tenants, userIdDoc, emailDoc] = await Promise.all([ - getDoc(TENANT_DOC), - getDoc(userId), - getDoc(email), - ]) - if (!Array.isArray(tenants.tenantIds)) { - tenants = { - _id: TENANT_DOC, - tenantIds: [], - } - } - let promises = [] - if (userIdDoc) { - userIdDoc.tenantId = tenantId - promises.push(db.put(userIdDoc)) - } - if (emailDoc) { - emailDoc.tenantId = tenantId - emailDoc.userId = userId - promises.push(db.put(emailDoc)) - } - if (tenants.tenantIds.indexOf(tenantId) === -1) { - tenants.tenantIds.push(tenantId) - promises.push(db.put(tenants)) - } - await Promise.all(promises) -} - -exports.getGlobalDBName = (tenantId = null) => { - // tenant ID can be set externally, for example user API where - // new tenants are being created, this may be the case - if (!tenantId) { - tenantId = getTenantId() - } - - let dbName - if (tenantId === DEFAULT_TENANT_ID) { - dbName = StaticDatabases.GLOBAL.name - } else { - dbName = `${tenantId}${SEPARATOR}${StaticDatabases.GLOBAL.name}` - } - return dbName -} - -exports.getGlobalDB = (tenantId = null) => { - const dbName = exports.getGlobalDBName(tenantId) - return getDB(dbName) -} - -exports.lookupTenantId = async userId => { - const db = getDB(StaticDatabases.PLATFORM_INFO.name) - let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null - try { - const doc = await db.get(userId) - if (doc && doc.tenantId) { - tenantId = doc.tenantId - } - } catch (err) { - // just return the default - } - return tenantId -} - -// lookup, could be email or userId, either will return a doc -exports.getTenantUser = async identifier => { - const db = getDB(PLATFORM_INFO_DB) - try { - return await db.get(identifier) - } catch (err) { - return null - } -} - -exports.isUserInAppTenant = (appId, user = null) => { - let userTenantId - if (user) { - userTenantId = user.tenantId || DEFAULT_TENANT_ID - } else { - userTenantId = getTenantId() - } - const tenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID - return tenantId === userTenantId -} - -exports.getTenantIds = async () => { - const db = getDB(PLATFORM_INFO_DB) - let tenants - try { - tenants = await db.get(TENANT_DOC) - } catch (err) { - // if theres an error the doc doesn't exist, no tenants exist - return [] - } - return (tenants && tenants.tenantIds) || [] -} diff --git a/packages/backend-core/src/tenancy/tenancy.ts b/packages/backend-core/src/tenancy/tenancy.ts new file mode 100644 index 0000000000..041f694d34 --- /dev/null +++ b/packages/backend-core/src/tenancy/tenancy.ts @@ -0,0 +1,151 @@ +import { doWithDB } from "../db" +import { StaticDatabases } from "../db/constants" +import { baseGlobalDBName } from "./utils" +import { + getTenantId, + DEFAULT_TENANT_ID, + isMultiTenant, + getTenantIDFromAppID, +} from "../context" +import env from "../environment" + +const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants +const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name + +export const addTenantToUrl = (url: string) => { + const tenantId = getTenantId() + + if (isMultiTenant()) { + const char = url.indexOf("?") === -1 ? "?" : "&" + url += `${char}tenantId=${tenantId}` + } + + return url +} + +export const doesTenantExist = async (tenantId: string) => { + return doWithDB(PLATFORM_INFO_DB, async (db: any) => { + let tenants + try { + tenants = await db.get(TENANT_DOC) + } catch (err) { + // if theres an error the doc doesn't exist, no tenants exist + return false + } + return ( + tenants && + Array.isArray(tenants.tenantIds) && + tenants.tenantIds.indexOf(tenantId) !== -1 + ) + }) +} + +export const tryAddTenant = async ( + tenantId: string, + userId: string, + email: string, + afterCreateTenant: () => Promise +) => { + return doWithDB(PLATFORM_INFO_DB, async (db: any) => { + const getDoc = async (id: string) => { + if (!id) { + return null + } + try { + return await db.get(id) + } catch (err) { + return { _id: id } + } + } + let [tenants, userIdDoc, emailDoc] = await Promise.all([ + getDoc(TENANT_DOC), + getDoc(userId), + getDoc(email), + ]) + if (!Array.isArray(tenants.tenantIds)) { + tenants = { + _id: TENANT_DOC, + tenantIds: [], + } + } + let promises = [] + if (userIdDoc) { + userIdDoc.tenantId = tenantId + promises.push(db.put(userIdDoc)) + } + if (emailDoc) { + emailDoc.tenantId = tenantId + emailDoc.userId = userId + promises.push(db.put(emailDoc)) + } + if (tenants.tenantIds.indexOf(tenantId) === -1) { + tenants.tenantIds.push(tenantId) + promises.push(db.put(tenants)) + await afterCreateTenant() + } + await Promise.all(promises) + }) +} + +export const getGlobalDBName = (tenantId?: string) => { + // tenant ID can be set externally, for example user API where + // new tenants are being created, this may be the case + if (!tenantId) { + tenantId = getTenantId() + } + return baseGlobalDBName(tenantId) +} + +export const doWithGlobalDB = (tenantId: string, cb: any) => { + return doWithDB(getGlobalDBName(tenantId), cb) +} + +export const lookupTenantId = async (userId: string) => { + return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => { + let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null + try { + const doc = await db.get(userId) + if (doc && doc.tenantId) { + tenantId = doc.tenantId + } + } catch (err) { + // just return the default + } + return tenantId + }) +} + +// lookup, could be email or userId, either will return a doc +export const getTenantUser = async (identifier: string) => { + return doWithDB(PLATFORM_INFO_DB, async (db: any) => { + try { + return await db.get(identifier) + } catch (err) { + return null + } + }) +} + +export const isUserInAppTenant = (appId: string, user: any) => { + let userTenantId + if (user) { + userTenantId = user.tenantId || DEFAULT_TENANT_ID + } else { + userTenantId = getTenantId() + } + const tenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID + return tenantId === userTenantId +} + +export const getTenantIds = async () => { + return doWithDB(PLATFORM_INFO_DB, async (db: any) => { + let tenants + try { + tenants = await db.get(TENANT_DOC) + } catch (err) { + // if theres an error the doc doesn't exist, no tenants exist + return [] + } + return (tenants && tenants.tenantIds) || [] + }) +} diff --git a/packages/backend-core/src/tenancy/utils.js b/packages/backend-core/src/tenancy/utils.js new file mode 100644 index 0000000000..70a965ddb7 --- /dev/null +++ b/packages/backend-core/src/tenancy/utils.js @@ -0,0 +1,12 @@ +const { DEFAULT_TENANT_ID } = require("../constants") +const { StaticDatabases, SEPARATOR } = require("../db/constants") + +exports.baseGlobalDBName = tenantId => { + let dbName + if (!tenantId || tenantId === DEFAULT_TENANT_ID) { + dbName = StaticDatabases.GLOBAL.name + } else { + dbName = `${tenantId}${SEPARATOR}${StaticDatabases.GLOBAL.name}` + } + return dbName +} diff --git a/packages/backend-core/src/tests/utilities/db.js b/packages/backend-core/src/tests/utilities/db.js deleted file mode 100644 index bb99592d1c..0000000000 --- a/packages/backend-core/src/tests/utilities/db.js +++ /dev/null @@ -1,17 +0,0 @@ -const PouchDB = require("pouchdb") -const env = require("../../environment") - -let POUCH_DB_DEFAULTS - -// should always be test but good to do the sanity check -if (env.isTest()) { - PouchDB.plugin(require("pouchdb-adapter-memory")) - POUCH_DB_DEFAULTS = { - prefix: undefined, - adapter: "memory", - } -} - -const Pouch = PouchDB.defaults(POUCH_DB_DEFAULTS) - -module.exports = Pouch diff --git a/packages/backend-core/src/tests/utilities/dbConfig.js b/packages/backend-core/src/tests/utilities/dbConfig.js deleted file mode 100644 index 45b9ff33f9..0000000000 --- a/packages/backend-core/src/tests/utilities/dbConfig.js +++ /dev/null @@ -1,3 +0,0 @@ -const packageConfiguration = require("../../index") -const CouchDB = require("./db") -packageConfiguration.init(CouchDB) diff --git a/packages/backend-core/src/tests/utils.spec.js b/packages/backend-core/src/tests/utils.spec.js new file mode 100644 index 0000000000..b487f23c6d --- /dev/null +++ b/packages/backend-core/src/tests/utils.spec.js @@ -0,0 +1,17 @@ +require("../../tests/utilities/TestConfiguration") +const { structures } = require("../../tests/utilities") +const utils = require("../utils") +const events = require("../events") +const { doInTenant, DEFAULT_TENANT_ID }= require("../context") + +describe("utils", () => { + describe("platformLogout", () => { + it("should call platform logout", async () => { + await doInTenant(DEFAULT_TENANT_ID, async () => { + const ctx = structures.koa.newContext() + await utils.platformLogout({ ctx, userId: "test" }) + expect(events.auth.logout).toBeCalledTimes(1) + }) + }) + }) +}) \ No newline at end of file diff --git a/packages/backend-core/src/users.js b/packages/backend-core/src/users.js new file mode 100644 index 0000000000..0c1350a674 --- /dev/null +++ b/packages/backend-core/src/users.js @@ -0,0 +1,43 @@ +const { ViewNames } = require("./db/utils") +const { queryGlobalView } = require("./db/views") +const { UNICODE_MAX } = require("./db/constants") + +/** + * Given an email address this will use a view to search through + * all the users to find one with this email address. + * @param {string} email the email to lookup the user by. + * @return {Promise} + */ +exports.getGlobalUserByEmail = async email => { + if (email == null) { + throw "Must supply an email address to view" + } + + const response = await queryGlobalView(ViewNames.USER_BY_EMAIL, { + key: email.toLowerCase(), + include_docs: true, + }) + + return response +} + +/** + * Performs a starts with search on the global email view. + */ +exports.searchGlobalUsersByEmail = async (email, opts) => { + if (typeof email !== "string") { + throw new Error("Must provide a string to search by") + } + const lcEmail = email.toLowerCase() + // handle if passing up startkey for pagination + const startkey = opts && opts.startkey ? opts.startkey : lcEmail + let response = await queryGlobalView(ViewNames.USER_BY_EMAIL, { + ...opts, + startkey, + endkey: `${lcEmail}${UNICODE_MAX}`, + }) + if (!response) { + response = [] + } + return Array.isArray(response) ? response : [response] +} diff --git a/packages/backend-core/src/utils.js b/packages/backend-core/src/utils.js index 8909f62995..cf32539c58 100644 --- a/packages/backend-core/src/utils.js +++ b/packages/backend-core/src/utils.js @@ -2,25 +2,16 @@ const { DocumentTypes, SEPARATOR, ViewNames, - generateGlobalUserID, getAllApps, } = require("./db/utils") const jwt = require("jsonwebtoken") const { options } = require("./middleware/passport/jwt") const { queryGlobalView } = require("./db/views") -const { Headers, UserStatus, Cookies, MAX_VALID_DATE } = require("./constants") -const { - getGlobalDB, - updateTenantId, - getTenantUser, - tryAddTenant, -} = require("./tenancy") -const environment = require("./environment") -const accounts = require("./cloud/accounts") -const { hash } = require("./hashing") -const userCache = require("./cache/user") +const { Headers, Cookies, MAX_VALID_DATE } = require("./constants") const env = require("./environment") +const userCache = require("./cache/user") const { getUserSessions, invalidateSessions } = require("./security/sessions") +const events = require("./events") const tenancy = require("./tenancy") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -135,8 +126,8 @@ exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => { overwrite: true, } - if (environment.COOKIE_DOMAIN) { - config.domain = environment.COOKIE_DOMAIN + if (env.COOKIE_DOMAIN) { + config.domain = env.COOKIE_DOMAIN } ctx.cookies.set(name, value, config) @@ -159,111 +150,25 @@ exports.isClient = ctx => { return ctx.headers[Headers.TYPE] === "client" } -/** - * Given an email address this will use a view to search through - * all the users to find one with this email address. - * @param {string} email the email to lookup the user by. - * @return {Promise} - */ -exports.getGlobalUserByEmail = async email => { - if (email == null) { - throw "Must supply an email address to view" +const getBuilders = async () => { + const builders = await queryGlobalView(ViewNames.USER_BY_BUILDERS, { + include_docs: false, + }) + + if (!builders) { + return [] } - return queryGlobalView(ViewNames.USER_BY_EMAIL, { - key: email.toLowerCase(), - include_docs: true, - }) + if (Array.isArray(builders)) { + return builders + } else { + return [builders] + } } -exports.saveUser = async ( - user, - tenantId, - hashPassword = true, - requirePassword = true -) => { - if (!tenantId) { - throw "No tenancy specified." - } - // need to set the context for this request, as specified - updateTenantId(tenantId) - // specify the tenancy incase we're making a new admin user (public) - const db = getGlobalDB(tenantId) - let { email, password, _id } = user - // make sure another user isn't using the same email - let dbUser - if (email) { - // check budibase users inside the tenant - dbUser = await exports.getGlobalUserByEmail(email) - if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) { - throw `Email address ${email} already in use.` - } - - // check budibase users in other tenants - if (env.MULTI_TENANCY) { - const tenantUser = await getTenantUser(email) - if (tenantUser != null && tenantUser.tenantId !== tenantId) { - throw `Email address ${email} already in use.` - } - } - - // check root account users in account portal - if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { - const account = await accounts.getAccount(email) - if (account && account.verified && account.tenantId !== tenantId) { - throw `Email address ${email} already in use.` - } - } - } else { - dbUser = await db.get(_id) - } - - // get the password, make sure one is defined - let hashedPassword - if (password) { - hashedPassword = hashPassword ? await hash(password) : password - } else if (dbUser) { - hashedPassword = dbUser.password - } else if (requirePassword) { - throw "Password must be specified." - } - - _id = _id || generateGlobalUserID() - user = { - createdAt: Date.now(), - ...dbUser, - ...user, - _id, - password: hashedPassword, - tenantId, - } - // make sure the roles object is always present - if (!user.roles) { - user.roles = {} - } - // add the active status to a user if its not provided - if (user.status == null) { - user.status = UserStatus.ACTIVE - } - try { - const response = await db.put({ - password: hashedPassword, - ...user, - }) - await tryAddTenant(tenantId, _id, email) - await userCache.invalidateUser(response.id) - return { - _id: response.id, - _rev: response.rev, - email, - } - } catch (err) { - if (err.status === 409) { - throw "User exists already" - } else { - throw err - } - } +exports.getBuildersCount = async () => { + const builders = await getBuilders() + return builders.length } /** @@ -289,4 +194,10 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => { userId, sessions.map(({ sessionId }) => sessionId) ) + await events.auth.logout() + await userCache.invalidateUser(userId) +} + +exports.timeout = timeMs => { + return new Promise(resolve => setTimeout(resolve, timeMs)) } diff --git a/packages/backend-core/tests/index.ts b/packages/backend-core/tests/index.ts new file mode 100644 index 0000000000..b23d52e6e0 --- /dev/null +++ b/packages/backend-core/tests/index.ts @@ -0,0 +1 @@ +export * from "./utilities" diff --git a/packages/backend-core/tests/utilities/TestConfiguration.js b/packages/backend-core/tests/utilities/TestConfiguration.js new file mode 100644 index 0000000000..207b1d937f --- /dev/null +++ b/packages/backend-core/tests/utilities/TestConfiguration.js @@ -0,0 +1 @@ +require("./db") diff --git a/packages/backend-core/tests/utilities/db.js b/packages/backend-core/tests/utilities/db.js new file mode 100644 index 0000000000..9d94bc2c7c --- /dev/null +++ b/packages/backend-core/tests/utilities/db.js @@ -0,0 +1,6 @@ +const core = require("../../src/index") +const dbConfig = { + inMemory: true, + allDbs: true, +} +core.init({ db: dbConfig }) diff --git a/packages/backend-core/tests/utilities/index.ts b/packages/backend-core/tests/utilities/index.ts new file mode 100644 index 0000000000..1e73be4c17 --- /dev/null +++ b/packages/backend-core/tests/utilities/index.ts @@ -0,0 +1,2 @@ +export * as mocks from "./mocks" +export * as structures from "./structures" diff --git a/packages/backend-core/tests/utilities/mocks/date.js b/packages/backend-core/tests/utilities/mocks/date.js new file mode 100644 index 0000000000..19248c6f11 --- /dev/null +++ b/packages/backend-core/tests/utilities/mocks/date.js @@ -0,0 +1,2 @@ +exports.MOCK_DATE = new Date("2020-01-01T00:00:00.000Z") +exports.MOCK_DATE_TIMESTAMP = 1577836800000 diff --git a/packages/backend-core/tests/utilities/mocks/events.js b/packages/backend-core/tests/utilities/mocks/events.js new file mode 100644 index 0000000000..a4055cc5ea --- /dev/null +++ b/packages/backend-core/tests/utilities/mocks/events.js @@ -0,0 +1,111 @@ +const processors = require("../../../src/events/processors") + +jest.spyOn(processors.analyticsProcessor, "processEvent") + +const events = require("../../../src/events") + +jest.spyOn(events.identification, "identifyTenantGroup") +jest.spyOn(events.identification, "identifyUser") + +jest.spyOn(events.backfill, "appSucceeded") +jest.spyOn(events.backfill, "tenantSucceeded") + +jest.spyOn(events.account, "created") +jest.spyOn(events.account, "deleted") +jest.spyOn(events.account, "verified") + +jest.spyOn(events.app, "created") +jest.spyOn(events.app, "updated") +jest.spyOn(events.app, "deleted") +jest.spyOn(events.app, "published") +jest.spyOn(events.app, "unpublished") +jest.spyOn(events.app, "templateImported") +jest.spyOn(events.app, "fileImported") +jest.spyOn(events.app, "versionUpdated") +jest.spyOn(events.app, "versionReverted") +jest.spyOn(events.app, "reverted") +jest.spyOn(events.app, "exported") + +jest.spyOn(events.auth, "login") +jest.spyOn(events.auth, "logout") +jest.spyOn(events.auth, "SSOCreated") +jest.spyOn(events.auth, "SSOUpdated") +jest.spyOn(events.auth, "SSOActivated") +jest.spyOn(events.auth, "SSODeactivated") + +jest.spyOn(events.automation, "created") +jest.spyOn(events.automation, "deleted") +jest.spyOn(events.automation, "tested") +jest.spyOn(events.automation, "stepCreated") +jest.spyOn(events.automation, "stepDeleted") +jest.spyOn(events.automation, "triggerUpdated") + +jest.spyOn(events.datasource, "created") +jest.spyOn(events.datasource, "updated") +jest.spyOn(events.datasource, "deleted") + +jest.spyOn(events.email, "SMTPCreated") +jest.spyOn(events.email, "SMTPUpdated") + +jest.spyOn(events.layout, "created") +jest.spyOn(events.layout, "deleted") + +jest.spyOn(events.org, "nameUpdated") +jest.spyOn(events.org, "logoUpdated") +jest.spyOn(events.org, "platformURLUpdated") +jest.spyOn(events.org, "analyticsOptOut") + +jest.spyOn(events.installation, "versionChecked") + +jest.spyOn(events.query, "created") +jest.spyOn(events.query, "updated") +jest.spyOn(events.query, "deleted") +jest.spyOn(events.query, "imported") +jest.spyOn(events.query, "previewed") + +jest.spyOn(events.role, "created") +jest.spyOn(events.role, "updated") +jest.spyOn(events.role, "deleted") +jest.spyOn(events.role, "assigned") +jest.spyOn(events.role, "unassigned") + +jest.spyOn(events.rows, "imported") +jest.spyOn(events.rows, "created") + +jest.spyOn(events.screen, "created") +jest.spyOn(events.screen, "deleted") + +jest.spyOn(events.user, "created") +jest.spyOn(events.user, "updated") +jest.spyOn(events.user, "deleted") +jest.spyOn(events.user, "permissionAdminAssigned") +jest.spyOn(events.user, "permissionAdminRemoved") +jest.spyOn(events.user, "permissionBuilderAssigned") +jest.spyOn(events.user, "permissionBuilderRemoved") +jest.spyOn(events.user, "invited") +jest.spyOn(events.user, "inviteAccepted") +jest.spyOn(events.user, "passwordForceReset") +jest.spyOn(events.user, "passwordUpdated") +jest.spyOn(events.user, "passwordResetRequested") +jest.spyOn(events.user, "passwordReset") + +jest.spyOn(events.serve, "servedBuilder") +jest.spyOn(events.serve, "servedApp") +jest.spyOn(events.serve, "servedAppPreview") + +jest.spyOn(events.table, "created") +jest.spyOn(events.table, "updated") +jest.spyOn(events.table, "deleted") +jest.spyOn(events.table, "exported") +jest.spyOn(events.table, "imported") + +jest.spyOn(events.view, "created") +jest.spyOn(events.view, "updated") +jest.spyOn(events.view, "deleted") +jest.spyOn(events.view, "exported") +jest.spyOn(events.view, "filterCreated") +jest.spyOn(events.view, "filterUpdated") +jest.spyOn(events.view, "filterDeleted") +jest.spyOn(events.view, "calculationCreated") +jest.spyOn(events.view, "calculationUpdated") +jest.spyOn(events.view, "calculationDeleted") diff --git a/packages/backend-core/tests/utilities/mocks/index.js b/packages/backend-core/tests/utilities/mocks/index.js new file mode 100644 index 0000000000..3dd5c854c0 --- /dev/null +++ b/packages/backend-core/tests/utilities/mocks/index.js @@ -0,0 +1,7 @@ +const events = require("./events") +const date = require("./date") + +module.exports = { + date, + events, +} diff --git a/packages/backend-core/tests/utilities/structures/index.ts b/packages/backend-core/tests/utilities/structures/index.ts new file mode 100644 index 0000000000..12b6ab7ad6 --- /dev/null +++ b/packages/backend-core/tests/utilities/structures/index.ts @@ -0,0 +1 @@ +export * as koa from "./koa" diff --git a/packages/backend-core/tests/utilities/structures/koa.ts b/packages/backend-core/tests/utilities/structures/koa.ts new file mode 100644 index 0000000000..6f0f7866e6 --- /dev/null +++ b/packages/backend-core/tests/utilities/structures/koa.ts @@ -0,0 +1,5 @@ +import { createMockContext } from "@shopify/jest-koa-mocks" + +export const newContext = () => { + return createMockContext() +} diff --git a/packages/backend-core/tsconfig.build.json b/packages/backend-core/tsconfig.build.json new file mode 100644 index 0000000000..40ffe6b827 --- /dev/null +++ b/packages/backend-core/tsconfig.build.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "lib": ["es2020"], + "allowJs": true, + "strict": true, + "noImplicitAny": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "incremental": true, + "sourceMap": true, + "declaration": true, + "types": [ "node", "jest" ], + "outDir": "dist" + }, + "include": [ + "**/*.js", + "**/*.ts", + "package.json" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.spec.ts", + "**/*.spec.js" + ] +} \ No newline at end of file diff --git a/packages/backend-core/tsconfig.json b/packages/backend-core/tsconfig.json new file mode 100644 index 0000000000..ccefd149a0 --- /dev/null +++ b/packages/backend-core/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "composite": true, + "baseUrl": ".", + "paths": { + "@budibase/types": ["../types/src"] + } + }, + "references": [ + { "path": "../types" }, + ] +} \ No newline at end of file diff --git a/packages/backend-core/yarn.lock b/packages/backend-core/yarn.lock index fc70e3d6a1..e1f38a798f 100644 --- a/packages/backend-core/yarn.lock +++ b/packages/backend-core/yarn.lock @@ -2,177 +2,158 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" - integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== dependencies: - "@babel/highlight" "^7.16.0" + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" -"@babel/compat-data@^7.16.0": - version "7.16.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" - integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== - -"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.5": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.0.tgz#c4ff44046f5fe310525cc9eb4ef5147f0c5374d4" - integrity sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/generator" "^7.16.0" - "@babel/helper-compilation-targets" "^7.16.0" - "@babel/helper-module-transforms" "^7.16.0" - "@babel/helpers" "^7.16.0" - "@babel/parser" "^7.16.0" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.0" - "@babel/types" "^7.16.0" + "@babel/highlight" "^7.16.7" + +"@babel/compat-data@^7.17.10": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab" + integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw== + +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.2.tgz#87b2fcd7cce9becaa7f5acebdc4f09f3dd19d876" + integrity sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-compilation-targets" "^7.18.2" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helpers" "^7.18.2" + "@babel/parser" "^7.18.0" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.1.2" + json5 "^2.2.1" semver "^6.3.0" - source-map "^0.5.0" -"@babel/generator@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.0.tgz#d40f3d1d5075e62d3500bccb67f3daa8a95265b2" - integrity sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew== +"@babel/generator@^7.18.2", "@babel/generator@^7.7.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" + integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.18.2" + "@jridgewell/gen-mapping" "^0.3.0" jsesc "^2.5.1" - source-map "^0.5.0" -"@babel/helper-compilation-targets@^7.16.0": - version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0" - integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA== +"@babel/helper-compilation-targets@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" + integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== dependencies: - "@babel/compat-data" "^7.16.0" - "@babel/helper-validator-option" "^7.14.5" - browserslist "^4.17.5" + "@babel/compat-data" "^7.17.10" + "@babel/helper-validator-option" "^7.16.7" + browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-function-name@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" - integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== +"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" + integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== + +"@babel/helper-function-name@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== dependencies: - "@babel/helper-get-function-arity" "^7.16.0" - "@babel/template" "^7.16.0" - "@babel/types" "^7.16.0" + "@babel/template" "^7.16.7" + "@babel/types" "^7.17.0" -"@babel/helper-get-function-arity@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" - integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-hoist-variables@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" - integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== +"@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-member-expression-to-functions@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz#29287040efd197c77636ef75188e81da8bccd5a4" - integrity sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ== +"@babel/helper-module-transforms@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" + integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== dependencies: - "@babel/types" "^7.16.0" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.0" + "@babel/types" "^7.18.0" -"@babel/helper-module-imports@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" - integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" + integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== + +"@babel/helper-simple-access@^7.17.7": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" + integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.18.2" -"@babel/helper-module-transforms@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz#1c82a8dd4cb34577502ebd2909699b194c3e9bb5" - integrity sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA== +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== dependencies: - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-replace-supers" "^7.16.0" - "@babel/helper-simple-access" "^7.16.0" - "@babel/helper-split-export-declaration" "^7.16.0" - "@babel/helper-validator-identifier" "^7.15.7" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.0" - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-optimise-call-expression@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338" - integrity sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw== +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== + +"@babel/helpers@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" + integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== dependencies: - "@babel/types" "^7.16.0" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" - integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== - -"@babel/helper-replace-supers@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz#73055e8d3cf9bcba8ddb55cad93fedc860f68f17" - integrity sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA== +"@babel/highlight@^7.16.7": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351" + integrity sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg== dependencies: - "@babel/helper-member-expression-to-functions" "^7.16.0" - "@babel/helper-optimise-call-expression" "^7.16.0" - "@babel/traverse" "^7.16.0" - "@babel/types" "^7.16.0" - -"@babel/helper-simple-access@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517" - integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw== - dependencies: - "@babel/types" "^7.16.0" - -"@babel/helper-split-export-declaration@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" - integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== - dependencies: - "@babel/types" "^7.16.0" - -"@babel/helper-validator-identifier@^7.15.7": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" - integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== - -"@babel/helper-validator-option@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" - integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== - -"@babel/helpers@^7.16.0": - version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.3.tgz#27fc64f40b996e7074dc73128c3e5c3e7f55c43c" - integrity sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w== - dependencies: - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.3" - "@babel/types" "^7.16.0" - -"@babel/highlight@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" - integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== - dependencies: - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-validator-identifier" "^7.16.7" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.3": - version "7.16.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e" - integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.0": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef" + integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -258,36 +239,51 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/template@^7.16.0", "@babel/template@^7.3.3": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" - integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" + integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/parser" "^7.16.0" - "@babel/types" "^7.16.0" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.16.3": - version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.3.tgz#f63e8a938cc1b780f66d9ed3c54f532ca2d14787" - integrity sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag== +"@babel/runtime@^7.15.4": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" + integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/generator" "^7.16.0" - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-hoist-variables" "^7.16.0" - "@babel/helper-split-export-declaration" "^7.16.0" - "@babel/parser" "^7.16.3" - "@babel/types" "^7.16.0" + regenerator-runtime "^0.13.4" + +"@babel/template@^7.16.7", "@babel/template@^7.3.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.7.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.2.tgz#b77a52604b5cc836a9e1e08dca01cba67a12d2e8" + integrity sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-environment-visitor" "^7.18.2" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.18.0" + "@babel/types" "^7.18.2" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" - integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== +"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" + integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== dependencies: - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -295,14 +291,6 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@cnakazawa/watch@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" - integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== - dependencies: - exec-sh "^0.3.2" - minimist "^1.2.0" - "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -319,177 +307,243 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" - integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== +"@jest/console@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^26.6.2" - jest-util "^26.6.2" + jest-message-util "^27.5.1" + jest-util "^27.5.1" slash "^3.0.0" -"@jest/core@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" - integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== +"@jest/core@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== dependencies: - "@jest/console" "^26.6.2" - "@jest/reporters" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/reporters" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" + emittery "^0.8.1" exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^26.6.2" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-resolve-dependencies "^26.6.3" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - jest-watcher "^26.6.2" - micromatch "^4.0.2" - p-each-series "^2.1.0" + graceful-fs "^4.2.9" + jest-changed-files "^27.5.1" + jest-config "^27.5.1" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-resolve-dependencies "^27.5.1" + jest-runner "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + jest-watcher "^27.5.1" + micromatch "^4.0.4" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" - integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== +"@jest/environment@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== dependencies: - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" + jest-mock "^27.5.1" -"@jest/fake-timers@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" - integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== +"@jest/fake-timers@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== dependencies: - "@jest/types" "^26.6.2" - "@sinonjs/fake-timers" "^6.0.1" + "@jest/types" "^27.5.1" + "@sinonjs/fake-timers" "^8.0.1" "@types/node" "*" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-util "^27.5.1" -"@jest/globals@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" - integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== +"@jest/globals@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== dependencies: - "@jest/environment" "^26.6.2" - "@jest/types" "^26.6.2" - expect "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/types" "^27.5.1" + expect "^27.5.1" -"@jest/reporters@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" - integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== +"@jest/reporters@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.2" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" + istanbul-lib-instrument "^5.1.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.6.2" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" + istanbul-reports "^3.1.3" + jest-haste-map "^27.5.1" + jest-resolve "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^7.0.0" - optionalDependencies: - node-notifier "^8.0.0" + v8-to-istanbul "^8.1.0" -"@jest/source-map@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" - integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== +"@jest/source-map@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== dependencies: callsites "^3.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" source-map "^0.6.0" -"@jest/test-result@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" - integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== +"@jest/test-result@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== dependencies: - "@jest/console" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/types" "^27.5.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" - integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== +"@jest/test-sequencer@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== dependencies: - "@jest/test-result" "^26.6.2" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" + "@jest/test-result" "^27.5.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-runtime "^27.5.1" -"@jest/transform@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" - integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== +"@jest/transform@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^26.6.2" - babel-plugin-istanbul "^6.0.0" + "@jest/types" "^27.5.1" + babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-regex-util "^26.0.0" - jest-util "^26.6.2" - micromatch "^4.0.2" - pirates "^4.0.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-regex-util "^27.5.1" + jest-util "^27.5.1" + micromatch "^4.0.4" + pirates "^4.0.4" slash "^3.0.0" source-map "^0.6.1" write-file-atomic "^3.0.0" -"@jest/types@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" - "@types/yargs" "^15.0.0" + "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9" + integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" + integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + +"@jridgewell/set-array@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" + integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.13" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" + integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" + integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@mapbox/node-pre-gyp@^1.0.0": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc" + integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@shopify/jest-koa-mocks@3.1.5": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@shopify/jest-koa-mocks/-/jest-koa-mocks-3.1.5.tgz#11f77ccfbcaf35cf5ee2c6108a286e61e6bea084" + integrity sha512-gQ3/7ELerv00TWO37AGFX5mT9CsFCS+3/UbKMuoIlKEU0QH2OX8BV9WBf/EKw7adCDNlxss0lqV6J8kf5pgr4A== + dependencies: + koa "^2.13.4" + node-mocks-http "^1.5.8" + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -497,14 +551,21 @@ dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" - integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== dependencies: "@sinonjs/commons" "^1.7.0" -"@techpass/passport-openidconnect@^0.3.0": +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +"@techpass/passport-openidconnect@0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@techpass/passport-openidconnect/-/passport-openidconnect-0.3.2.tgz#f8fd5d97256286665dbf26dac92431f977ab1e63" integrity sha512-fnCtEiexXSHA029B//hJcCJlLJrT3lhpNCyA0rnz58Qttz0BLGCVv6yMT8HmOnGThH6vcDOVwdgKM3kbCQtEhw== @@ -520,10 +581,17 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": - version "7.1.16" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702" - integrity sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ== +"@types/accepts@*": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" + integrity sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ== + dependencies: + "@types/node" "*" + +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": + version "7.1.19" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -532,9 +600,9 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.3.tgz#f456b4b2ce79137f768aa130d2423d2f0ccfaba5" - integrity sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA== + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== dependencies: "@babel/types" "^7.0.0" @@ -547,12 +615,73 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.14.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" - integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + version "7.17.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" + integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== dependencies: "@babel/types" "^7.3.0" +"@types/bluebird@*": + version "3.5.36" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.36.tgz#00d9301d4dc35c2f6465a8aec634bb533674c652" + integrity sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q== + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/content-disposition@*": + version "0.5.5" + resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.5.tgz#650820e95de346e1f84e30667d168c8fd25aa6e3" + integrity sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA== + +"@types/cookies@*": + version "0.7.7" + resolved "https://registry.yarnpkg.com/@types/cookies/-/cookies-0.7.7.tgz#7a92453d1d16389c05a5301eef566f34946cfd81" + integrity sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA== + dependencies: + "@types/connect" "*" + "@types/express" "*" + "@types/keygrip" "*" + "@types/node" "*" + +"@types/debug@*": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" + integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg== + dependencies: + "@types/ms" "*" + +"@types/express-serve-static-core@^4.17.18": + version "4.17.28" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -560,10 +689,20 @@ dependencies: "@types/node" "*" +"@types/http-assert@*": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.3.tgz#ef8e3d1a8d46c387f04ab0f2e8ab8cb0c5078661" + integrity sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA== + +"@types/http-errors@*": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-1.8.2.tgz#7315b4c4c54f82d13fa61c228ec5c2ea5cc9e0e1" + integrity sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" - integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== "@types/istanbul-lib-report@*": version "3.0.0" @@ -579,42 +718,320 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.1.tgz#2c8b6dc6ff85c33bcd07d0b62cb3d19ddfdb3ab9" + integrity sha512-fUy7YRpT+rHXto1YlL+J9rs0uLGyiqVt3ZOTQR+4ROc47yNl8WLdVLgUloBRhOxP1PZvguHl44T3H0wAWxahYQ== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + +"@types/keygrip@*": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72" + integrity sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw== + +"@types/koa-compose@*": + version "3.2.5" + resolved "https://registry.yarnpkg.com/@types/koa-compose/-/koa-compose-3.2.5.tgz#85eb2e80ac50be95f37ccf8c407c09bbe3468e9d" + integrity sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ== + dependencies: + "@types/koa" "*" + +"@types/koa@*": + version "2.13.4" + resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b" + integrity sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw== + dependencies: + "@types/accepts" "*" + "@types/content-disposition" "*" + "@types/cookies" "*" + "@types/http-assert" "*" + "@types/http-errors" "*" + "@types/keygrip" "*" + "@types/koa-compose" "*" + "@types/node" "*" + +"@types/koa@2.0.52": + version "2.0.52" + resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.0.52.tgz#7dd11de4189ab339ad66c4ccad153716b14e525f" + integrity sha512-cp/GTOhOYwomlSKqEoG0kaVEVJEzP4ojYmfa7EKaGkmkkRwJ4B/1VBLbQZ49Z+WJNvzXejQB/9GIKqMo9XLgFQ== + dependencies: + "@types/accepts" "*" + "@types/cookies" "*" + "@types/http-assert" "*" + "@types/keygrip" "*" + "@types/koa-compose" "*" + "@types/node" "*" + +"@types/lodash@4.14.180": + version "4.14.180" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670" + integrity sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/ms@*": + version "0.7.31" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== + +"@types/node-fetch@2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975" + integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + "@types/node@*": - version "16.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" - integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== + version "17.0.41" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b" + integrity sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw== -"@types/normalize-package-data@^2.4.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" - integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== +"@types/node@14.18.20": + version "14.18.20" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.20.tgz#268f028b36eaf51181c3300252f605488c4f0650" + integrity sha512-Q8KKwm9YqEmUBRsqJ2GWJDtXltBDxTdC4m5vTdXBolu2PeQh8LX+f6BTwU+OuXPu37fLxoN6gidqBmnky36FXA== -"@types/prettier@^2.0.0": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.2.tgz#4c62fae93eb479660c3bd93f9d24d561597a8281" - integrity sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA== +"@types/pouchdb-adapter-cordova-sqlite@*": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-cordova-sqlite/-/pouchdb-adapter-cordova-sqlite-1.0.1.tgz#49e5ee6df7cc0c23196fcb340f43a560e74eb1d6" + integrity sha512-nqlXpW1ho3KBg1mUQvZgH2755y3z/rw4UA7ZJCPMRTHofxGMY8izRVw5rHBL4/7P615or0J2udpRYxgkT3D02g== + dependencies: + "@types/pouchdb-core" "*" + +"@types/pouchdb-adapter-fruitdown@*": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-fruitdown/-/pouchdb-adapter-fruitdown-6.1.3.tgz#9b140ad9645cc56068728acf08ec19ac0046658e" + integrity sha512-Wz1Z1JLOW1hgmFQjqnSkmyyfH7by/iWb4abKn684WMvQfmxx6BxKJpJ4+eulkVPQzzgMMSgU1MpnQOm9FgRkbw== + dependencies: + "@types/pouchdb-core" "*" + +"@types/pouchdb-adapter-http@*": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-http/-/pouchdb-adapter-http-6.1.3.tgz#6e592d5f48deb6274a21ddac1498dd308096bcf3" + integrity sha512-9Z4TLbF/KJWy/D2sWRPBA+RNU0odQimfdvlDX+EY7rGcd3aVoH8qjD/X0Xcd/0dfBH5pKrNIMFFQgW/TylRCmA== + dependencies: + "@types/pouchdb-core" "*" + +"@types/pouchdb-adapter-idb@*": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-idb/-/pouchdb-adapter-idb-6.1.4.tgz#cb9a18864585d600820cd325f007614c5c3989cd" + integrity sha512-KIAXbkF4uYUz0ZwfNEFLtEkK44mEWopAsD76UhucH92XnJloBysav+TjI4FFfYQyTjoW3S1s6V+Z14CUJZ0F6w== + dependencies: + "@types/pouchdb-core" "*" + +"@types/pouchdb-adapter-leveldb@*": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-leveldb/-/pouchdb-adapter-leveldb-6.1.3.tgz#17c7e75d75b992050bca15991e97fba575c61bb3" + integrity sha512-ex8NFqQGFwEpFi7AaZ5YofmuemfZNsL3nTFZBUCAKYMBkazQij1pe2ILLStSvJr0XS0qxgXjCEW19T5Wqiiskg== + dependencies: + "@types/pouchdb-core" "*" + +"@types/pouchdb-adapter-localstorage@*": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-localstorage/-/pouchdb-adapter-localstorage-6.1.3.tgz#0dde02ba6b9d6073a295a20196563942ba9a54bd" + integrity sha512-oor040tye1KKiGLWYtIy7rRT7C2yoyX3Tf6elEJRpjOA7Ja/H8lKc4LaSh9ATbptIcES6MRqZDxtp7ly9hsW3Q== + dependencies: + "@types/pouchdb-core" "*" + +"@types/pouchdb-adapter-memory@*": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-memory/-/pouchdb-adapter-memory-6.1.3.tgz#9eabdbc890fcf58960ee8b68b8685f837e75c844" + integrity sha512-gVbsIMzDzgZYThFVT4eVNsmuZwVm/4jDxP1sjlgc3qtDIxbtBhGgyNfcskwwz9Zu5Lv1avkDsIWvcxQhnvRlHg== + dependencies: + "@types/pouchdb-core" "*" + +"@types/pouchdb-adapter-node-websql@*": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-node-websql/-/pouchdb-adapter-node-websql-6.1.3.tgz#aa18bc68af8cf509acd12c400010dcd5fab2243d" + integrity sha512-F/P+os6Jsa7CgHtH64+Z0HfwIcj0hIRB5z8gNhF7L7dxPWoAfkopK5H2gydrP3sQrlGyN4WInF+UJW/Zu1+FKg== + dependencies: + "@types/pouchdb-adapter-websql" "*" + "@types/pouchdb-core" "*" + +"@types/pouchdb-adapter-websql@*": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@types/pouchdb-adapter-websql/-/pouchdb-adapter-websql-6.1.4.tgz#359fbe42ccac0ac90b492ddb8c32fafd0aa96d79" + integrity sha512-zMJQCtXC40hBsIDRn0GhmpeGMK0f9l/OGWfLguvczROzxxcOD7REI+e6SEmX7gJKw5JuMvlfuHzkQwjmvSJbtg== + dependencies: + "@types/pouchdb-core" "*" + +"@types/pouchdb-browser@*": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/pouchdb-browser/-/pouchdb-browser-6.1.3.tgz#8f33d6ef58d6817d1f6d36979148a1c7f63244d8" + integrity sha512-EdYowrWxW9SWBMX/rux2eq7dbHi5Zeyzz+FF/IAsgQKnUxgeCO5VO2j4zTzos0SDyJvAQU+EYRc11r7xGn5tvA== + dependencies: + "@types/pouchdb-adapter-http" "*" + "@types/pouchdb-adapter-idb" "*" + "@types/pouchdb-adapter-websql" "*" + "@types/pouchdb-core" "*" + "@types/pouchdb-mapreduce" "*" + "@types/pouchdb-replication" "*" + +"@types/pouchdb-core@*": + version "7.0.10" + resolved "https://registry.yarnpkg.com/@types/pouchdb-core/-/pouchdb-core-7.0.10.tgz#d1ea1549e7fad6cb579f71459b1bc27252e06a5a" + integrity sha512-mKhjLlWWXyV3PTTjDhzDV1kc2dolO7VYFa75IoKM/hr8Er9eo8RIbS7mJLfC8r/C3p6ihZu9yZs1PWC1LQ0SOA== + dependencies: + "@types/debug" "*" + "@types/pouchdb-find" "*" + +"@types/pouchdb-find@*": + version "6.3.7" + resolved "https://registry.yarnpkg.com/@types/pouchdb-find/-/pouchdb-find-6.3.7.tgz#f713534a53c1a7f3fd8fbbfb74131a1b04711ddc" + integrity sha512-b2dr9xoZRK5Mwl8UiRA9l5j9mmCxNfqXuu63H1KZHwJLILjoIIz7BntCvM0hnlnl7Q8P8wORq0IskuaMq5Nnnw== + dependencies: + "@types/pouchdb-core" "*" + +"@types/pouchdb-http@*": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/pouchdb-http/-/pouchdb-http-6.1.3.tgz#09576c0d409da1f8dee34ec5b768415e2472ea52" + integrity sha512-0e9E5SqNOyPl/3FnEIbENssB4FlJsNYuOy131nxrZk36S+y1R/6qO7ZVRypWpGTqBWSuVd7gCsq2UDwO/285+w== + dependencies: + "@types/pouchdb-adapter-http" "*" + "@types/pouchdb-core" "*" + +"@types/pouchdb-mapreduce@*": + version "6.1.7" + resolved "https://registry.yarnpkg.com/@types/pouchdb-mapreduce/-/pouchdb-mapreduce-6.1.7.tgz#9ab32d1e0f234f1bf6d1e4c5d7e216e9e23ac0a3" + integrity sha512-WzBwm7tmO9QhfRzVaWT4v6JQSS/fG2OoUDrWrhX87rPe2Pn6laPvdK5li6myNRxCoI/l5e8Jd+oYBAFnaiFucA== + dependencies: + "@types/pouchdb-core" "*" + +"@types/pouchdb-node@*": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@types/pouchdb-node/-/pouchdb-node-6.1.4.tgz#5214c0169fcfd2237d373380bbd65a934feb5dfb" + integrity sha512-wnTCH8X1JOPpNOfVhz8HW0AvmdHh6pt40MuRj0jQnK7QEHsHS79WujsKTKSOF8QXtPwpvCNSsI7ut7H7tfxxJQ== + dependencies: + "@types/pouchdb-adapter-http" "*" + "@types/pouchdb-adapter-leveldb" "*" + "@types/pouchdb-core" "*" + "@types/pouchdb-mapreduce" "*" + "@types/pouchdb-replication" "*" + +"@types/pouchdb-replication@*": + version "6.4.4" + resolved "https://registry.yarnpkg.com/@types/pouchdb-replication/-/pouchdb-replication-6.4.4.tgz#743406c90f13a988fa3e346ea74ce40acd170d00" + integrity sha512-BsE5LKpjJK4iAf6Fx5kyrMw+33V+Ip7uWldUnU2BYrrvtR+MLD22dcImm7DZN1st2wPPb91i0XEnQzvP0w1C/Q== + dependencies: + "@types/pouchdb-core" "*" + "@types/pouchdb-find" "*" + +"@types/pouchdb@6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@types/pouchdb/-/pouchdb-6.4.0.tgz#f9c41ca64b23029f9bf2eb4bf6956e6431cb79f8" + integrity sha512-eGCpX+NXhd5VLJuJMzwe3L79fa9+IDTrAG3CPaf4s/31PD56hOrhDJTSmRELSXuiqXr6+OHzzP0PldSaWsFt7w== + dependencies: + "@types/pouchdb-adapter-cordova-sqlite" "*" + "@types/pouchdb-adapter-fruitdown" "*" + "@types/pouchdb-adapter-http" "*" + "@types/pouchdb-adapter-idb" "*" + "@types/pouchdb-adapter-leveldb" "*" + "@types/pouchdb-adapter-localstorage" "*" + "@types/pouchdb-adapter-memory" "*" + "@types/pouchdb-adapter-node-websql" "*" + "@types/pouchdb-adapter-websql" "*" + "@types/pouchdb-browser" "*" + "@types/pouchdb-core" "*" + "@types/pouchdb-http" "*" + "@types/pouchdb-mapreduce" "*" + "@types/pouchdb-node" "*" + "@types/pouchdb-replication" "*" + +"@types/prettier@^2.1.5": + version "2.6.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a" + integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/redis@^2.8.0": + version "2.8.32" + resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.32.tgz#1d3430219afbee10f8cfa389dad2571a05ecfb11" + integrity sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w== + dependencies: + "@types/node" "*" + +"@types/redlock@4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/redlock/-/redlock-4.0.3.tgz#aeab5fe5f0d433a125f6dcf9a884372ac0cddd4b" + integrity sha512-mcvvrquwREbAqyZALNBIlf49AL9Aa324BG+J/Dv4TAP8g+nxQMBI4/APNqqS99QEY7VTNT9XvsaczCVGK8uNnQ== + dependencies: + "@types/bluebird" "*" + "@types/redis" "^2.8.0" + +"@types/semver@7.3.7": + version "7.3.7" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.7.tgz#b9eb89d7dfa70d5d1ce525bc1411a35347f533a3" + integrity sha512-4g1jrL98mdOIwSOUh6LTlB0Cs9I0dQPwINUhBg7C6pN4HLr8GS8xsksJxilW6S6dQHVi2K/o+lQuQcg7LroCnw== + +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== -"@types/yargs-parser@*": - version "20.2.1" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" - integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== +"@types/tar-fs@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/tar-fs/-/tar-fs-2.0.1.tgz#6391dcad1b03dea2d79fac07371585ab54472bb1" + integrity sha512-qlsQyIY9sN7p221xHuXKNoMfUenOcvEBN4zI8dGsYbYCqHtTarXOEXSIgUnK+GcR0fZDse6pAIc5pIrCh9NefQ== + dependencies: + "@types/node" "*" + "@types/tar-stream" "*" -"@types/yargs@^15.0.0": - version "15.0.14" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" - integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== +"@types/tar-stream@*": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@types/tar-stream/-/tar-stream-2.2.2.tgz#be9d0be9404166e4b114151f93e8442e6ab6fb1d" + integrity sha512-1AX+Yt3icFuU6kxwmPakaiGrJUwG44MpuiqPg4dSolRFk6jmvs4b3IbUol9wKDLIgU76gevn3EwE8y/DkSJCZQ== + dependencies: + "@types/node" "*" + +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== dependencies: "@types/yargs-parser" "*" abab@^2.0.3, abab@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" - integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== abort-controller@3.0.0: version "3.0.0" @@ -623,10 +1040,10 @@ abort-controller@3.0.0: dependencies: event-target-shim "^5.0.0" -abstract-leveldown@^6.2.1, abstract-leveldown@~6.2.1, abstract-leveldown@~6.2.3: - version "6.2.3" - resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz#036543d87e3710f2528e47040bc3261b77a9a8eb" - integrity sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ== +abstract-leveldown@^6.2.1: + version "6.3.0" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz#d25221d1e6612f820c35963ba4bd739928f6026a" + integrity sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ== dependencies: buffer "^5.5.0" immediate "^3.2.3" @@ -641,6 +1058,25 @@ abstract-leveldown@~2.7.1: dependencies: xtend "~4.0.0" +abstract-leveldown@~6.2.1, abstract-leveldown@~6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz#036543d87e3710f2528e47040bc3261b77a9a8eb" + integrity sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ== + dependencies: + buffer "^5.5.0" + immediate "^3.2.3" + level-concat-iterator "~2.0.0" + level-supports "~1.0.0" + xtend "~4.0.0" + +accepts@^1.3.5, accepts@^1.3.7: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" @@ -654,20 +1090,15 @@ acorn-walk@^7.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^5.2.1: - version "5.7.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" - integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== - acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== agent-base@6: version "6.0.2" @@ -676,14 +1107,6 @@ agent-base@6: dependencies: debug "4" -ajv@^4.9.1: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - ajv@^6.12.3: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -694,10 +1117,12 @@ ajv@^6.12.3: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= +ansi-align@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" ansi-escapes@^4.2.1: version "4.3.2" @@ -706,7 +1131,7 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" -ansi-regex@^5.0.0, ansi-regex@^5.0.1: +ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -725,15 +1150,17 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^3.0.3: +any-promise@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -741,6 +1168,19 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -751,78 +1191,33 @@ argparse@^1.0.7: argsarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb" - integrity sha1-bnIHtOzbObCviDA/pa4ivajfYcs= - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + integrity sha512-u96dg2GcAKtpTrBdDoFIM7PjcBA+6rSP0OR94MOReNRyUECL6MtQt5XXmRr4qrftYaef9+l5hcpO5te7sML1Cg== asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" - integrity sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y= + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assert-plus@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" - integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ= - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -ast-types@0.9.6: - version "0.9.6" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" - integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk= - -async-hook-jl@^1.7.6: - version "1.7.6" - resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" - integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== - dependencies: - stack-chain "^1.3.7" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== async@~2.1.4: version "2.1.5" resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc" - integrity sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw= + integrity sha512-+g/Ncjbx0JSq2Mk03WQkyKvNh5q9Qvyo/RIqIqnmC5feJY70PNl2ESwZU2BhAB+AZPkHNzzyC2Dq2AS5VnTKhQ== dependencies: lodash "^4.14.0" asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -aws-sdk@^2.901.0: +aws-sdk@2.1030.0: version "2.1030.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1030.0.tgz#24a856af3d2b8b37c14a8f59974993661c66fd82" integrity sha512-to0STOb8DsSGuSsUb/WCbg/UFnMGfIYavnJH5ZlRCHzvCFjTyR+vfE8ku+qIZvfFM4+5MNTQC/Oxfun2X/TuyA== @@ -837,41 +1232,46 @@ aws-sdk@^2.901.0: uuid "3.3.2" xml2js "0.4.19" -aws-sign2@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" - integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8= - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.2.1: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" - integrity sha1-g+9cqGCysy5KDe7e6MdxudtXRx4= + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== aws4@^1.8.0: version "1.11.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -babel-jest@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" - integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== +axios-retry@^3.1.9: + version "3.2.5" + resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.2.5.tgz#64952992837c7d9a12eec156a2694a7945f60895" + integrity sha512-a8umkKbfIkTiYJQLx3v3TzKM85TGKB8ZQYz4zwykt2fpO64TsRlUhjaPaAb3fqMWCXFm2YhWcd8V5FHDKO9bSA== dependencies: - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.6.2" + "@babel/runtime" "^7.15.4" + is-retry-allowed "^2.2.0" + +axios@0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== + dependencies: + follow-redirects "^1.14.4" + +babel-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== + dependencies: + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^27.5.1" chalk "^4.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" slash "^3.0.0" -babel-plugin-istanbul@^6.0.0: +babel-plugin-istanbul@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== @@ -882,10 +1282,10 @@ babel-plugin-istanbul@^6.0.0: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" - integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== +babel-plugin-jest-hoist@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -910,12 +1310,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" - integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== +babel-preset-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== dependencies: - babel-plugin-jest-hoist "^26.6.2" + babel-plugin-jest-hoist "^27.5.1" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: @@ -923,50 +1323,35 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base62@^1.1.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428" - integrity sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA== - base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -base64url@2.0.0, base64url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" - integrity sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs= - base64url@3.x.x, base64url@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" - integrity sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40= + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== dependencies: tweetnacl "^0.14.3" -bcryptjs@^2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" - integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= +bcrypt@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71" + integrity sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + node-addon-api "^3.1.0" + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== bl@^4.0.3: version "4.1.0" @@ -977,12 +1362,24 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -boom@2.x.x: - version "2.10.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" - integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +boxen@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== dependencies: - hoek "2.x.x" + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" brace-expansion@^1.1.7: version "1.1.11" @@ -992,23 +1389,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^3.0.1: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -1020,17 +1401,24 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.17.5: - version "4.18.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f" - integrity sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ== +browserslist@^4.20.2: + version "4.20.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.4.tgz#98096c9042af689ee1e0271333dbc564b8ce4477" + integrity sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw== dependencies: - caniuse-lite "^1.0.30001280" - electron-to-chromium "^1.3.896" + caniuse-lite "^1.0.30001349" + electron-to-chromium "^1.4.147" escalade "^3.1.1" - node-releases "^2.0.1" + node-releases "^2.0.5" picocolors "^1.0.0" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -1041,14 +1429,14 @@ bser@2.1.1: buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== buffer-from@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -buffer-from@^1.0.0: +buffer-from@1.1.2, buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== @@ -1070,52 +1458,51 @@ buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" -cache-base@^1.0.1: +cache-content-type@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c" + integrity sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA== dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" + mime-types "^2.1.18" + ylru "^1.2.0" + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase@^5.0.0, camelcase@^5.3.1: +camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" - integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001280: - version "1.0.30001282" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz#38c781ee0a90ccfe1fe7fefd00e43f5ffdcb96fd" - integrity sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg== - -capture-exit@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" - integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== - dependencies: - rsvp "^4.8.4" +caniuse-lite@^1.0.30001349: + version "1.0.30001352" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz#cc6f5da3f983979ad1e2cdbae0505dccaa7c6a12" + integrity sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== chalk@^2.0.0: version "2.4.2" @@ -1126,7 +1513,7 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1139,53 +1526,76 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== + +chokidar@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -cjs-module-lexer@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" - integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== +ci-info@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.1.tgz#58331f6f472a25fe3a50a351ae3052936c2c7f32" + integrity sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg== -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" + wrap-ansi "^7.0.0" clone-buffer@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" - integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= + integrity sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g== -cls-hooked@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" - integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q== dependencies: - async-hook-jl "^1.7.6" - emitter-listener "^1.0.1" - semver "^5.4.1" + mimic-response "^1.0.0" cluster-key-slot@^1.1.0: version "1.1.0" @@ -1195,21 +1605,13 @@ cluster-key-slot@^1.1.0: co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== collect-v8-coverage@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1227,19 +1629,17 @@ color-convert@^2.0.1: color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" - integrity sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk= - dependencies: - delayed-stream "~1.0.0" +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" @@ -1248,35 +1648,44 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.5.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commoner@^0.10.1: - version "0.10.8" - resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" - integrity sha1-NPw2cs0kOT6LtH5wyqApOBH08sU= - dependencies: - commander "^2.5.0" - detective "^4.3.1" - glob "^5.0.15" - graceful-fs "^4.1.2" - iconv-lite "^0.4.5" - mkdirp "^0.5.0" - private "^0.1.6" - q "^1.1.2" - recast "^0.11.17" - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== +component-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.2.1.tgz#8a47901700238e4fc32269771230226f24b415a9" + integrity sha512-Kgy+2+Uwr75vAi6ChWXgHuLvd+QLD7ssgpaRq2zCvt80ptvAfMc/hijcJxXkBa2wMlEZcJvC2H8Ubo+A9ATHIg== concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +content-disposition@^0.5.3, content-disposition@~0.5.2: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" @@ -1285,28 +1694,33 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +cookies@~0.7.1: + version "0.7.3" + resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.3.tgz#7912ce21fbf2e8c2da70cf1c3f351aecf59dadfa" + integrity sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A== + dependencies: + depd "~1.1.2" + keygrip "~1.0.3" -core-util-is@~1.0.0: +cookies@~0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90" + integrity sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow== + dependencies: + depd "~2.0.0" + keygrip "~1.1.0" + +core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cross-spawn@^7.0.0: +cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1315,12 +1729,15 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= - dependencies: - boom "2.x.x" +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== cssom@^0.4.4: version "0.4.4" @@ -1342,7 +1759,7 @@ cssstyle@^2.3.0: dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== dependencies: assert-plus "^1.0.0" @@ -1355,34 +1772,53 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== dependencies: ms "2.0.0" -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - decimal.js@^10.2.1: version "10.3.1" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== + dependencies: + mimic-response "^1.0.0" + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +deep-equal@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + integrity sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw== + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== deep-is@~0.1.3: version "0.1.4" @@ -1394,6 +1830,11 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + deferred-leveldown@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz#27a997ad95408b61161aa69bd489b86c71b78058" @@ -1402,60 +1843,50 @@ deferred-leveldown@~5.3.0: abstract-leveldown "~6.2.1" inherits "^2.0.3" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== denque@^1.1.0: version "1.5.1" resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== +depd@^1.1.0, depd@^1.1.2, depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +depd@^2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-libc@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -detective@^4.3.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" - integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig== - dependencies: - acorn "^5.2.1" - defined "^1.0.0" - -diff-sequences@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" - integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== domexception@^2.0.1: version "2.0.1" @@ -1464,17 +1895,35 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +dotenv@16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" + integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== + double-ended-queue@2.1.0-0: version "2.1.0-0" resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" - integrity sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw= + integrity sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ== + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA== ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" - integrity sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU= + 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: version "1.0.11" @@ -1483,36 +1932,38 @@ ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer "^5.0.1" -ecdsa-sig-formatter@1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" - integrity sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE= - dependencies: - base64url "^2.0.0" - safe-buffer "^5.0.1" +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.3.896: - version "1.3.900" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5" - integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ== +electron-to-chromium@^1.4.147: + version "1.4.150" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.150.tgz#89f0e12505462d5df7e56c5b91aff7e1dfdd33ec" + integrity sha512-MP3oBer0X7ZeS9GJ0H6lmkn561UxiwOIY9TTkdxVY7lI9G6GVCKfgJaHaDcakwdKxBXA4T3ybeswH/WBIN/KTA== -emitter-listener@^1.0.1: +emitter-listener@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== dependencies: shimmer "^1.2.0" -emittery@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" - integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +encodeurl@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + encoding-down@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/encoding-down/-/encoding-down-6.3.0.tgz#b1c4eb0e1728c146ecaef8e32963c549e76d082b" @@ -1533,7 +1984,7 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: end-stream@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/end-stream/-/end-stream-0.1.0.tgz#32003f3f438a2b0143168137f8fa6e9866c81ed5" - integrity sha1-MgA/P0OKKwFDFoE3+PpumGbIHtU= + integrity sha512-Brl10T8kYnc75IepKizW6Y9liyW8ikz1B7n/xoHrJxoVSSjoqPn30sb7XVFfQERK4QfUMYRGs9dhWwtt2eu6uA== dependencies: write-stream "~0.4.3" @@ -1551,24 +2002,30 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es3ify@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/es3ify/-/es3ify-0.2.2.tgz#5dae3e650e5be3684b88066513d528d092629862" - integrity sha1-Xa4+ZQ5b42hLiAZlE9Uo0JJimGI= - dependencies: - esprima "^2.7.1" - jstransform "~11.0.0" - through "~2.3.4" +error-inject@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37" + integrity sha512-JM8N6PytDbmIYm1IhPWlo8vr3NtfjhDY/1MhD/a5b/aad/USE8a0+NsqE9d5n+GVGmuNkPQWm4bFQWv18d8tMg== escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== escape-string-regexp@^2.0.0: version "2.0.0" @@ -1587,26 +2044,11 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -esprima-fb@^15001.1.0-dev-harmony-fb: - version "15001.1.0-dev-harmony-fb" - resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" - integrity sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE= - -esprima@^2.7.1: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" - integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= - esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esprima@~3.1.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - estraverse@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" @@ -1625,121 +2067,59 @@ event-target-shim@^5.0.0: events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" - integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= + integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== -exec-sh@^0.3.2: - version "0.3.6" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" - integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" is-stream "^2.0.0" merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" strip-final-newline "^2.0.0" exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= +expect@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expect@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" - integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== - dependencies: - "@jest/types" "^26.6.2" - ansi-styles "^4.0.0" - jest-get-type "^26.3.0" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@~3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" - integrity sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ= + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== -extsprintf@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" - integrity sha1-4QgOBljjALBilJkMxw4VAiNf1VA= +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -1747,7 +2127,7 @@ fast-json-stable-stringify@^2.0.0: fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fb-watchman@^2.0.0: version "2.0.1" @@ -1757,9 +2137,9 @@ fb-watchman@^2.0.0: bser "2.1.1" fengari-interop@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/fengari-interop/-/fengari-interop-0.1.2.tgz#f7731dcdd2ff4449073fb7ac3c451a8841ce1e87" - integrity sha512-8iTvaByZVoi+lQJhHH9vC+c/Yaok9CwOqNQZN6JrVpjmWwW4dDkeblBXhnHC+BoI6eF4Cy5NKW3z6ICEjvgywQ== + version "0.1.3" + resolved "https://registry.yarnpkg.com/fengari-interop/-/fengari-interop-0.1.3.tgz#3ad37a90e7430b69b365441e9fc0ba168942a146" + integrity sha512-EtZ+oTu3kEwVJnoymFPBVLIbQcCoy9uWCVnMA6h3M/RqHkUBsLYp29+RRHf9rKr6GwjubWREU1O7RretFIXjHw== fengari@^0.1.4: version "0.1.4" @@ -1777,15 +2157,12 @@ fetch-cookie@0.10.1: dependencies: tough-cookie "^2.3.3 || ^3.0.1 || ^4.0.0" -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= +fetch-cookie@0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-0.11.0.tgz#e046d2abadd0ded5804ce7e2cae06d4331c15407" + integrity sha512-BQm7iZLFhMWFy5CZ/162sAGjBfdNWb7a8LEqqnzsHFhxT/X/SVj/z2t2nu3aJvjlbQkrAlTUApplPRjWyH4mhA== dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" + tough-cookie "^2.3.3 || ^3.0.1 || ^4.0.0" fill-range@^7.0.1: version "7.0.1" @@ -1802,15 +2179,15 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= +follow-redirects@^1.14.4: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== form-data@^3.0.0: version "3.0.1" @@ -1821,15 +2198,6 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@~2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" - integrity sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE= - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -1839,24 +2207,29 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" +fresh@^0.5.2, fresh@~0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.1.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -1869,14 +2242,29 @@ function-bind@^1.1.1: functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.1: +get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -1886,55 +2274,58 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^4.0.0: +get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" -get-stream@^5.0.0: +get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== dependencies: assert-plus "^1.0.0" -glob@^5.0.15: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" + is-glob "^4.0.1" glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== + dependencies: + ini "2.0.0" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -1943,7 +2334,7 @@ globals@^11.1.0: google-auth-library@~0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.10.0.tgz#6e15babee85fd1dd14d8d128a295b6838d52136e" - integrity sha1-bhW6vuhf0d0U2NEoopW2g41SE24= + integrity sha512-KM54Y9GhdAzfXUHmWEoYmaOykSLuMG7W4HvVLYqyogxOyE6px8oSS8W13ngqW0oDGZ915GFW3V6OM6+qcdvPOA== dependencies: gtoken "^1.2.1" jws "^3.1.4" @@ -1953,56 +2344,55 @@ google-auth-library@~0.10.0: google-p12-pem@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-0.1.2.tgz#33c46ab021aa734fa0332b3960a9a3ffcb2f3177" - integrity sha1-M8RqsCGqc0+gMys5YKmj/8svMXc= + integrity sha512-puhMlJ2+E/rgvxWaqgN/nC7x623OAE8MR9vBUqxF0inCE7HoVfCHvTeQ9+BR+rj9KM0fIg6XV6tmbt7XHHssoQ== dependencies: node-forge "^0.7.1" googleapis@^16.0.0: version "16.1.0" resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-16.1.0.tgz#0f19f2d70572d918881a0f626e3b1a2fa8629576" - integrity sha1-Dxny1wVy2RiIGg9ibjsaL6hilXY= + integrity sha512-5czmF7xkIlJKc1+/+5tltrI1skoR3HKtkDOld9rk+DOucTpZRjOhCoJzoSjxB3M8rP2tEb1VIr1TPyzR3V2PUQ== dependencies: async "~2.1.4" google-auth-library "~0.10.0" string-template "~1.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.2.4: - version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" - integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +graceful-fs@^4.1.2, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== gtoken@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.2.tgz#172776a1a9d96ac09fc22a00f5be83cee6de8820" - integrity sha1-Fyd2oanZasCfwioA9b6DzubeiCA= + version "1.2.3" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.3.tgz#5509571b8afd4322e124cf66cf68115284c476d8" + integrity sha512-wQAJflfoqSgMWrSBk9Fg86q+sd6s7y6uJhIvvIPz++RElGlMtEqsdAR2oWwZ/WTEtp7P9xFbJRrT976oRgzJ/w== dependencies: google-p12-pem "^0.1.0" jws "^3.0.0" - mime "^1.2.11" + mime "^1.4.1" request "^2.72.0" -har-schema@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" - integrity sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4= - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" - integrity sha1-M0gdDxu/9gDdID11gSpqX7oALio= - dependencies: - ajv "^4.9.1" - har-schema "^1.0.5" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== har-validator@~5.1.3: version "5.1.5" @@ -2015,43 +2405,34 @@ har-validator@~5.1.3: has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" +has-symbols@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-value@^1.0.0: +has-tostringtag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" + has-symbols "^1.0.2" -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== has@^1.0.3: version "1.0.3" @@ -2060,26 +2441,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hawk@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" - integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ= - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" - -hoek@2.x.x: - version "2.16.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" - integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" @@ -2092,6 +2453,30 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +http-assert@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.5.0.tgz#c389ccd87ac16ed2dfa6246fd73b926aa00e6b8f" + integrity sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w== + dependencies: + deep-equal "~1.0.1" + http-errors "~1.8.0" + +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http-errors@^1.6.3, http-errors@~1.8.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.1" + http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" @@ -2101,49 +2486,50 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-signature@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" - integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8= - dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== dependencies: assert-plus "^1.0.0" jsprim "^1.2.2" sshpk "^1.7.0" https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: agent-base "6" debug "4" -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -iconv-lite@0.4.24, iconv-lite@^0.4.5: +iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@1.1.13, ieee754@^1.1.13, ieee754@^1.1.4: +ieee754@1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ieee754@^1.1.13, ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + immediate@3.3.0, immediate@^3.2.3: version "3.3.0" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.3.0.tgz#1aef225517836bcdf7f2a2de2600c79ff0269266" @@ -2152,12 +2538,17 @@ immediate@3.3.0, immediate@^3.2.3: immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A== import-local@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.3.tgz#4d51c2c495ca9393da259ec66b62e022920211e0" - integrity sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== dependencies: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" @@ -2165,22 +2556,32 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ioredis-mock@^5.5.5: +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +ioredis-mock@5.8.0: version "5.8.0" resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-5.8.0.tgz#99d3f0fe58419c2ff15c81cb4e13709aff8f2a44" integrity sha512-PokVyNC/d3qaAyHAdf+Ex4HHBe7GdaSGrcn8pP7ran+pVmSrJ6Aofwm7Od5bQ5JtjDSiylEtIoZzEPYIjUKMHA== @@ -2190,7 +2591,7 @@ ioredis-mock@^5.5.5: lodash "^4.17.21" standard-as-callback "^2.1.0" -ioredis@^4.27.1: +ioredis@4.28.0: version "4.28.0" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.28.0.tgz#5a2be3f37ff2075e2332f280eaeb02ab4d9ff0d3" integrity sha512-I+zkeeWp3XFgPT2CtJKxvaF5FjGBGt4yGYljRjQecdQKteThuAsKqffeF1lgHVlYnuNeozRbPOCDNZ7tDWPeig== @@ -2207,26 +2608,19 @@ ioredis@^4.27.1: redis-parser "^3.0.0" standard-as-callback "^2.1.0" -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-buffer@^1.1.5: +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -2238,61 +2632,17 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.2.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" - integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== +is-core-module@^2.8.1: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== dependencies: has "^1.0.3" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -2304,34 +2654,57 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== dependencies: - kind-of "^3.0.2" + has-tostringtag "^1.0.0" + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-retry-allowed@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz#88f34cbd236e043e71b6932d09b0c65fb7b4d71d" + integrity sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg== is-stream@^2.0.0: version "2.0.1" @@ -2341,71 +2714,42 @@ is-stream@^2.0.0: is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== -isarray@1.0.0, isarray@^1.0.0: +isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" - integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== - dependencies: - "@babel/core" "^7.7.5" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" - semver "^6.3.0" - -istanbul-lib-instrument@^5.0.4: - version "5.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" - integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== dependencies: "@babel/core" "^7.12.3" "@babel/parser" "^7.14.7" @@ -2431,208 +2775,236 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.2: - version "3.0.5" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384" - integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ== +istanbul-reports@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" + integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" - integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== +jest-changed-files@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== dependencies: - "@jest/types" "^26.6.2" - execa "^4.0.0" - throat "^5.0.0" + "@jest/types" "^27.5.1" + execa "^5.0.0" + throat "^6.0.1" -jest-cli@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" - integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== +jest-circus@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== dependencies: - "@jest/core" "^26.6.3" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== + dependencies: + "@jest/core" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" chalk "^4.0.0" exit "^0.1.2" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" import-local "^3.0.2" - is-ci "^2.0.0" - jest-config "^26.6.3" - jest-util "^26.6.2" - jest-validate "^26.6.2" + jest-config "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" prompts "^2.0.1" - yargs "^15.4.1" + yargs "^16.2.0" -jest-config@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" - integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== +jest-config@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.6.3" - "@jest/types" "^26.6.2" - babel-jest "^26.6.3" + "@babel/core" "^7.8.0" + "@jest/test-sequencer" "^27.5.1" + "@jest/types" "^27.5.1" + babel-jest "^27.5.1" chalk "^4.0.0" + ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.1" - graceful-fs "^4.2.4" - jest-environment-jsdom "^26.6.2" - jest-environment-node "^26.6.2" - jest-get-type "^26.3.0" - jest-jasmine2 "^26.6.3" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - micromatch "^4.0.2" - pretty-format "^26.6.2" + graceful-fs "^4.2.9" + jest-circus "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-get-type "^27.5.1" + jest-jasmine2 "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runner "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^27.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" -jest-diff@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" - integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== dependencies: chalk "^4.0.0" - diff-sequences "^26.6.2" - jest-get-type "^26.3.0" - pretty-format "^26.6.2" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" -jest-docblock@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" - integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== +jest-docblock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== dependencies: detect-newline "^3.0.0" -jest-each@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" - integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== +jest-each@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" chalk "^4.0.0" - jest-get-type "^26.3.0" - jest-util "^26.6.2" - pretty-format "^26.6.2" + jest-get-type "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" -jest-environment-jsdom@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" - integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== +jest-environment-jsdom@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" - jsdom "^16.4.0" + jest-mock "^27.5.1" + jest-util "^27.5.1" + jsdom "^16.6.0" -jest-environment-node@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" - integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== +jest-environment-node@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-mock "^27.5.1" + jest-util "^27.5.1" -jest-get-type@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" - integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== -jest-haste-map@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" - integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== +jest-haste-map@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/graceful-fs" "^4.1.2" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" - graceful-fs "^4.2.4" - jest-regex-util "^26.0.0" - jest-serializer "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" - micromatch "^4.0.2" - sane "^4.0.3" + graceful-fs "^4.2.9" + jest-regex-util "^27.5.1" + jest-serializer "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + micromatch "^4.0.4" walker "^1.0.7" optionalDependencies: - fsevents "^2.1.2" + fsevents "^2.3.2" -jest-jasmine2@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" - integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== +jest-jasmine2@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^26.6.2" + expect "^27.5.1" is-generator-fn "^2.0.0" - jest-each "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - pretty-format "^26.6.2" - throat "^5.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + throat "^6.0.1" -jest-leak-detector@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" - integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== +jest-leak-detector@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== dependencies: - jest-get-type "^26.3.0" - pretty-format "^26.6.2" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" -jest-matcher-utils@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" - integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== +jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== dependencies: chalk "^4.0.0" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - pretty-format "^26.6.2" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" -jest-message-util@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" - integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.6.2" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.5.1" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.2" - pretty-format "^26.6.2" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^27.5.1" slash "^3.0.0" - stack-utils "^2.0.2" + stack-utils "^2.0.3" -jest-mock@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" - integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== +jest-mock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" jest-pnp-resolver@^1.2.2: @@ -2640,189 +3012,191 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-regex-util@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" - integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== +jest-regex-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== -jest-resolve-dependencies@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" - integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== +jest-resolve-dependencies@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== dependencies: - "@jest/types" "^26.6.2" - jest-regex-util "^26.0.0" - jest-snapshot "^26.6.2" + "@jest/types" "^27.5.1" + jest-regex-util "^27.5.1" + jest-snapshot "^27.5.1" -jest-resolve@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" - integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== +jest-resolve@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" chalk "^4.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" jest-pnp-resolver "^1.2.2" - jest-util "^26.6.2" - read-pkg-up "^7.0.1" - resolve "^1.18.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" - integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== +jest-runner@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - emittery "^0.7.1" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-docblock "^26.0.0" - jest-haste-map "^26.6.2" - jest-leak-detector "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - jest-runtime "^26.6.3" - jest-util "^26.6.2" - jest-worker "^26.6.2" + emittery "^0.8.1" + graceful-fs "^4.2.9" + jest-docblock "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-haste-map "^27.5.1" + jest-leak-detector "^27.5.1" + jest-message-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runtime "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" source-map-support "^0.5.6" - throat "^5.0.0" + throat "^6.0.1" -jest-runtime@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" - integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== +jest-runtime@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/globals" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/yargs" "^15.0.0" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/globals" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" chalk "^4.0.0" - cjs-module-lexer "^0.6.0" + cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" - exit "^0.1.2" + execa "^5.0.0" glob "^7.1.3" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" slash "^3.0.0" strip-bom "^4.0.0" - yargs "^15.4.1" -jest-serializer@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" - integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== +jest-serializer@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== dependencies: "@types/node" "*" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" -jest-snapshot@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" - integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== +jest-snapshot@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" "@babel/types" "^7.0.0" - "@jest/types" "^26.6.2" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/babel__traverse" "^7.0.4" - "@types/prettier" "^2.0.0" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^26.6.2" - graceful-fs "^4.2.4" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - jest-haste-map "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" + expect "^27.5.1" + graceful-fs "^4.2.9" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + jest-haste-map "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-util "^27.5.1" natural-compare "^1.4.0" - pretty-format "^26.6.2" + pretty-format "^27.5.1" semver "^7.3.2" -jest-util@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" - integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== +jest-util@^27.0.0, jest-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - micromatch "^4.0.2" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" -jest-validate@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" - integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== +jest-validate@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== dependencies: - "@jest/types" "^26.6.2" - camelcase "^6.0.0" + "@jest/types" "^27.5.1" + camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^26.3.0" + jest-get-type "^27.5.1" leven "^3.1.0" - pretty-format "^26.6.2" + pretty-format "^27.5.1" -jest-watcher@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" - integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== +jest-watcher@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== dependencies: - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^26.6.2" + jest-util "^27.5.1" string-length "^4.0.1" -jest-worker@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== +jest-worker@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: "@types/node" "*" merge-stream "^2.0.0" - supports-color "^7.0.0" + supports-color "^8.0.0" -jest@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" - integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== +jest@27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" + integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== dependencies: - "@jest/core" "^26.6.3" + "@jest/core" "^27.5.1" import-local "^3.0.2" - jest-cli "^26.6.3" + jest-cli "^27.5.1" jmespath@0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" - integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= + integrity sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w== -jodid25519@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" - integrity sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc= - dependencies: - jsbn "~0.1.0" +join-component@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5" + integrity sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ== js-tokens@^4.0.0: version "4.0.0" @@ -2840,9 +3214,9 @@ js-yaml@^3.13.1: jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== -jsdom@^16.4.0: +jsdom@^16.6.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== @@ -2880,6 +3254,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -2890,36 +3269,22 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== -json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= - dependencies: - jsonify "~0.0.0" - -json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" +json5@2.x, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= - -jsonwebtoken@^8.2.0, jsonwebtoken@^8.5.1: +jsonwebtoken@8.5.1, jsonwebtoken@^8.2.0: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== @@ -2936,35 +3301,14 @@ jsonwebtoken@^8.2.0, jsonwebtoken@^8.5.1: semver "^5.6.0" jsprim@^1.2.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" - integrity sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg= + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: assert-plus "1.0.0" - extsprintf "1.0.2" - json-schema "0.2.3" - verror "1.3.6" - -jstransform@~11.0.0: - version "11.0.3" - resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223" - integrity sha1-CaeJk+CuTU70SH9hVakfYZDLQiM= - dependencies: - base62 "^1.1.0" - commoner "^0.10.1" - esprima-fb "^15001.1.0-dev-harmony-fb" - object-assign "^2.0.0" - source-map "^0.4.2" - -jwa@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" - integrity sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU= - dependencies: - base64url "2.0.0" - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.9" - safe-buffer "^5.0.1" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" jwa@^1.4.1: version "1.4.1" @@ -2975,16 +3319,7 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jws@^3.0.0, jws@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" - integrity sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI= - dependencies: - base64url "^2.0.0" - jwa "^1.1.4" - safe-buffer "^5.0.1" - -jws@^3.2.2: +jws@^3.0.0, jws@^3.1.4, jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== @@ -2992,42 +3327,136 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= +keygrip@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.3.tgz#399d709f0aed2bab0a059e0cdd3a5023a053e1dc" + integrity sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g== + +keygrip@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" + integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ== dependencies: - is-buffer "^1.1.5" + tsscmp "1.0.6" -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + json-buffer "3.0.0" kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -koa-passport@^4.1.4: +koa-compose@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-3.2.1.tgz#a85ccb40b7d986d8e5a345b3a1ace8eabcf54de7" + integrity sha512-8gen2cvKHIZ35eDEik5WOo8zbVp9t4cP8p4hW4uE55waxolLRexKKrqfCpwhGVppnB40jWeF8bZeTVg99eZgPw== + dependencies: + any-promise "^1.1.0" + +koa-compose@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877" + integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw== + +koa-convert@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-1.2.0.tgz#da40875df49de0539098d1700b50820cebcd21d0" + integrity sha512-K9XqjmEDStGX09v3oxR7t5uPRy0jqJdvodHa6wxWTHrTfDq0WUNnYTOOUZN6g8OM8oZQXprQASbiIXG2Ez8ehA== + dependencies: + co "^4.6.0" + koa-compose "^3.0.0" + +koa-convert@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-2.0.0.tgz#86a0c44d81d40551bae22fee6709904573eea4f5" + integrity sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA== + dependencies: + co "^4.6.0" + koa-compose "^4.1.0" + +koa-is-json@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14" + integrity sha512-+97CtHAlWDx0ndt0J8y3P12EWLwTLMXIfMnYDev3wOTwH/RpBGMlfn4bDXlMEg1u73K6XRE9BbUp+5ZAYoRYWw== + +koa-passport@4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/koa-passport/-/koa-passport-4.1.4.tgz#5f1665c1c2a37ace79af9f970b770885ca30ccfa" integrity sha512-dJBCkl4X+zdYxbI2V2OtoGy0PUenpvp2ZLLWObc8UJhsId0iQpTFT8RVcuA0709AL2txGwRHnSPoT1bYNGa6Kg== dependencies: passport "^0.4.0" +koa@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.7.0.tgz#7e00843506942b9d82c6cc33749f657c6e5e7adf" + integrity sha512-7ojD05s2Q+hFudF8tDLZ1CpCdVZw8JQELWSkcfG9bdtoTDzMmkRF6BQBU7JzIzCCOY3xd3tftiy/loHBUYaY2Q== + dependencies: + accepts "^1.3.5" + cache-content-type "^1.0.0" + content-disposition "~0.5.2" + content-type "^1.0.4" + cookies "~0.7.1" + debug "~3.1.0" + delegates "^1.0.0" + depd "^1.1.2" + destroy "^1.0.4" + error-inject "^1.0.0" + escape-html "^1.0.3" + fresh "~0.5.2" + http-assert "^1.3.0" + http-errors "^1.6.3" + is-generator-function "^1.0.7" + koa-compose "^4.1.0" + koa-convert "^1.2.0" + koa-is-json "^1.0.0" + on-finished "^2.3.0" + only "~0.0.2" + parseurl "^1.3.2" + statuses "^1.5.0" + type-is "^1.6.16" + vary "^1.1.2" + +koa@^2.13.4: + version "2.13.4" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.4.tgz#ee5b0cb39e0b8069c38d115139c774833d32462e" + integrity sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g== + dependencies: + accepts "^1.3.5" + cache-content-type "^1.0.0" + content-disposition "~0.5.2" + content-type "^1.0.4" + cookies "~0.8.0" + debug "^4.3.2" + delegates "^1.0.0" + depd "^2.0.0" + destroy "^1.0.4" + encodeurl "^1.0.2" + escape-html "^1.0.3" + fresh "~0.5.2" + http-assert "^1.3.0" + http-errors "^1.6.3" + is-generator-function "^1.0.7" + koa-compose "^4.1.0" + koa-convert "^2.0.0" + on-finished "^2.3.0" + only "~0.0.2" + parseurl "^1.3.2" + statuses "^1.5.0" + type-is "^1.6.16" + vary "^1.1.2" + +latest-version@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + level-codec@9.0.2, level-codec@^9.0.0: version "9.0.2" resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-9.0.2.tgz#fd60df8c64786a80d44e63423096ffead63d8cbc" @@ -3084,7 +3513,7 @@ level-supports@~1.0.0: level-write-stream@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/level-write-stream/-/level-write-stream-1.0.0.tgz#3f7fbb679a55137c0feb303dee766e12ee13c1dc" - integrity sha1-P3+7Z5pVE3wP6zA97nZuEu4Twdw= + integrity sha512-bBNKOEOMl8msO+uIM9YX/gUO6ckokZ/4pCwTm/lwvs46x6Xs8Zy0sn3Vh37eDqse4mhy4fOMIb/JsSM2nyQFtw== dependencies: end-stream "~0.1.0" @@ -3125,7 +3554,7 @@ leven@^3.1.0: levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== dependencies: prelude-ls "~1.1.2" type-check "~0.3.2" @@ -3133,14 +3562,14 @@ levn@~0.3.0: lie@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" - integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= + integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== dependencies: immediate "~3.0.5" lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== locate-path@^5.0.0: version "5.0.0" @@ -3152,68 +3581,83 @@ locate-path@^5.0.0: lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== -lodash.isarguments@^3.1.0: +lodash.isarguments@3.1.0, lodash.isarguments@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== lodash.isnumber@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.noop@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" - integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw= + integrity sha512-TmYdmu/pebrdTIBDK/FDx9Bmfzs9x0sZG6QIJuMDTqEPfeciLcN13ij+cOd0i9vwJfBtbG9UQ+C7MkXgYxrIJg== lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== -lodash@^4.14.0: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= +lodash.pick@^4.0.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q== -lodash@^4.17.21, lodash@^4.7.0: +lodash@4.17.21, lodash@^4.14.0, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -3224,15 +3668,20 @@ lru-cache@^6.0.0: ltgt@2.2.1, ltgt@^2.1.2, ltgt@~2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" - integrity sha1-81ypHEk/e3PaDgdJUwTxezH4fuU= + integrity sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA== -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -3240,22 +3689,24 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== dependencies: - object-visit "^1.0.0" + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== memdown@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/memdown/-/memdown-1.4.1.tgz#b4e4e192174664ffbae41361aa500f3119efe215" - integrity sha1-tOThkhdGZP+65BNhqlAPMRnv4hU= + integrity sha512-iVrGHZB8i4OQfM155xx8akvG9FIj+ht14DX5CQkCTG4EHzZ3d3sgckIf/Lm9ivZalEsFuEVnWv2B2WZvbrro2w== dependencies: abstract-leveldown "~2.7.1" functional-red-black-tree "^1.0.1" @@ -3264,130 +3715,107 @@ memdown@1.4.1: ltgt "~2.2.0" safe-buffer "~5.1.1" +merge-descriptors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== +methods@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" + braces "^3.0.2" + picomatch "^2.3.1" -micromatch@^4.0.2: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + mime-db "1.52.0" -mime-db@1.51.0: - version "1.51.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== - -mime-db@~1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" - integrity sha1-gg9XIpa70g7CXtVeW13oaeVDbrE= - -mime-types@^2.1.12, mime-types@~2.1.7: - version "2.1.15" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" - integrity sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0= - dependencies: - mime-db "~1.27.0" - -mime-types@~2.1.19: - version "2.1.34" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" - integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== - dependencies: - mime-db "1.51.0" - -mime@^1.2.11: - version "1.3.6" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" - integrity sha1-WR2E02U6awtKO5343lqoEI5y5eA= +mime@^1.3.4, mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -"minimatch@2 || 3", minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.0: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== +minipass@^3.0.0: + version "3.1.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" + integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" mkdirp-classic@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@^0.5.0: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" +ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== napi-macros@~2.0.0: version "2.0.0" @@ -3397,19 +3825,34 @@ napi-macros@~2.0.0: natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +ndjson@^1.4.3: + version "1.5.0" + resolved "https://registry.yarnpkg.com/ndjson/-/ndjson-1.5.0.tgz#ae603b36b134bcec347b452422b0bf98d5832ec8" + integrity sha512-hUPLuaziboGjNF7wHngkgVc0FOclR8dDk/HfEvTtDr/iUrqBWiRcRSTK3/nLOqKH33th714BrMmTPtObI9gZxQ== + dependencies: + json-stringify-safe "^5.0.1" + minimist "^1.2.0" + split2 "^2.1.0" + through2 "^2.0.3" + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +node-addon-api@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" + integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== node-fetch@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== -node-fetch@^2.6.1: +node-fetch@2.6.7, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -3417,9 +3860,9 @@ node-fetch@^2.6.1: whatwg-url "^5.0.0" node-forge@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300" - integrity sha1-naYR6giYL0uUIGs760zJZl8gwwA= + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== node-gyp-build@~4.1.0: version "4.1.1" @@ -3429,76 +3872,91 @@ node-gyp-build@~4.1.0: node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-modules-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" - integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= - -node-notifier@^8.0.0: - version "8.0.2" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" - integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== +node-mocks-http@^1.5.8: + version "1.11.0" + resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.11.0.tgz#defc0febf6b935f08245397d47534a8de592996e" + integrity sha512-jS/WzSOcKbOeGrcgKbenZeNhxUNnP36Yw11+hL4TTxQXErGfqYZ+MaYNNvhaTiGIJlzNSqgQkk9j8dSu1YWSuw== dependencies: - growly "^1.3.0" - is-wsl "^2.2.0" - semver "^7.3.2" - shellwords "^0.1.1" - uuid "^8.3.0" - which "^2.0.2" + accepts "^1.3.7" + content-disposition "^0.5.3" + depd "^1.1.0" + fresh "^0.5.2" + merge-descriptors "^1.0.1" + methods "^1.1.2" + mime "^1.3.4" + parseurl "^1.3.3" + range-parser "^1.2.0" + type-is "^1.6.18" -node-releases@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" - integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== +node-releases@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" + integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== +nodemon@2.0.16: + version "2.0.16" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.16.tgz#d71b31bfdb226c25de34afea53486c8ef225fdef" + integrity sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w== dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.8" + semver "^5.7.1" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + update-notifier "^5.1.0" -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== dependencies: - remove-trailing-separator "^1.0.1" + abbrev "1" -normalize-path@^3.0.0: +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" +normalize-url@^4.1.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== -npm-run-path@^4.0.0: +npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -oauth-sign@~0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -3507,50 +3965,39 @@ oauth-sign@~0.9.0: oauth@0.9.x, oauth@^0.9.15: version "0.9.15" resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" - integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= + integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA== -object-assign@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" - integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo= +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= +on-finished@^2.3.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" + ee-first "1.1.1" once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -onetime@^5.1.0: +onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" +only@~0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" + integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ== + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -3566,17 +4013,12 @@ optionator@^0.8.1: os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== -p-each-series@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" - integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== p-limit@^2.2.0: version "2.3.0" @@ -3602,7 +4044,17 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -parse-json@^5.0.0: +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + +parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -3617,15 +4069,15 @@ parse5@6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +parseurl@^1.3.2, parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -passport-google-auth@^1.0.2: +passport-google-auth@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938" - integrity sha1-izALWqRC70M94dgy7TESh30LKTg= + integrity sha512-cfAqna6jZLyMEwUdd4PIwAh2mQKQVEDAaRIaom1pG6h4x4Gwjllf/Jflt3TkR1Sen5Rkvr3l7kSXCWE1EKkh8g== dependencies: googleapis "^16.0.0" passport-strategy "1.x" @@ -3633,7 +4085,7 @@ passport-google-auth@^1.0.2: passport-google-oauth1@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc" - integrity sha1-r3SoA99R7GRvZqRNgigr5vEI4Mw= + integrity sha512-qpCEhuflJgYrdg5zZIpAq/K3gTqa1CtHjbubsEsidIdpBPLkEVq6tB1I8kBNcH89RdSiYbnKpCBXAZXX/dtx1Q== dependencies: passport-oauth1 "1.x.x" @@ -3644,7 +4096,7 @@ passport-google-oauth20@2.x.x: dependencies: passport-oauth2 "1.x.x" -passport-google-oauth@^2.0.0: +passport-google-oauth@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/passport-google-oauth/-/passport-google-oauth-2.0.0.tgz#f6eb4bc96dd6c16ec0ecfdf4e05ec48ca54d4dae" integrity sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA== @@ -3652,7 +4104,7 @@ passport-google-oauth@^2.0.0: passport-google-oauth1 "1.x.x" passport-google-oauth20 "2.x.x" -passport-jwt@^4.0.0: +passport-jwt@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.0.tgz#7f0be7ba942e28b9f5d22c2ebbb8ce96ef7cf065" integrity sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg== @@ -3660,10 +4112,10 @@ passport-jwt@^4.0.0: jsonwebtoken "^8.2.0" passport-strategy "^1.0.0" -passport-local@^1.0.0: +passport-local@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" - integrity sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4= + integrity sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow== dependencies: passport-strategy "1.x.x" @@ -3676,6 +4128,11 @@ passport-oauth1@1.x.x: passport-strategy "1.x.x" utils-merge "1.x.x" +passport-oauth2-refresh@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/passport-oauth2-refresh/-/passport-oauth2-refresh-2.1.0.tgz#c31cd133826383f5539d16ad8ab4f35ca73ce4a4" + integrity sha512-4ML7ooCESCqiTgdDBzNUFTBcPR8zQq9iM6eppEUGMMvLdsjqRL93jKwWm4Az3OJcI+Q2eIVyI8sVRcPFvxcF/A== + passport-oauth2@1.x.x: version "1.6.1" resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.1.tgz#c5aee8f849ce8bd436c7f81d904a3cd1666f181b" @@ -3690,7 +4147,7 @@ passport-oauth2@1.x.x: passport-strategy@1.x, passport-strategy@1.x.x, passport-strategy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" - integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= + integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== passport@^0.4.0: version "0.4.1" @@ -3708,19 +4165,14 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -3728,34 +4180,27 @@ path-parse@^1.0.6: pause@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" - integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= - -performance-now@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" - integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU= + integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pirates@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" - integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== - dependencies: - node-modules-regexp "^1.0.0" +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== pkg-dir@^4.2.0: version "4.2.0" @@ -3764,10 +4209,41 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +posthog-node@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/posthog-node/-/posthog-node-1.3.0.tgz#804ed2f213a2f05253f798bf9569d55a9cad94f7" + integrity sha512-2+VhqiY/rKIqKIXyvemBFHbeijHE25sP7eKltnqcFqAssUE6+sX6vusN9A4luzToOqHQkUZexiCKxvuGagh7JA== + dependencies: + axios "0.24.0" + axios-retry "^3.1.9" + component-type "^1.2.1" + join-component "^1.1.0" + md5 "^2.3.0" + ms "^2.1.3" + remove-trailing-slash "^0.1.1" + uuid "^8.3.2" + +pouch-stream@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/pouch-stream/-/pouch-stream-0.4.1.tgz#0c6d8475c9307677627991a2f079b301c3b89bdd" + integrity sha512-RAWFhsGDbG4xZQpvrrQlhrITVUNVCKmglfe5WWDnJaDf1u9DMaRLHv//m65tBZevuo4QTGjwcyggwYxd7AGLsg== + dependencies: + inherits "^2.0.1" + readable-stream "^1.0.27-1" + +pouchdb-abstract-mapreduce@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/pouchdb-abstract-mapreduce/-/pouchdb-abstract-mapreduce-7.2.2.tgz#dd1b10a83f8d24361dce9aaaab054614b39f766f" + integrity sha512-7HWN/2yV2JkwMnGnlp84lGvFtnm0Q55NiBUdbBcaT810+clCGKvhssBCrXnmwShD1SXTwT83aszsgiSfW+SnBA== + dependencies: + pouchdb-binary-utils "7.2.2" + pouchdb-collate "7.2.2" + pouchdb-collections "7.2.2" + pouchdb-errors "7.2.2" + pouchdb-fetch "7.2.2" + pouchdb-mapreduce-utils "7.2.2" + pouchdb-md5 "7.2.2" + pouchdb-utils "7.2.2" pouchdb-adapter-leveldb-core@7.2.2: version "7.2.2" @@ -3789,7 +4265,7 @@ pouchdb-adapter-leveldb-core@7.2.2: sublevel-pouchdb "7.2.2" through2 "3.0.2" -pouchdb-adapter-memory@^7.2.2: +pouchdb-adapter-memory@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/pouchdb-adapter-memory/-/pouchdb-adapter-memory-7.2.2.tgz#c0ec2e87928d516ca9d1b5badc7269df6f95e5ea" integrity sha512-9o+zdItPEq7rIrxdkUxgsLNaZkDJAGEqqoYgeYdrHidOCZnlhxhX3g7/R/HcpDKC513iEPqJWDJQSfeT6nVKkw== @@ -3810,17 +4286,6 @@ pouchdb-adapter-utils@7.2.2: pouchdb-merge "7.2.2" pouchdb-utils "7.2.2" -pouchdb-all-dbs@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pouchdb-all-dbs/-/pouchdb-all-dbs-1.1.1.tgz#85f04a39cafda52497ec49abf1c93bb5e72813f6" - integrity sha512-UUnsdmcnRSQ8MAOYSJjfTwKkQNb/6fvOfd/f7dNNivWZ2YDYVuMfgw1WQdL634yEtcXTxAENZ/EyLRdzPCB41A== - dependencies: - argsarray "0.0.1" - es3ify "^0.2.2" - inherits "~2.0.1" - pouchdb-promise "6.4.3" - tiny-queue "^0.2.0" - pouchdb-binary-utils@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/pouchdb-binary-utils/-/pouchdb-binary-utils-7.2.2.tgz#0690b348052c543b1e67f032f47092ca82bcb10e" @@ -3828,6 +4293,11 @@ pouchdb-binary-utils@7.2.2: dependencies: buffer-from "1.1.1" +pouchdb-collate@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/pouchdb-collate/-/pouchdb-collate-7.2.2.tgz#fc261f5ef837c437e3445fb0abc3f125d982c37c" + integrity sha512-/SMY9GGasslknivWlCVwXMRMnQ8myKHs4WryQ5535nq1Wj/ehpqWloMwxEQGvZE1Sda3LOm7/5HwLTcB8Our+w== + pouchdb-collections@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/pouchdb-collections/-/pouchdb-collections-7.2.2.tgz#aeed77f33322429e3f59d59ea233b48ff0e68572" @@ -3840,6 +4310,28 @@ pouchdb-errors@7.2.2: dependencies: inherits "2.0.4" +pouchdb-fetch@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/pouchdb-fetch/-/pouchdb-fetch-7.2.2.tgz#492791236d60c899d7e9973f9aca0d7b9cc02230" + integrity sha512-lUHmaG6U3zjdMkh8Vob9GvEiRGwJfXKE02aZfjiVQgew+9SLkuOxNw3y2q4d1B6mBd273y1k2Lm0IAziRNxQnA== + dependencies: + abort-controller "3.0.0" + fetch-cookie "0.10.1" + node-fetch "2.6.0" + +pouchdb-find@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/pouchdb-find/-/pouchdb-find-7.2.2.tgz#1227afdd761812d508fe0794b3e904518a721089" + integrity sha512-BmFeFVQ0kHmDehvJxNZl9OmIztCjPlZlVSdpijuFbk/Fi1EFPU1BAv3kLC+6DhZuOqU/BCoaUBY9sn66pPY2ag== + dependencies: + pouchdb-abstract-mapreduce "7.2.2" + pouchdb-collate "7.2.2" + pouchdb-errors "7.2.2" + pouchdb-fetch "7.2.2" + pouchdb-md5 "7.2.2" + pouchdb-selector-core "7.2.2" + pouchdb-utils "7.2.2" + pouchdb-json@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/pouchdb-json/-/pouchdb-json-7.2.2.tgz#b939be24b91a7322e9a24b8880a6e21514ec5e1f" @@ -3847,6 +4339,16 @@ pouchdb-json@7.2.2: dependencies: vuvuzela "1.0.3" +pouchdb-mapreduce-utils@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/pouchdb-mapreduce-utils/-/pouchdb-mapreduce-utils-7.2.2.tgz#13a46a3cc2a3f3b8e24861da26966904f2963146" + integrity sha512-rAllb73hIkU8rU2LJNbzlcj91KuulpwQu804/F6xF3fhZKC/4JQMClahk+N/+VATkpmLxp1zWmvmgdlwVU4HtQ== + dependencies: + argsarray "0.0.1" + inherits "2.0.4" + pouchdb-collections "7.2.2" + pouchdb-utils "7.2.2" + pouchdb-md5@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/pouchdb-md5/-/pouchdb-md5-7.2.2.tgz#415401acc5a844112d765bd1fb4e5d9f38fb0838" @@ -3860,13 +4362,34 @@ pouchdb-merge@7.2.2: resolved "https://registry.yarnpkg.com/pouchdb-merge/-/pouchdb-merge-7.2.2.tgz#940d85a2b532d6a93a6cab4b250f5648511bcc16" integrity sha512-6yzKJfjIchBaS7Tusuk8280WJdESzFfQ0sb4jeMUNnrqs4Cx3b0DIEOYTRRD9EJDM+je7D3AZZ4AT0tFw8gb4A== -pouchdb-promise@6.4.3: +pouchdb-promise@^6.0.4: version "6.4.3" resolved "https://registry.yarnpkg.com/pouchdb-promise/-/pouchdb-promise-6.4.3.tgz#74516f4acf74957b54debd0fb2c0e5b5a68ca7b3" integrity sha512-ruJaSFXwzsxRHQfwNHjQfsj58LBOY1RzGzde4PM5CWINZwFjCQAhZwfMrch2o/0oZT6d+Xtt0HTWhq35p3b0qw== dependencies: lie "3.1.1" +pouchdb-replication-stream@1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/pouchdb-replication-stream/-/pouchdb-replication-stream-1.2.9.tgz#aa4fa5d8f52df4825392f18e07c7e11acffc650a" + integrity sha512-hM8XRBfamTTUwRhKwLS/jSNouBhn9R/4ugdHNRD1EvJzwV8iImh6sDYbCU9PGuznjyOjXz6vpFRzKeI2KYfwnQ== + dependencies: + argsarray "0.0.1" + inherits "^2.0.3" + lodash.pick "^4.0.0" + ndjson "^1.4.3" + pouch-stream "^0.4.0" + pouchdb-promise "^6.0.4" + through2 "^2.0.0" + +pouchdb-selector-core@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/pouchdb-selector-core/-/pouchdb-selector-core-7.2.2.tgz#264d7436a8c8ac3801f39960e79875ef7f3879a0" + integrity sha512-XYKCNv9oiNmSXV5+CgR9pkEkTFqxQGWplnVhO3W9P154H08lU0ZoNH02+uf+NjZ2kjse7Q1fxV4r401LEcGMMg== + dependencies: + pouchdb-collate "7.2.2" + pouchdb-utils "7.2.2" + pouchdb-utils@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/pouchdb-utils/-/pouchdb-utils-7.2.2.tgz#c17c4788f1d052b0daf4ef8797bbc4aaa3945aa4" @@ -3881,17 +4404,17 @@ pouchdb-utils@7.2.2: pouchdb-md5 "7.2.2" uuid "8.1.0" -pouchdb@^7.2.1: - version "7.2.2" - resolved "https://registry.yarnpkg.com/pouchdb/-/pouchdb-7.2.2.tgz#fcae82862db527e4cf7576ed8549d1384961f364" - integrity sha512-5gf5nw5XH/2H/DJj8b0YkvG9fhA/4Jt6kL0Y8QjtztVjb1y4J19Rg4rG+fUbXu96gsUrlyIvZ3XfM0b4mogGmw== +pouchdb@7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/pouchdb/-/pouchdb-7.3.0.tgz#440fbef12dfd8f9002320802528665e883a3b7f8" + integrity sha512-OwsIQGXsfx3TrU1pLruj6PGSwFH+h5k4hGNxFkZ76Um7/ZI8F5TzUHFrpldVVIhfXYi2vP31q0q7ot1FSLFYOw== dependencies: abort-controller "3.0.0" argsarray "0.0.1" - buffer-from "1.1.1" + buffer-from "1.1.2" clone-buffer "1.0.0" double-ended-queue "2.1.0-0" - fetch-cookie "0.10.1" + fetch-cookie "0.11.0" immediate "3.3.0" inherits "2.0.4" level "6.0.1" @@ -3900,32 +4423,36 @@ pouchdb@^7.2.1: leveldown "5.6.0" levelup "4.4.0" ltgt "2.2.1" - node-fetch "2.6.0" + node-fetch "2.6.7" readable-stream "1.1.14" - spark-md5 "3.0.1" + spark-md5 "3.0.2" through2 "3.0.2" - uuid "8.1.0" + uuid "8.3.2" vuvuzela "1.0.3" prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -pretty-format@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" - integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== + +pretty-format@^27.0.0, pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== dependencies: - "@jest/types" "^26.6.2" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" react-is "^17.0.1" -private@^0.1.6, private@~0.1.5: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== prompts@^2.0.1: version "2.4.2" @@ -3938,13 +4465,18 @@ prompts@^2.0.1: prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -3956,73 +4488,61 @@ pump@^3.0.0: punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - -qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" - integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= +pupa@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== + +range-parser@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +rc@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" react-is@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - -readable-stream@1.1.14: +readable-stream@1.1.14, readable-stream@^1.0.27-1: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ== dependencies: core-util-is "~1.0.0" inherits "~2.0.1" isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0: +"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -4034,23 +4554,33 @@ readable-stream@1.1.14: readable-stream@~0.0.2: version "0.0.4" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-0.0.4.tgz#f32d76e3fb863344a548d79923007173665b3b8d" - integrity sha1-8y124/uGM0SlSNeZIwBxc2ZbO40= + integrity sha512-azrivNydKRYt7zwLV5wWUK7YzKTWs3q87xSmY6DlHapPrCvaT6ZrukvM5erV+yCSSPmZT8zkSdttOHQpWWm9zw== + +readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" readline-sync@^1.4.9: version "1.4.10" resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== -recast@^0.11.17: - version "0.11.23" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" - integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= - dependencies: - ast-types "0.9.6" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - redis-commands@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" @@ -4059,67 +4589,47 @@ redis-commands@1.7.0: redis-errors@^1.0.0, redis-errors@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== redis-parser@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== dependencies: redis-errors "^1.0.0" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== +redlock@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/redlock/-/redlock-4.2.0.tgz#c26590768559afd5fff76aa1133c94b411ff4f5f" + integrity sha512-j+oQlG+dOwcetUt2WJWttu4CZVeRzUrcVcISFmEmfyuwCVSJ93rDT7YSgg7H7rnxwoRyk/jU46kycVka5tW7jA== dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" + bluebird "^3.7.2" -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -request@^2.72.0, request@^2.74.0: - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" - integrity sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA= +registry-auth-token@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~4.2.1" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" - uuid "^3.0.0" + rc "^1.2.8" -request@^2.88.0: +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + +remove-trailing-slash@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz#be2285a59f39c74d1bce4f825950061915e3780d" + integrity sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA== + +request@^2.72.0, request@^2.74.0, request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -4148,12 +4658,7 @@ request@^2.88.0: require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== resolve-cwd@^3.0.0: version "3.0.0" @@ -4167,84 +4672,60 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== -resolve@^1.10.0, resolve@^1.18.1: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== +resolve@^1.20.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== + dependencies: + lowercase-keys "^1.0.0" -rimraf@^3.0.0: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" -rsvp@^4.8.4: - version "4.8.5" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" - integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== - -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.1: +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sane@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" - integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== - dependencies: - "@cnakazawa/watch" "^1.0.3" - anymatch "^2.0.0" - capture-exit "^2.0.0" - exec-sh "^0.3.2" - execa "^1.0.0" - fb-watchman "^2.0.0" - micromatch "^3.1.4" - minimist "^1.1.1" - walker "~1.0.5" - -sanitize-s3-objectkey@^0.0.1: +sanitize-s3-objectkey@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/sanitize-s3-objectkey/-/sanitize-s3-objectkey-0.0.1.tgz#efa9887cd45275b40234fb4bb12fc5754fe64e7e" integrity sha512-ZTk7aqLxy4sD40GWcYWoLfbe05XLmkKvh6vGKe13ADlei24xlezcvjgKy1qRArlaIbIMYaqK7PCalvZtulZlaQ== -sax@1.2.1, sax@>=0.6.0: +sax@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" - integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= + integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== -sax@>=0.1.1: +sax@>=0.1.1, sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -4256,44 +4737,39 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + +semver@7.3.7, semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -shebang-command@^1.2.0: +setprototypeof@1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== shebang-command@^2.0.0: version "2.0.0" @@ -4302,30 +4778,20 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - shimmer@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.5" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" - integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== sisteransi@^1.0.5: version "1.0.5" @@ -4337,126 +4803,40 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= - dependencies: - hoek "2.x.x" - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - source-map-support@^0.5.6: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - -source-map@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - integrity sha1-66T12pwNyZneaAMti092FzZSA2s= - dependencies: - amdefine ">=0.0.4" - -source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== spark-md5@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.1.tgz#83a0e255734f2ab4e5c466e5a2cfc9ba2aa2124d" integrity sha512-0tF3AGSD1ppQeuffsLDIOWlKUd3lS92tFxcsrh5Pe3ZphhnoK+oXIBTzOAThZCiuINZLvpiLH/1VS1/ANEJVig== -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== +spark-md5@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc" + integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw== + +split2@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493" + integrity sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw== dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" - integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" + through2 "^2.0.2" sprintf-js@^1.1.1: version "1.1.2" @@ -4466,30 +4846,24 @@ sprintf-js@^1.1.1: sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== sshpk@^1.7.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.0.tgz#ff2a3e4fd04497555fed97b39a0fd82fafb3a33c" - integrity sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw= + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - optionalDependencies: bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" ecc-jsbn "~0.1.1" - jodid25519 "^1.0.0" + getpass "^0.1.1" jsbn "~0.1.0" + safer-buffer "^2.0.2" tweetnacl "~0.14.0" -stack-chain@^1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" - integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= - -stack-utils@^2.0.2: +stack-utils@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== @@ -4501,18 +4875,15 @@ standard-as-callback@^2.1.0: resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" +"statuses@>= 1.5.0 < 2", statuses@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== step@0.0.x: version "0.0.6" resolved "https://registry.yarnpkg.com/step/-/step-0.0.6.tgz#143e7849a5d7d3f4a088fe29af94915216eeede2" - integrity sha1-FD54SaXX0/SgiP4pr5SRUhbu7eI= + integrity sha512-qSSeQinUJk2w38vUFobjFoE307GqsozMC8VisOCkJLpklvKPT0ptPHwWOrENoag8rgLudvTkfP3bancwP93/Jw== string-length@^4.0.1: version "4.0.2" @@ -4525,9 +4896,9 @@ string-length@^4.0.1: string-template@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96" - integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y= + integrity sha512-SLqR3GBUXuoPP5MmYtD7ompvXiG87QjT6lzOszyXjTM86Uu7At7vNnt2xgyTLq5o9T4IxTYFyGxcULqpsmsfdg== -string-width@^4.1.0, string-width@^4.2.0: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4546,12 +4917,14 @@ string_decoder@^1.1.1: string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== -stringstream@~0.0.4: - version "0.0.6" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" - integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" @@ -4565,16 +4938,21 @@ strip-bom@^4.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + sublevel-pouchdb@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/sublevel-pouchdb/-/sublevel-pouchdb-7.2.2.tgz#49e46cd37883bf7ff5006d7c5b9bcc7bcc1f422f" @@ -4585,7 +4963,7 @@ sublevel-pouchdb@7.2.2: ltgt "2.2.1" readable-stream "1.1.14" -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -4599,6 +4977,13 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-hyperlinks@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" @@ -4607,12 +4992,17 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -tar-fs@^2.1.1: +tar-fs@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== @@ -4633,6 +5023,18 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" +tar@^6.1.11: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -4650,10 +5052,10 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== through2@3.0.2: version "3.0.2" @@ -4663,15 +5065,18 @@ through2@3.0.2: inherits "^2.0.4" readable-stream "2 || 3" -through@~2.3.4: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +through2@^2.0.0, through2@^2.0.2, through2@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" -tiny-queue@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046" - integrity sha1-JaZ/LG4lOyypQZd7XvdELvl6YEY= +timekeeper@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/timekeeper/-/timekeeper-2.2.0.tgz#9645731fce9e3280a18614a57a9d1b72af3ca368" + integrity sha512-W3AmPTJWZkRwu+iSNxPIsLZ2ByADsOLbbLxe46UJyWj3mlYLlwucKiq+/dPm0l9wTzqoF3/2PH0AGFCebjq23A== tmp@^0.0.33: version "0.0.33" @@ -4690,20 +5095,10 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== to-regex-range@^5.0.1: version "5.0.1" @@ -4712,25 +5107,19 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -"tough-cookie@^2.3.3 || ^3.0.1 || ^4.0.0", tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== dependencies: - psl "^1.1.28" - punycode "^2.1.1" + nopt "~1.0.10" -tough-cookie@^4.0.0: +"tough-cookie@^2.3.3 || ^3.0.1 || ^4.0.0", tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== @@ -4739,12 +5128,13 @@ tough-cookie@^4.0.0: punycode "^2.1.1" universalify "^0.1.2" -tough-cookie@~2.3.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" - integrity sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo= +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== dependencies: - punycode "^1.4.1" + psl "^1.1.28" + punycode "^2.1.1" tr46@^2.1.0: version "2.1.0" @@ -4758,6 +5148,25 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +ts-jest@27.1.5: + version "27.1.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297" + integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^27.0.0" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "20.x" + +tsscmp@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" + integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -4782,20 +5191,23 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-is@^1.6.16, type-is@^1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" typedarray-to-buffer@^3.1.5: version "3.1.5" @@ -4804,33 +5216,52 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript@4.7.3: + version "4.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" + integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== + uid2@0.0.x: version "0.0.4" resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.4.tgz#033f3b1d5d32505f5ce5f888b9f3b667123c0a44" integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA== -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" + crypto-random-string "^2.0.0" universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= +update-notifier@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== dependencies: - has-value "^0.3.1" - isobject "^3.0.0" + boxen "^5.0.0" + chalk "^4.1.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" uri-js@^4.2.2: version "4.4.1" @@ -4839,10 +5270,12 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" url@0.10.3: version "0.10.3" @@ -4852,12 +5285,7 @@ url@0.10.3: punycode "1.3.2" querystring "0.2.0" -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -4877,44 +5305,38 @@ uuid@8.1.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== -uuid@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" - integrity sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE= +uuid@8.3.2, uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.0, uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-to-istanbul@^7.0.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" - integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== +v8-to-istanbul@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" + integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" source-map "^0.7.3" -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" +vary@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -verror@1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" - integrity sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw= +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= dependencies: - extsprintf "1.0.2" + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" vuvuzela@1.0.3: version "1.0.3" @@ -4935,7 +5357,7 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" -walker@^1.0.7, walker@~1.0.5: +walker@^1.0.7: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== @@ -4994,34 +5416,36 @@ whatwg-url@^8.0.0, whatwg-url@^8.5.0: tr46 "^2.1.0" webidl-conversions "^6.1.0" -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1, which@^2.0.2: +which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -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== +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" @@ -5050,9 +5474,14 @@ write-stream@~0.4.3: readable-stream "~0.0.2" ws@^7.4.6: - version "7.5.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" - integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== + version "7.5.8" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.8.tgz#ac2729881ab9e7cbaf8787fe3469a48c5c7f636a" + integrity sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw== + +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== xml-name-validator@^3.0.0: version "3.0.0" @@ -5084,47 +5513,45 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xtend@^4.0.2, xtend@~4.0.0: +xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@20.x, yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs@^15.4.1: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" + y18n "^5.0.5" + yargs-parser "^20.2.2" -zlib@^1.0.5: +ylru@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.3.2.tgz#0de48017473275a4cbdfc83a1eaf67c01af8a785" + integrity sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA== + +zlib@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0" integrity sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA= diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 1fbcd69cab..2d19a2a2ff 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "1.0.104-alpha.0", + "version": "1.1.22", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "^1.2.1", - "@budibase/string-templates": "^1.0.104-alpha.0", + "@budibase/string-templates": "^1.1.22", "@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/avatar": "^3.0.2", @@ -66,11 +66,12 @@ "@spectrum-css/radio": "^3.0.2", "@spectrum-css/search": "^3.0.2", "@spectrum-css/sidenav": "^3.0.2", + "@spectrum-css/slider": "3.0.1", "@spectrum-css/statuslight": "^3.0.2", "@spectrum-css/stepper": "^3.0.3", "@spectrum-css/switch": "^1.0.2", "@spectrum-css/table": "^3.0.1", - "@spectrum-css/tabs": "^3.0.1", + "@spectrum-css/tabs": "^3.2.12", "@spectrum-css/tags": "^3.0.2", "@spectrum-css/textfield": "^3.0.1", "@spectrum-css/toast": "^3.0.1", diff --git a/packages/bbui/src/ActionButton/ActionButton.svelte b/packages/bbui/src/ActionButton/ActionButton.svelte index 5ab7a7f047..53ba6c7e51 100644 --- a/packages/bbui/src/ActionButton/ActionButton.svelte +++ b/packages/bbui/src/ActionButton/ActionButton.svelte @@ -13,6 +13,7 @@ export let size = "M" export let active = false export let fullWidth = false + export let noPadding = false function longPress(element) { if (!longPressable) return @@ -41,6 +42,7 @@ class:spectrum-ActionButton--quiet={quiet} class:spectrum-ActionButton--emphasized={emphasized} class:is-selected={selected} + class:noPadding class:fullWidth class="spectrum-ActionButton spectrum-ActionButton--size{size}" class:active @@ -80,8 +82,14 @@ .active svg { color: var(--spectrum-global-color-blue-600); } - - .spectrum-ActionButton-label { - padding-bottom: 2px; + :global([dir="ltr"] .spectrum-ActionButton .spectrum-Icon) { + margin-left: 0; + } + .is-selected:not(.spectrum-ActionButton--emphasized) { + background: var(--spectrum-global-color-gray-300); + } + .noPadding { + padding: 0; + min-width: 0; } diff --git a/packages/bbui/src/ActionMenu/ActionMenu.svelte b/packages/bbui/src/ActionMenu/ActionMenu.svelte index 08425e8f59..c5602d6b0c 100644 --- a/packages/bbui/src/ActionMenu/ActionMenu.svelte +++ b/packages/bbui/src/ActionMenu/ActionMenu.svelte @@ -6,6 +6,7 @@ export let disabled = false export let align = "left" export let portalTarget + export let dataCy let anchor let dropdown @@ -36,7 +37,7 @@
- + diff --git a/packages/bbui/src/Banner/Banner.svelte b/packages/bbui/src/Banner/Banner.svelte index f41fb5f803..3810021a61 100644 --- a/packages/bbui/src/Banner/Banner.svelte +++ b/packages/bbui/src/Banner/Banner.svelte @@ -8,6 +8,7 @@ export let size = "S" export let extraButtonText export let extraButtonAction + export let showCloseButton = true let show = true @@ -39,22 +40,24 @@ {/if} -
- -
+ {#if showCloseButton} +
+ +
+ {/if} {/if} @@ -63,4 +66,7 @@ pointer-events: all; width: 100%; } + .spectrum-Button { + border: 1px solid rgba(255, 255, 255, 0.2); + } diff --git a/packages/bbui/src/Button/Button.svelte b/packages/bbui/src/Button/Button.svelte index e8f6b4500e..36abcbf4da 100644 --- a/packages/bbui/src/Button/Button.svelte +++ b/packages/bbui/src/Button/Button.svelte @@ -14,6 +14,7 @@ export let active = false export let tooltip = undefined export let dataCy + export let newStyles = false let showTooltip = false @@ -25,6 +26,7 @@ class:spectrum-Button--warning={warning} class:spectrum-Button--overBackground={overBackground} class:spectrum-Button--quiet={quiet} + class:new-styles={newStyles} class:active class="spectrum-Button spectrum-Button--size{size.toUpperCase()}" {disabled} @@ -93,4 +95,20 @@ padding-left: var(--spacing-m); line-height: 0; } + .spectrum-Button--primary.new-styles { + background: var(--spectrum-global-color-gray-800); + border-color: transparent; + color: var(--spectrum-global-color-gray-50); + } + .spectrum-Button--primary.new-styles:hover { + background: var(--spectrum-global-color-gray-900); + } + .spectrum-Button--secondary.new-styles { + background: var(--spectrum-global-color-gray-200); + border-color: transparent; + color: var(--spectrum-global-color-gray-900); + } + .spectrum-Button--secondary.new-styles:hover { + background: var(--spectrum-global-color-gray-300); + } diff --git a/packages/bbui/src/Divider/Divider.svelte b/packages/bbui/src/Divider/Divider.svelte index 2b4de9cfb0..e4f0f2fb61 100644 --- a/packages/bbui/src/Divider/Divider.svelte +++ b/packages/bbui/src/Divider/Divider.svelte @@ -16,6 +16,9 @@ /> diff --git a/packages/bbui/src/Form/Core/Combobox.svelte b/packages/bbui/src/Form/Core/Combobox.svelte index 6a6423cccf..2835b3cd40 100644 --- a/packages/bbui/src/Form/Core/Combobox.svelte +++ b/packages/bbui/src/Form/Core/Combobox.svelte @@ -18,23 +18,6 @@ const dispatch = createEventDispatcher() let open = false let focus = false - $: fieldText = getFieldText(value, options, placeholder) - - const getFieldText = (value, options, placeholder) => { - // Always use placeholder if no value - if (value == null || value === "") { - return placeholder || "Choose an option or type" - } - - // Wait for options to load if there is a value but no options - if (!options?.length) { - return "" - } - - // Render the label if the selected option is found, otherwise raw value - const selected = options.find(option => getOptionValue(option) === value) - return selected ? getOptionLabel(selected) : value - } const selectOption = value => { dispatch("change", value) @@ -69,7 +52,10 @@ {id} type="text" on:focus={() => (focus = true)} - on:blur={() => (focus = false)} + on:blur={() => { + focus = false + dispatch("blur") + }} on:change={onType} value={value || ""} placeholder={placeholder || ""} diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte index c1c4cc866f..39a7d9d626 100644 --- a/packages/bbui/src/Form/Core/DatePicker.svelte +++ b/packages/bbui/src/Form/Core/DatePicker.svelte @@ -15,35 +15,71 @@ export let placeholder = null export let appendTo = undefined export let timeOnly = false + export let ignoreTimezones = false const dispatch = createEventDispatcher() const flatpickrId = `${uuid()}-wrapper` let open = false - let flatpickr, flatpickrOptions, isTimeOnly + let flatpickr, flatpickrOptions + + const resolveTimeStamp = timestamp => { + let maskedDate = new Date(`0-${timestamp}`) + + if (maskedDate instanceof Date && !isNaN(maskedDate.getTime())) { + return maskedDate + } else { + return null + } + } - $: isTimeOnly = !timeOnly && value ? !isNaN(new Date(`0-${value}`)) : timeOnly $: flatpickrOptions = { element: `#${flatpickrId}`, - enableTime: isTimeOnly || enableTime || false, - noCalendar: isTimeOnly || false, + enableTime: timeOnly || enableTime || false, + noCalendar: timeOnly || false, altInput: true, - altFormat: isTimeOnly ? "H:i" : enableTime ? "F j Y, H:i" : "F j, Y", + altFormat: timeOnly ? "H:i" : enableTime ? "F j Y, H:i" : "F j, Y", wrap: true, appendTo, disableMobile: "true", + onReady: () => { + let timestamp = resolveTimeStamp(value) + if (timeOnly && timestamp) { + dispatch("change", timestamp.toISOString()) + } + }, } const handleChange = event => { const [dates] = event.detail + const noTimezone = enableTime && !timeOnly && ignoreTimezones let newValue = dates[0] if (newValue) { newValue = newValue.toISOString() } - // if time only set date component to today + + // If time only set date component to 2000-01-01 if (timeOnly) { - const todayDate = new Date().toISOString().split("T")[0] - newValue = `${todayDate}T${newValue.split("T")[1]}` + newValue = `2000-01-01T${newValue.split("T")[1]}` } + + // For date-only fields, construct a manual timestamp string without a time + // or time zone + else if (!enableTime) { + const year = dates[0].getFullYear() + const month = `${dates[0].getMonth() + 1}`.padStart(2, "0") + const day = `${dates[0].getDate()}`.padStart(2, "0") + newValue = `${year}-${month}-${day}T00:00:00.000` + } + + // For non-timezone-aware fields, create an ISO 8601 timestamp of the exact + // time picked, without timezone + else if (noTimezone) { + const offset = dates[0].getTimezoneOffset() * 60000 + newValue = new Date(dates[0].getTime() - offset) + .toISOString() + .slice(0, -1) + } + dispatch("change", newValue) } @@ -76,10 +112,13 @@ return null } let date - let time = new Date(`0-${val}`) + let time + // it is a string like 00:00:00, just time - if (timeOnly || (typeof val === "string" && !isNaN(time))) { - date = time + let ts = resolveTimeStamp(val) + + if (timeOnly && ts) { + date = ts } else if (val instanceof Date) { // Use real date obj if already parsed date = val @@ -90,10 +129,12 @@ // Treat as numerical timestamp date = new Date(parseInt(val)) } + time = date.getTime() if (isNaN(time)) { return null } + // By rounding to the nearest second we avoid locking up in an endless // loop in the builder, caused by potentially enriching {{ now }} to every // millisecond. @@ -101,7 +142,7 @@ } -{#key isTimeOnly} +{#key timeOnly} @@ -195,4 +236,7 @@ :global(.flatpickr-calendar) { font-family: "Source Sans Pro", sans-serif; } + .is-disabled { + pointer-events: none !important; + } diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte index d739e751c9..36515acbc5 100644 --- a/packages/bbui/src/Form/Core/Dropzone.svelte +++ b/packages/bbui/src/Form/Core/Dropzone.svelte @@ -18,6 +18,7 @@ export let fileSizeLimit = BYTES_IN_MB * 20 export let processFiles = null export let handleFileTooLarge = null + export let handleTooManyFiles = null export let gallery = true export let error = null export let fileTags = [] @@ -71,6 +72,13 @@ handleFileTooLarge(fileSizeLimit, value) return } + + const fileCount = fileList.length + value.length + if (handleTooManyFiles && maximum && fileCount > maximum) { + handleTooManyFiles(maximum) + return + } + if (processFiles) { const processedFiles = await processFiles(fileList) const newValue = [...value, ...processedFiles] diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index 143536a60a..fc9f801be2 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -6,12 +6,15 @@ import { createEventDispatcher } from "svelte" import clickOutside from "../../Actions/click_outside" import Search from "./Search.svelte" + import Icon from "../../Icon/Icon.svelte" + import StatusLight from "../../StatusLight/StatusLight.svelte" export let id = null export let disabled = false export let error = null export let fieldText = "" export let fieldIcon = "" + export let fieldColour = "" export let isPlaceholder = false export let placeholderOption = null export let options = [] @@ -20,6 +23,7 @@ export let getOptionLabel = option => option export let getOptionValue = option => option export let getOptionIcon = () => null + export let getOptionColour = () => null export let open = false export let readonly = false export let quiet = false @@ -43,7 +47,7 @@ return } searchTerm = null - open = true + open = !open } const getSortedOptions = (options, getLabel, sort) => { @@ -71,105 +75,77 @@ } - -{#if open} -
(open = false)} - transition:fly|local={{ y: -20, duration: 200 }} - class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open" - class:auto-width={autoWidth} - > - {#if autocomplete} - (searchTerm = event.detail)} - {disabled} - placeholder="Search" - /> - {/if} -
    - {#if placeholderOption} -
  • onSelectOption(null)} - > - {placeholderOption} - -
  • + + {#if open} +
    + {#if autocomplete} + (searchTerm = event.detail)} + {disabled} + placeholder="Search" + /> {/if} - {#if filteredOptions.length} - {#each filteredOptions as option, idx} +
      + {#if placeholderOption}
    • onSelectOption(getOptionValue(option, idx))} + on:click={() => onSelectOption(null)} > - {#if getOptionIcon(option, idx)} - - icon - - {/if} - - {getOptionLabel(option, idx)} - + {placeholderOption}
    • - {/each} - {/if} -
    -
    -{/if} + {/if} + {#if filteredOptions.length} + {#each filteredOptions as option, idx} +
  • onSelectOption(getOptionValue(option, idx))} + > + {#if getOptionIcon(option, idx)} + + + + {/if} + + {getOptionLabel(option, idx)} + + + {#if getOptionColour(option, idx)} + + + + {/if} +
  • + {/each} + {/if} +
+
+ {/if} + diff --git a/packages/bbui/src/Form/Core/TextField.svelte b/packages/bbui/src/Form/Core/TextField.svelte index 78b698eed2..0a723c140a 100644 --- a/packages/bbui/src/Form/Core/TextField.svelte +++ b/packages/bbui/src/Form/Core/TextField.svelte @@ -1,6 +1,6 @@
{/if} diff --git a/packages/bbui/src/Form/Core/index.js b/packages/bbui/src/Form/Core/index.js index 3c3f9acb4d..7c81cfd70b 100644 --- a/packages/bbui/src/Form/Core/index.js +++ b/packages/bbui/src/Form/Core/index.js @@ -3,6 +3,7 @@ export { default as CoreSelect } from "./Select.svelte" export { default as CoreMultiselect } from "./Multiselect.svelte" export { default as CoreCheckbox } from "./Checkbox.svelte" export { default as CoreRadioGroup } from "./RadioGroup.svelte" +export { default as CoreCheckboxGroup } from "./CheckboxGroup.svelte" export { default as CoreTextArea } from "./TextArea.svelte" export { default as CoreCombobox } from "./Combobox.svelte" export { default as CoreSwitch } from "./Switch.svelte" @@ -11,3 +12,4 @@ export { default as CoreDatePicker } from "./DatePicker.svelte" export { default as CoreDropzone } from "./Dropzone.svelte" export { default as CoreStepper } from "./Stepper.svelte" export { default as CoreRichTextField } from "./RichTextField.svelte" +export { default as CoreSlider } from "./Slider.svelte" diff --git a/packages/bbui/src/Form/DatePicker.svelte b/packages/bbui/src/Form/DatePicker.svelte index 9298c49177..a4b2379782 100644 --- a/packages/bbui/src/Form/DatePicker.svelte +++ b/packages/bbui/src/Form/DatePicker.svelte @@ -12,6 +12,7 @@ export let timeOnly = false export let placeholder = null export let appendTo = undefined + export let ignoreTimezones = false const dispatch = createEventDispatcher() @@ -30,6 +31,7 @@ {enableTime} {timeOnly} {appendTo} + {ignoreTimezones} on:change={onChange} /> diff --git a/packages/bbui/src/Form/Dropzone.svelte b/packages/bbui/src/Form/Dropzone.svelte index 757d76398b..f1b548f7f1 100644 --- a/packages/bbui/src/Form/Dropzone.svelte +++ b/packages/bbui/src/Form/Dropzone.svelte @@ -11,6 +11,7 @@ export let fileSizeLimit = undefined export let processFiles = undefined export let handleFileTooLarge = undefined + export let handleTooManyFiles = undefined export let gallery = true export let fileTags = [] export let maximum = undefined @@ -30,6 +31,7 @@ {fileSizeLimit} {processFiles} {handleFileTooLarge} + {handleTooManyFiles} {gallery} {fileTags} {maximum} diff --git a/packages/bbui/src/Form/Input.svelte b/packages/bbui/src/Form/Input.svelte index 1660b67799..251b23c86a 100644 --- a/packages/bbui/src/Form/Input.svelte +++ b/packages/bbui/src/Form/Input.svelte @@ -14,6 +14,7 @@ export let updateOnChange = true export let quiet = false export let dataCy + export let autofocus const dispatch = createEventDispatcher() const onChange = e => { @@ -33,6 +34,7 @@ {placeholder} {type} {quiet} + {autofocus} on:change={onChange} on:click on:input diff --git a/packages/bbui/src/Form/Search.svelte b/packages/bbui/src/Form/Search.svelte index 25dd98306b..74ffeeb22a 100644 --- a/packages/bbui/src/Form/Search.svelte +++ b/packages/bbui/src/Form/Search.svelte @@ -10,6 +10,7 @@ export let disabled = false export let updateOnChange = true export let quiet = false + export let inputRef const dispatch = createEventDispatcher() const onChange = e => { @@ -25,6 +26,7 @@ {value} {placeholder} {quiet} + bind:inputRef on:change={onChange} on:click on:input diff --git a/packages/bbui/src/Form/Select.svelte b/packages/bbui/src/Form/Select.svelte index 0df27e2ff0..1b68746c5e 100644 --- a/packages/bbui/src/Form/Select.svelte +++ b/packages/bbui/src/Form/Select.svelte @@ -14,6 +14,7 @@ export let getOptionLabel = option => extractProperty(option, "label") export let getOptionValue = option => extractProperty(option, "value") export let getOptionIcon = option => option?.icon + export let getOptionColour = option => option?.colour export let quiet = false export let autoWidth = false export let sort = false @@ -47,6 +48,7 @@ {getOptionLabel} {getOptionValue} {getOptionIcon} + {getOptionColour} on:change={onChange} on:click /> diff --git a/packages/bbui/src/Form/Slider.svelte b/packages/bbui/src/Form/Slider.svelte new file mode 100644 index 0000000000..34b2251b35 --- /dev/null +++ b/packages/bbui/src/Form/Slider.svelte @@ -0,0 +1,24 @@ + + + + + diff --git a/packages/bbui/src/Icon/Icon.svelte b/packages/bbui/src/Icon/Icon.svelte index fbaac7098c..9c99178fdb 100644 --- a/packages/bbui/src/Icon/Icon.svelte +++ b/packages/bbui/src/Icon/Icon.svelte @@ -3,6 +3,9 @@ - (showTooltip = true)} + on:focus={() => (showTooltip = true)} + on:mouseleave={() => (showTooltip = false)} + on:click={() => (showTooltip = false)} > - - + + + + {#if tooltip && showTooltip} +
+ +
+ {/if} +
diff --git a/packages/bbui/src/IconSideNav/IconSideNav.svelte b/packages/bbui/src/IconSideNav/IconSideNav.svelte new file mode 100644 index 0000000000..e8144402e4 --- /dev/null +++ b/packages/bbui/src/IconSideNav/IconSideNav.svelte @@ -0,0 +1,14 @@ +
+ +
+ + diff --git a/packages/bbui/src/IconSideNav/IconSideNavItem.svelte b/packages/bbui/src/IconSideNav/IconSideNavItem.svelte new file mode 100644 index 0000000000..46625c9707 --- /dev/null +++ b/packages/bbui/src/IconSideNav/IconSideNavItem.svelte @@ -0,0 +1,56 @@ + + +
(showTooltip = true)} + on:focus={() => (showTooltip = true)} + on:mouseleave={() => (showTooltip = false)} + on:click +> + + {#if tooltip && showTooltip} +
+ +
+ {/if} +
+ + diff --git a/packages/bbui/src/Layout/Layout.svelte b/packages/bbui/src/Layout/Layout.svelte index 0dcb1f46ee..6a01d9bece 100644 --- a/packages/bbui/src/Layout/Layout.svelte +++ b/packages/bbui/src/Layout/Layout.svelte @@ -36,6 +36,14 @@ padding-left: var(--spacing-l); padding-right: var(--spacing-l); } + .paddingX-XL { + padding-left: var(--spacing-xl); + padding-right: var(--spacing-xl); + } + .paddingX-XXL { + padding-left: var(--spectrum-alias-grid-gutter-large); + padding-right: var(--spectrum-alias-grid-gutter-large); + } .paddingY-S { padding-top: var(--spacing-s); padding-bottom: var(--spacing-s); @@ -48,6 +56,14 @@ padding-top: var(--spacing-l); padding-bottom: var(--spacing-l); } + .paddingY-XL { + padding-top: var(--spacing-xl); + padding-bottom: var(--spacing-xl); + } + .paddingY-XXL { + padding-top: var(--spectrum-alias-grid-gutter-large); + padding-bottom: var(--spectrum-alias-grid-gutter-large); + } .gap-XXS { grid-gap: var(--spacing-xs); } diff --git a/packages/bbui/src/Layout/Page.svelte b/packages/bbui/src/Layout/Page.svelte index 9658f9b9f1..2996bcc613 100644 --- a/packages/bbui/src/Layout/Page.svelte +++ b/packages/bbui/src/Layout/Page.svelte @@ -1,8 +1,10 @@ -
+
@@ -12,7 +14,7 @@ flex-direction: column; justify-content: flex-start; align-items: stretch; - max-width: 80ch; + max-width: var(--max-width); margin: 0 auto; padding: calc(var(--spacing-xl) * 2); min-height: calc(100% - var(--spacing-xl) * 4); @@ -22,4 +24,9 @@ max-width: none; margin: 0; } + + .noPadding { + padding: 0px; + margin: 0px; + } diff --git a/packages/bbui/src/Modal/ModalContent.svelte b/packages/bbui/src/Modal/ModalContent.svelte index 89c10bb625..10cd4b10ba 100644 --- a/packages/bbui/src/Modal/ModalContent.svelte +++ b/packages/bbui/src/Modal/ModalContent.svelte @@ -23,6 +23,7 @@ export let secondaryButtonText = undefined export let secondaryAction = undefined export let secondaryButtonWarning = false + export let dataCy = null const { hide, cancel } = getContext(Context.Modal) let loading = false @@ -63,21 +64,26 @@ role="dialog" tabindex="-1" aria-modal="true" + data-cy={dataCy} >
- {#if title} + {#if title || $$slots.header}

- {title} - + {#if title} + {title} + {:else if $$slots.header} + + {/if}

{#if showDivider} {/if} {/if} +
diff --git a/packages/bbui/src/Notification/Notification.svelte b/packages/bbui/src/Notification/Notification.svelte index 1d21131553..53ab062701 100644 --- a/packages/bbui/src/Notification/Notification.svelte +++ b/packages/bbui/src/Notification/Notification.svelte @@ -1,15 +1,20 @@ -
+
{#if icon} {/if} -
+
{message || ""}
+ {#if action} + +
{actionMessage}
+
+ {/if}
{#if dismissable}
@@ -46,4 +56,15 @@ .spectrum-Toast { pointer-events: all; } + + .wide { + width: 100%; + } + + .actionBody { + justify-content: space-between; + display: flex; + width: 100%; + align-items: center; + } diff --git a/packages/bbui/src/Notification/NotificationDisplay.svelte b/packages/bbui/src/Notification/NotificationDisplay.svelte index eb778f3aa0..0f7e93eb23 100644 --- a/packages/bbui/src/Notification/NotificationDisplay.svelte +++ b/packages/bbui/src/Notification/NotificationDisplay.svelte @@ -8,13 +8,15 @@
- {#each $notifications as { type, icon, message, id, dismissable } (id)} -
+ {#each $notifications as { type, icon, message, id, dismissable, action, wide } (id)} +
notifications.dismiss(id)} />
@@ -25,7 +27,7 @@ diff --git a/packages/bbui/src/ProgressBar/ProgressBar.svelte b/packages/bbui/src/ProgressBar/ProgressBar.svelte index 221453d428..0bc50fb452 100644 --- a/packages/bbui/src/ProgressBar/ProgressBar.svelte +++ b/packages/bbui/src/ProgressBar/ProgressBar.svelte @@ -16,11 +16,11 @@ easing: easing, }) - $: if (value) $progress = value + $: if (value || value === 0) $progress = value
{#if $$slots}
{/if} - {#if value} + {#if value || value === 0}
@@ -47,7 +47,7 @@